From df3c025cbc301a70cc49269293da364cce341225 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 7 Aug 2022 04:09:49 +0300 Subject: [PATCH 01/33] Initial VTF read support --- src/PIL/VtfImagePlugin.py | 233 ++++++++++++++++++++++++++++++++++++++ src/PIL/__init__.py | 1 + 2 files changed, 234 insertions(+) create mode 100644 src/PIL/VtfImagePlugin.py diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py new file mode 100644 index 00000000000..fe1a9076232 --- /dev/null +++ b/src/PIL/VtfImagePlugin.py @@ -0,0 +1,233 @@ +""" +A Pillow loader for .vtf files (aka Valve Texture Format) +REDxEYE + +Documentation: + https://developer.valvesoftware.com/wiki/Valve_Texture_Format + +The contents of this file are hereby released in the public domain (CC0) +Full text of the CC0 license: + https://creativecommons.org/publicdomain/zero/1.0/ +""" + +import struct +from enum import IntFlag, IntEnum +from typing import NamedTuple + +from . import Image, ImageFile + + +class VTFException(Exception): + pass + + +class CompiledVtfFlags(IntFlag): + # Flags from the *.txt config file + POINTSAMPLE = 0x00000001 + TRILINEAR = 0x00000002 + CLAMPS = 0x00000004 + CLAMPT = 0x00000008 + ANISOTROPIC = 0x00000010 + HINT_DXT5 = 0x00000020 + PWL_CORRECTED = 0x00000040 + NORMAL = 0x00000080 + NOMIP = 0x00000100 + NOLOD = 0x00000200 + ALL_MIPS = 0x00000400 + PROCEDURAL = 0x00000800 + + # These are automatically generated by vtex from the texture data. + ONEBITALPHA = 0x00001000 + EIGHTBITALPHA = 0x00002000 + + # Newer flags from the *.txt config file + ENVMAP = 0x00004000 + RENDERTARGET = 0x00008000 + DEPTHRENDERTARGET = 0x00010000 + NODEBUGOVERRIDE = 0x00020000 + SINGLECOPY = 0x00040000 + PRE_SRGB = 0x00080000 + + UNUSED_00100000 = 0x00100000 + UNUSED_00200000 = 0x00200000 + UNUSED_00400000 = 0x00400000 + + NODEPTHBUFFER = 0x00800000 + + UNUSED_01000000 = 0x01000000 + + CLAMPU = 0x02000000 + VERTEXTEXTURE = 0x04000000 + SSBUMP = 0x08000000 + + UNUSED_10000000 = 0x10000000 + + BORDER = 0x20000000 + + UNUSED_40000000 = 0x40000000 + UNUSED_80000000 = 0x80000000 + + +class VtfPF(IntEnum): + NONE = -1 + RGBA8888 = 0 + ABGR8888 = 1 + RGB888 = 2 + BGR888 = 3 + RGB565 = 4 + I8 = 5 + IA88 = 6 + P8 = 7 + A8 = 8 + RGB888_BLUESCREEN = 9 + BGR888_BLUESCREEN = 10 + ARGB8888 = 11 + BGRA8888 = 12 + DXT1 = 13 + DXT3 = 14 + DXT5 = 15 + BGRX8888 = 16 + BGR565 = 17 + BGRX5551 = 18 + BGRA4444 = 19 + DXT1_ONEBITALPHA = 20 + BGRA5551 = 21 + UV88 = 22 + UVWQ8888 = 23 + RGBA16161616F = 24 + RGBA16161616 = 25 + UVLX8888 = 26 + + +VTFHeader = NamedTuple("VTFHeader", [ + ("header_size", int), + ("width", int), + ("height", int), + ("flags", int), + ("frames", int), + ("first_frames", int), + ("padding0", int), + ("reflectivity_r", float), + ("reflectivity_g", float), + ("reflectivity_b", float), + ("padding1", int), + ("bumpmap_scale", float), + ("pixel_format", int), + ("mipmap_count", int), + ("lowres_format", int), + ("lowres_width", int), + ("lowres_height", int), + + # V 7.2+ + ('depth', int), + + # V 7.3+ + ('padding2', int), + ('padding2_', int), + ('resource_count', int), + ('padding3', int), + +]) +RGB_FORMATS = (VtfPF.RGB888,) +RGBA_FORMATS = (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5, VtfPF.RGBA8888) +L_FORMATS = (VtfPF.A8, VtfPF.I8,) +LA_FORMATS = (VtfPF.IA88, VtfPF.UV88,) + +SUPPORTED_FORMATS = RGBA_FORMATS + RGB_FORMATS + LA_FORMATS + L_FORMATS + + +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: + return width * height + elif pixel_format in LA_FORMATS: + return width * height * 2 + elif pixel_format in (VtfPF.RGB888,): + return width * height * 3 + elif pixel_format in (VtfPF.RGBA8888,): + return width * height * 4 + raise VTFException(f'Unsupported VTF pixel format: {pixel_format}') + + +class VtfImageFile(ImageFile.ImageFile): + format = "VTF" + format_description = "Valve Texture Format" + + def _open(self): + if not _accept(self.fp.read(12)): + raise SyntaxError("not a VTF file") + self.fp.seek(4) + version = struct.unpack('<2I', self.fp.read(8)) + if version <= (7, 2): + header = VTFHeader(*struct.unpack('> mip_id, min_res) + mip_height = max(header.height >> mip_id, min_res) + + data_start += _get_texture_size(pixel_format, mip_width, mip_height) + if pixel_format in (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA): + tile = ("bcn", (0, 0) + self.size, data_start, (1, 'DXT1')) + elif pixel_format == VtfPF.DXT3: + 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,): + tile = ("raw", (0, 0) + self.size, data_start, ("RGBA", 0, 1)) + elif pixel_format in (VtfPF.RGB888,): + tile = ("raw", (0, 0) + self.size, data_start, ("RGB", 0, 1)) + elif pixel_format in L_FORMATS: + tile = ("raw", (0, 0) + self.size, data_start, ("L", 0, 1)) + elif pixel_format in LA_FORMATS: + tile = ("raw", (0, 0) + self.size, data_start, ("LA", 0, 1)) + else: + raise VTFException(f'Unsupported VTF pixel format: {pixel_format}') + self.tile = [tile] + + +def _save(im, fp, filename): + if im.mode not in ("RGB", "RGBA"): + raise OSError(f"cannot write mode {im.mode} as VTF") + + +def _accept(prefix): + valid_header = prefix[:4] == b"VTF\x00" + valid_version = struct.unpack_from('<2I', prefix, 4) >= (7, 0) + return valid_header and valid_version + + +Image.register_open(VtfImageFile.format, VtfImageFile, _accept) +Image.register_save(VtfImageFile.format, _save) +Image.register_extension(VtfImageFile.format, ".vtf") diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index e65b155b2dc..4908a3fc30b 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -69,6 +69,7 @@ "XbmImagePlugin", "XpmImagePlugin", "XVThumbImagePlugin", + "VtfImagePlugin", ] From 9223c064828c3755d7b1f62a1e257ed2b96647d1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 7 Aug 2022 01:12:09 +0000 Subject: [PATCH 02/33] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/VtfImagePlugin.py | 136 +++++++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 52 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index fe1a9076232..9621ed2918c 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -11,7 +11,7 @@ """ import struct -from enum import IntFlag, IntEnum +from enum import IntEnum, IntFlag from typing import NamedTuple from . import Image, ImageFile @@ -99,39 +99,51 @@ class VtfPF(IntEnum): UVLX8888 = 26 -VTFHeader = NamedTuple("VTFHeader", [ - ("header_size", int), - ("width", int), - ("height", int), - ("flags", int), - ("frames", int), - ("first_frames", int), - ("padding0", int), - ("reflectivity_r", float), - ("reflectivity_g", float), - ("reflectivity_b", float), - ("padding1", int), - ("bumpmap_scale", float), - ("pixel_format", int), - ("mipmap_count", int), - ("lowres_format", int), - ("lowres_width", int), - ("lowres_height", int), - - # V 7.2+ - ('depth', int), - - # V 7.3+ - ('padding2', int), - ('padding2_', int), - ('resource_count', int), - ('padding3', int), - -]) +VTFHeader = NamedTuple( + "VTFHeader", + [ + ("header_size", int), + ("width", int), + ("height", int), + ("flags", int), + ("frames", int), + ("first_frames", int), + ("padding0", int), + ("reflectivity_r", float), + ("reflectivity_g", float), + ("reflectivity_b", float), + ("padding1", int), + ("bumpmap_scale", float), + ("pixel_format", int), + ("mipmap_count", int), + ("lowres_format", int), + ("lowres_width", int), + ("lowres_height", int), + # V 7.2+ + ("depth", int), + # V 7.3+ + ("padding2", int), + ("padding2_", int), + ("resource_count", int), + ("padding3", int), + ], +) RGB_FORMATS = (VtfPF.RGB888,) -RGBA_FORMATS = (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5, VtfPF.RGBA8888) -L_FORMATS = (VtfPF.A8, VtfPF.I8,) -LA_FORMATS = (VtfPF.IA88, VtfPF.UV88,) +RGBA_FORMATS = ( + VtfPF.DXT1, + VtfPF.DXT1_ONEBITALPHA, + VtfPF.DXT3, + VtfPF.DXT5, + VtfPF.RGBA8888, +) +L_FORMATS = ( + VtfPF.A8, + VtfPF.I8, +) +LA_FORMATS = ( + VtfPF.IA88, + VtfPF.UV88, +) SUPPORTED_FORMATS = RGBA_FORMATS + RGB_FORMATS + LA_FORMATS + L_FORMATS @@ -139,7 +151,14 @@ class VtfPF(IntEnum): 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, + ) + + L_FORMATS + ): return width * height elif pixel_format in LA_FORMATS: return width * height * 2 @@ -147,7 +166,7 @@ def _get_texture_size(pixel_format: VtfPF, width, height): return width * height * 3 elif pixel_format in (VtfPF.RGBA8888,): return width * height * 4 - raise VTFException(f'Unsupported VTF pixel format: {pixel_format}') + raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") class VtfImageFile(ImageFile.ImageFile): @@ -158,28 +177,34 @@ def _open(self): if not _accept(self.fp.read(12)): raise SyntaxError("not a VTF file") self.fp.seek(4) - version = struct.unpack('<2I', self.fp.read(8)) + version = struct.unpack("<2I", self.fp.read(8)) if version <= (7, 2): - header = VTFHeader(*struct.unpack('> mip_id, min_res) mip_height = max(header.height >> mip_id, min_res) data_start += _get_texture_size(pixel_format, mip_width, mip_height) if pixel_format in (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA): - tile = ("bcn", (0, 0) + self.size, data_start, (1, 'DXT1')) + tile = ("bcn", (0, 0) + self.size, data_start, (1, "DXT1")) elif pixel_format == VtfPF.DXT3: - tile = ("bcn", (0, 0) + self.size, data_start, (2, 'DXT3')) + 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')) + tile = ("bcn", (0, 0) + self.size, data_start, (3, "DXT5")) elif pixel_format in (VtfPF.RGBA8888,): tile = ("raw", (0, 0) + self.size, data_start, ("RGBA", 0, 1)) elif pixel_format in (VtfPF.RGB888,): @@ -213,7 +245,7 @@ def _open(self): elif pixel_format in LA_FORMATS: tile = ("raw", (0, 0) + self.size, data_start, ("LA", 0, 1)) else: - raise VTFException(f'Unsupported VTF pixel format: {pixel_format}') + raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") self.tile = [tile] @@ -224,7 +256,7 @@ def _save(im, fp, filename): def _accept(prefix): valid_header = prefix[:4] == b"VTF\x00" - valid_version = struct.unpack_from('<2I', prefix, 4) >= (7, 0) + valid_version = struct.unpack_from("<2I", prefix, 4) >= (7, 0) return valid_header and valid_version From 59c2d1990449fb44e467e1df9e1a4d08e6350629 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 7 Aug 2022 16:33:28 +0300 Subject: [PATCH 03/33] Adjusted formatting regions --- src/PIL/VtfImagePlugin.py | 43 +++++++++++++++------------------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 9621ed2918c..ecfc3e9764b 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -116,7 +116,7 @@ class VtfPF(IntEnum): ("bumpmap_scale", float), ("pixel_format", int), ("mipmap_count", int), - ("lowres_format", int), + ("lowres_pixel_format", int), ("lowres_width", int), ("lowres_height", int), # V 7.2+ @@ -148,17 +148,11 @@ class VtfPF(IntEnum): SUPPORTED_FORMATS = RGBA_FORMATS + RGB_FORMATS + LA_FORMATS + L_FORMATS +# fmt: off 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,) + L_FORMATS: return width * height elif pixel_format in LA_FORMATS: return width * height * 2 @@ -169,32 +163,32 @@ def _get_texture_size(pixel_format: VtfPF, width, height): raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") +# fmt: on + + class VtfImageFile(ImageFile.ImageFile): format = "VTF" format_description = "Valve Texture Format" + # fmt: off def _open(self): if not _accept(self.fp.read(12)): raise SyntaxError("not a VTF file") self.fp.seek(4) version = struct.unpack("<2I", self.fp.read(8)) if version <= (7, 2): - header = VTFHeader( - *struct.unpack("> mip_id, min_res) mip_height = max(header.height >> mip_id, min_res) data_start += _get_texture_size(pixel_format, mip_width, mip_height) + if pixel_format in (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA): tile = ("bcn", (0, 0) + self.size, data_start, (1, "DXT1")) elif pixel_format == VtfPF.DXT3: @@ -247,6 +235,7 @@ def _open(self): else: raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") self.tile = [tile] + # fmt: on def _save(im, fp, filename): From 719a3ba709c8f45407b26129aa073faeeea126d3 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Wed, 10 Aug 2022 03:21:11 +0300 Subject: [PATCH 04/33] Initial implementation of Bcn encoder --- setup.py | 1 + src/PIL/VtfImagePlugin.py | 41 ++++++++ src/_imaging.c | 3 + src/encode.c | 29 ++++++ src/libImaging/Bcn.h | 29 ++++++ src/libImaging/BcnDecode.c | 23 +---- src/libImaging/BcnEncode.c | 205 +++++++++++++++++++++++++++++++++++++ src/libImaging/Imaging.h | 2 + 8 files changed, 311 insertions(+), 22 deletions(-) create mode 100644 src/libImaging/BcnEncode.c diff --git a/setup.py b/setup.py index 7a1fabe23f6..f8b7f6959d0 100755 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ def get_version(): "Reduce", "Bands", "BcnDecode", + "BcnEncode", "BitDecode", "Blend", "Chops", diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index ecfc3e9764b..13836790f89 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -163,6 +163,21 @@ def _get_texture_size(pixel_format: VtfPF, width, height): raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") +def _get_mipmap_count(pixel_format: VtfPF, width: int, height: int): + if pixel_format in (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5): + min_size = 4 + else: + min_size = 1 + mip_count = 0 + for i in range(8): + mip_width = width << i + mip_height = height << i + if mip_width < min_size or mip_height < min_size: + break + mip_count += 1 + return mip_count + + # fmt: on @@ -239,8 +254,34 @@ def _open(self): def _save(im, fp, filename): + im:Image.Image if im.mode not in ("RGB", "RGBA"): raise OSError(f"cannot write mode {im.mode} as VTF") + arguments = im.encoderinfo + pixel_format = VtfPF(arguments.get('pixel_format', VtfPF.RGBA8888)) + flags = CompiledVtfFlags(0) + if 'A' in im.mode: + if pixel_format == VtfPF.DXT1_ONEBITALPHA: + flags |= CompiledVtfFlags.ONEBITALPHA + else: + flags |= CompiledVtfFlags.EIGHTBITALPHA + + width, height = im.size + + lowres_width = min(16, width) + lowres_height = min(16, height) + mipmap_count = _get_mipmap_count(pixel_format, width, height) + thumb = im.convert('RGB') + thumb.thumbnail((lowres_width,lowres_height)) + ImageFile._save(thumb, fp, [("bcn", (0, 0, lowres_width, lowres_height), 96, (1, 'DXT1'))]) + + header = VTFHeader(96, width, height, flags, 1, 1, 0, 1.0, 1.0, 1.0, 0, 1.0, pixel_format, mipmap_count, + VtfPF.DXT1, lowres_width, lowres_height, 0, 0, 0, 0, 0) + fp.write( + b"VTF\x00" + + struct.pack('<2I', 7, 2) + + struct.pack('encode = ImagingBcnEncode; + encoder->state.ystep = 4; + encoder->cleanup = NULL; + + return (PyObject *)encoder; +} + /* -------------------------------------------------------------------- */ /* EPS */ /* -------------------------------------------------------------------- */ diff --git a/src/libImaging/Bcn.h b/src/libImaging/Bcn.h index 1a6fbee4576..743c5d12123 100644 --- a/src/libImaging/Bcn.h +++ b/src/libImaging/Bcn.h @@ -1,3 +1,32 @@ typedef struct { char *pixel_format; } BCNSTATE; + +typedef struct { + UINT8 r, g, b, a; +} rgba; + +typedef struct { + UINT8 l; +} lum; + +typedef struct { + FLOAT32 r, g, b; +} rgb32f; + +typedef struct { + UINT16 c0, c1; + UINT32 lut; +} bc1_color; + +typedef struct { + UINT8 a0, a1; + UINT8 lut[6]; +} bc3_alpha; + +typedef struct { + INT8 a0, a1; + UINT8 lut[6]; +} bc5s_alpha; + +rgba decode_565(UINT16 x); \ No newline at end of file diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index a57b74b61ac..9163d83355f 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -15,28 +15,7 @@ #include "Bcn.h" -typedef struct { - UINT8 r, g, b, a; -} rgba; - -typedef struct { - UINT8 l; -} lum; - -typedef struct { - UINT16 c0, c1; - UINT32 lut; -} bc1_color; - -typedef struct { - UINT8 a0, a1; - UINT8 lut[6]; -} bc3_alpha; -typedef struct { - INT8 a0, a1; - UINT8 lut[6]; -} bc5s_alpha; #define LOAD16(p) (p)[0] | ((p)[1] << 8) @@ -49,7 +28,7 @@ bc1_color_load(bc1_color *dst, const UINT8 *src) { dst->lut = LOAD32(src + 4); } -static rgba +rgba decode_565(UINT16 x) { rgba c; int r, g, b; diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c new file mode 100644 index 00000000000..8dd381abca3 --- /dev/null +++ b/src/libImaging/BcnEncode.c @@ -0,0 +1,205 @@ +/* + * The Python Imaging Library + * + * encoder for DXTn-compressed data + * + * Format documentation: + * https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt + * + * The contents of this file are in the public domain (CC0) + * Full text of the CC0 license: + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "Imaging.h" +#include "Bcn.h" +#include "math.h" + +#define PACK_SHORT_565(r, g, b) \ + ((((r) << 8) & 0xF800) | (((g) << 3) & 0x7E0) | ((b) >> 3)) +#define BIT_MASK(bit_count) ((1 << (bit_count)) - 1) +#define SET_BITS(target, bit_offset, bit_count, value) \ + target |= (((value)&BIT_MASK(bit_count)) << (bit_offset)) +#define SWAP(TYPE, A, B) \ + do { \ + TYPE TMP = A; \ + A = B; \ + B = TMP; \ + } while (0) + +static UINT16 +rgb565_diff(UINT16 a, UINT16 b) { + UINT8 r0, g0, b0, r1, g1, b1; + r0 = a & 31; + a >>= 5; + g0 = a & 63; + a >>= 6; + b0 = a & 31; + r1 = b & 31; + b >>= 5; + g1 = a & 63; + b >>= 6; + b1 = b & 31; + + return abs(r0 - r1) + abs(g0 - g1) + abs(b0 - b1); +} + +static inline UINT16 +rgb565_lerp(UINT16 a, UINT16 b, UINT8 a_fac, UINT8 b_fac) { + UINT8 r0, g0, b0, r1, g1, b1; + r0 = a & 31; + a >>= 5; + g0 = a & 63; + a >>= 6; + b0 = a & 31; + r1 = b & 31; + b >>= 5; + g1 = b & 63; + b >>= 6; + b1 = b & 31; + return PACK_SHORT_565( + (r0 * a_fac + r1 * b_fac) / (a_fac + b_fac), + (g0 * a_fac + g1 * b_fac) / (a_fac + b_fac), + (b0 * a_fac + b1 * b_fac) / (a_fac + b_fac)); +} + +typedef struct { + UINT16 value; + UINT8 frequency; +} Color; + +static void +selection_sort(Color arr[], uint32_t n) { + uint32_t min_idx; + + for (uint32_t i = 0; i < n - 1; i++) { + min_idx = i; + for (uint32_t j = i + 1; j < n; j++) + if (arr[j].frequency < arr[min_idx].frequency) + min_idx = j; + SWAP(Color, arr[min_idx], arr[i]); + } +} + +static void +pick_2_major_colors( + const UINT16 *unique_colors, + const UINT8 *color_freq, + UINT16 color_count, + UINT16 *color0, + UINT16 *color1) { + Color colors[16] = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }; + for (int i = 0; i < color_count; ++i) { + colors[i].value = unique_colors[i]; + colors[i].frequency = color_freq[i]; + } + selection_sort(colors, color_count); + *color0 = colors[color_count - 1].value; + + if (color_count == 1) { + *color1 = colors[color_count - 1].value; + } else + *color1 = colors[color_count - 2].value; +} + +static UINT8 +get_closest_color_index(const UINT16 colors[4], UINT16 color) { + UINT16 color_error = 65535; + UINT16 lowest_id = 0; + + for (int color_id = 0; color_id < 4; color_id++) { + UINT8 error = rgb565_diff(colors[color_id], color); + if (error == 0) { + return color_id; + } + if (error < color_error) { + color_error = error; + lowest_id = color_id; + } + } + return lowest_id; +} + +int +ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + UINT8 *output = buf; + BOOL has_alpha = 1; + if (strchr(im->mode, 'A') == NULL) + has_alpha = 0; + UINT32 row_block_count = im->xsize / 4; + + UINT16 unique_count = 0; + UINT16 all_colors[16] = {0}; + UINT16 unique_colors[16] = {0}; + UINT8 color_frequency[16] = {0}; + + for (int block_x = 0; block_x < row_block_count; ++block_x) { + for (int by = 0; by < 4; ++by) { + for (int bx = 0; bx < 16; bx += 4) { + UINT8 r = (im->image[state->y + by][bx]); + UINT8 g = (im->image[state->y + by][bx + 1]); + UINT8 b = (im->image[state->y + by][bx] + 2); + UINT16 color = PACK_SHORT_565(r, g, b); + all_colors[bx + by * 4] = color; + BOOL new_color = 1; + for (UINT16 color_id = 0; color_id < unique_count; color_id++) { + if (unique_colors[color_id] == color) { + color_frequency[color_id]++; + new_color = 0; + break; + } + } + if (new_color) { + unique_colors[unique_count] = color; + color_frequency[unique_count]++; + unique_count++; + } + } + } + + UINT16 color0 = 0, color1 = 0; + pick_2_major_colors( + unique_colors, color_frequency, unique_count, &color0, &color1); + if (color0 < color1) + SWAP(UINT16, color0, color1); + + UINT16 output_colors[4] = {color0, color1, 0, 0}; + if (has_alpha) { + output_colors[2] = rgb565_lerp(color0, color1, 1, 1); + output_colors[3] = 0; + } else { + output_colors[2] = rgb565_lerp(color0, color1, 2, 1); + output_colors[3] = rgb565_lerp(color0, color1, 1, 2); + } + bc1_color *block = &((bc1_color *)output)[block_x]; + + block->c0 = color0; + block->c1 = color1; + for (UINT32 color_id = 0; color_id < 16; ++color_id) { + UINT8 bc_color_id = + get_closest_color_index(output_colors, all_colors[color_id]); + SET_BITS(block->lut, color_id * 2, 2, bc_color_id); + } + + output += sizeof(bc1_color); + } + + return output - buf; +} diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index d9ded185238..9e147895caa 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -548,6 +548,8 @@ typedef int (*ImagingCodec)( extern int ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); extern int +ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); +extern int ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); extern int ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); From b883b6491159803032b805bc9ec6aeba775b5f92 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Wed, 10 Aug 2022 03:30:42 +0300 Subject: [PATCH 05/33] Update copyright --- src/libImaging/BcnEncode.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 8dd381abca3..14065e9765f 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -1,14 +1,15 @@ /* - * The Python Imaging Library + * The Python Imaging Library. + * $Id$ * - * encoder for DXTn-compressed data + * decoder for packed bitfields (converts to floating point) * - * Format documentation: - * https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt + * history: + * 22-08-11 Initial implementation * - * The contents of this file are in the public domain (CC0) - * Full text of the CC0 license: - * https://creativecommons.org/publicdomain/zero/1.0/ + * Copyright (c) REDxEYE 2022. + * + * See the README file for information on usage and redistribution. */ #include "Imaging.h" From 0d9ac2af85622894dd9dac5fa527c2afc2963acb Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Wed, 10 Aug 2022 09:04:21 +0300 Subject: [PATCH 06/33] Update copyright --- src/libImaging/BcnEncode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 14065e9765f..401552b13dd 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -2,7 +2,7 @@ * The Python Imaging Library. * $Id$ * - * decoder for packed bitfields (converts to floating point) + * encoder for packed bitfields (ST3C/DXT) * * history: * 22-08-11 Initial implementation From 43592dde2bbf3d2cf553f8b46a835e3e7f0b5645 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Fri, 12 Aug 2022 03:12:49 +0300 Subject: [PATCH 07/33] Add Bcn BC1 encoder --- src/PIL/VtfImagePlugin.py | 100 ++++++++++++++------ src/encode.c | 8 +- src/libImaging/BcnEncode.c | 185 ++++++++++++++++++++----------------- 3 files changed, 176 insertions(+), 117 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 13836790f89..179c0a85fa3 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -12,6 +12,8 @@ import struct from enum import IntEnum, IntFlag +from io import BytesIO +from math import log, ceil from typing import NamedTuple from . import Image, ImageFile @@ -108,11 +110,9 @@ class VtfPF(IntEnum): ("flags", int), ("frames", int), ("first_frames", int), - ("padding0", int), ("reflectivity_r", float), ("reflectivity_g", float), ("reflectivity_b", float), - ("padding1", int), ("bumpmap_scale", float), ("pixel_format", int), ("mipmap_count", int), @@ -122,10 +122,7 @@ class VtfPF(IntEnum): # V 7.2+ ("depth", int), # V 7.3+ - ("padding2", int), - ("padding2_", int), ("resource_count", int), - ("padding3", int), ], ) RGB_FORMATS = (VtfPF.RGB888,) @@ -146,6 +143,9 @@ class VtfPF(IntEnum): ) SUPPORTED_FORMATS = RGBA_FORMATS + RGB_FORMATS + LA_FORMATS + L_FORMATS +HEADER_V70_STRUCT = '> mip_count + mip_height = height >> mip_count + if mip_width < 1 or mip_height < 1: break mip_count += 1 return mip_count +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)) + + # fmt: on @@ -192,12 +193,16 @@ def _open(self): self.fp.seek(4) version = struct.unpack("<2I", self.fp.read(8)) if version <= (7, 2): - header = VTFHeader(*struct.unpack(" (7, 2): + size = struct.calcsize(HEADER_V73_STRUCT) + 12 + header = header._replace(header_size=size + (16 - size % 16)) + fp.write(struct.pack(HEADER_V73_STRUCT, *header)) + else: + raise VTFException(f'Unsupported version {version}') + + if version > (7, 2): + fp.write(b'\x01\x00\x00\x00') + fp.write(struct.pack('> mip_id) + mip_height = max(4, height >> mip_id) + mip = im.resize((mip_width, mip_height)) + ImageFile._save(mip, fp, [("bcn", (0, 0) + mip.size, (fp.tell()), (1, 'DXT1'))], mip_width * mip_height // 2) + ImageFile._save(im, fp, [("bcn", (0, 0) + im.size, (fp.tell()), (1, 'DXT1'))], im.width * im.height // 2) def _accept(prefix): diff --git a/src/encode.c b/src/encode.c index 2f222701e3f..58b4149893e 100644 --- a/src/encode.c +++ b/src/encode.c @@ -27,6 +27,7 @@ #include "libImaging/Imaging.h" #include "libImaging/Gif.h" +#include "libImaging/Bcn.h" #ifdef HAVE_UNISTD_H #include /* write */ @@ -389,16 +390,15 @@ PyImaging_BcnEncoderNew(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) { return NULL; } - printf("Mode: %s, n: %i, pixel_format: %s", mode, n, pixel_format); - encoder = PyImaging_EncoderNew(0); + encoder = PyImaging_EncoderNew(sizeof(char *)); if (encoder == NULL) { return NULL; } encoder->encode = ImagingBcnEncode; - encoder->state.ystep = 4; - encoder->cleanup = NULL; + encoder->state.state = n; + ((BCNSTATE *)encoder->state.context)->pixel_format = pixel_format; return (PyObject *)encoder; } diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 401552b13dd..4d814479938 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -16,8 +16,6 @@ #include "Bcn.h" #include "math.h" -#define PACK_SHORT_565(r, g, b) \ - ((((r) << 8) & 0xF800) | (((g) << 3) & 0x7E0) | ((b) >> 3)) #define BIT_MASK(bit_count) ((1 << (bit_count)) - 1) #define SET_BITS(target, bit_offset, bit_count, value) \ target |= (((value)&BIT_MASK(bit_count)) << (bit_offset)) @@ -28,40 +26,44 @@ B = TMP; \ } while (0) +typedef union { + struct { + UINT16 r : 5; + UINT16 g : 6; + UINT16 b : 5; + } color; + UINT16 value; +} rgb565; + +static UINT16 +pack_565(UINT8 r, UINT8 g, UINT8 b) { + rgb565 color; + color.color.r = r / (255 / 31); + color.color.g = g / (255 / 63); + color.color.b = b / (255 / 31); + return color.value; +} + static UINT16 rgb565_diff(UINT16 a, UINT16 b) { - UINT8 r0, g0, b0, r1, g1, b1; - r0 = a & 31; - a >>= 5; - g0 = a & 63; - a >>= 6; - b0 = a & 31; - r1 = b & 31; - b >>= 5; - g1 = a & 63; - b >>= 6; - b1 = b & 31; - - return abs(r0 - r1) + abs(g0 - g1) + abs(b0 - b1); + rgb565 c0; + c0.value = a; + rgb565 c1; + c1.value = b; + return ((UINT16)abs(c0.color.r - c1.color.r)) + abs(c0.color.g - c1.color.g) + + abs(c0.color.b - c1.color.b); } static inline UINT16 rgb565_lerp(UINT16 a, UINT16 b, UINT8 a_fac, UINT8 b_fac) { - UINT8 r0, g0, b0, r1, g1, b1; - r0 = a & 31; - a >>= 5; - g0 = a & 63; - a >>= 6; - b0 = a & 31; - r1 = b & 31; - b >>= 5; - g1 = b & 63; - b >>= 6; - b1 = b & 31; - return PACK_SHORT_565( - (r0 * a_fac + r1 * b_fac) / (a_fac + b_fac), - (g0 * a_fac + g1 * b_fac) / (a_fac + b_fac), - (b0 * a_fac + b1 * b_fac) / (a_fac + b_fac)); + rgb565 c0; + c0.value = a; + rgb565 c1; + c1.value = b; + return pack_565( + (c0.color.r * a_fac + c1.color.r * b_fac) / (a_fac + b_fac), + (c0.color.g * a_fac + c1.color.g * b_fac) / (a_fac + b_fac), + (c0.color.b * a_fac + c1.color.b * b_fac) / (a_fac + b_fac)); } typedef struct { @@ -89,24 +91,8 @@ pick_2_major_colors( UINT16 color_count, UINT16 *color0, UINT16 *color1) { - Color colors[16] = { - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - }; + Color colors[16]; + memset(colors, 0, sizeof(colors)); for (int i = 0; i < color_count; ++i) { colors[i].value = unique_colors[i]; colors[i].frequency = color_freq[i]; @@ -121,8 +107,8 @@ pick_2_major_colors( } static UINT8 -get_closest_color_index(const UINT16 colors[4], UINT16 color) { - UINT16 color_error = 65535; +get_closest_color_index(const UINT16 *colors, UINT16 color) { + UINT16 color_error = 0xFFF8; UINT16 lowest_id = 0; for (int color_id = 0; color_id < 4; color_id++) { @@ -139,26 +125,44 @@ get_closest_color_index(const UINT16 colors[4], UINT16 color) { } int -ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { - UINT8 *output = buf; - BOOL has_alpha = 1; +encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + bc1_color *blocks = (bc1_color *)buf; + UINT8 no_alpha = 0; if (strchr(im->mode, 'A') == NULL) - has_alpha = 0; - UINT32 row_block_count = im->xsize / 4; + no_alpha = 1; + UINT32 block_count = (im->xsize * im->ysize) / 16; + if (block_count * sizeof(bc1_color) > bytes) { + state->errcode = IMAGING_CODEC_MEMORY; + return 0; + } - UINT16 unique_count = 0; - UINT16 all_colors[16] = {0}; - UINT16 unique_colors[16] = {0}; - UINT8 color_frequency[16] = {0}; + memset(buf, 0, block_count * sizeof(bc1_color)); + for (int block_index = 0; block_index < block_count; block_index++) { + state->x = (block_index % (im->xsize / 4)); + state->y = (block_index / (im->xsize / 4)); + UINT16 unique_count = 0; + + UINT16 all_colors[16]; + UINT16 unique_colors[16]; + UINT8 color_frequency[16]; + UINT8 opaque[16]; + memset(all_colors, 0, sizeof(all_colors)); + memset(unique_colors, 0, sizeof(unique_colors)); + memset(color_frequency, 0, sizeof(color_frequency)); + memset(opaque, 0, sizeof(opaque)); - for (int block_x = 0; block_x < row_block_count; ++block_x) { for (int by = 0; by < 4; ++by) { - for (int bx = 0; bx < 16; bx += 4) { - UINT8 r = (im->image[state->y + by][bx]); - UINT8 g = (im->image[state->y + by][bx + 1]); - UINT8 b = (im->image[state->y + by][bx] + 2); - UINT16 color = PACK_SHORT_565(r, g, b); + for (int bx = 0; bx < 4; ++bx) { + int x = (state->x * 4) + bx; + int y = (state->y * 4) + by; + UINT8 r = im->image[y][x * im->pixelsize + 2]; + UINT8 g = im->image[y][x * im->pixelsize + 1]; + UINT8 b = im->image[y][x * im->pixelsize + 0]; + UINT8 a = im->image[y][x * im->pixelsize + 3]; + UINT16 color = pack_565(r, g, b); + opaque[bx + by * 4] = a >= 127; all_colors[bx + by * 4] = color; + BOOL new_color = 1; for (UINT16 color_id = 0; color_id < unique_count; color_id++) { if (unique_colors[color_id] == color) { @@ -175,32 +179,45 @@ ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } } - UINT16 color0 = 0, color1 = 0; - pick_2_major_colors( - unique_colors, color_frequency, unique_count, &color0, &color1); - if (color0 < color1) - SWAP(UINT16, color0, color1); + 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); - UINT16 output_colors[4] = {color0, color1, 0, 0}; - if (has_alpha) { - output_colors[2] = rgb565_lerp(color0, color1, 1, 1); - output_colors[3] = 0; + 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 { - output_colors[2] = rgb565_lerp(color0, color1, 2, 1); - output_colors[3] = rgb565_lerp(color0, color1, 1, 2); + palette[2] = rgb565_lerp(c0, c1, 1, 1); + palette[3] = 0; } - bc1_color *block = &((bc1_color *)output)[block_x]; + bc1_color *block = &blocks[block_index]; - block->c0 = color0; - block->c1 = color1; + block->c0 = c0; + block->c1 = c1; for (UINT32 color_id = 0; color_id < 16; ++color_id) { - UINT8 bc_color_id = - get_closest_color_index(output_colors, all_colors[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 + bc_color_id = 3; SET_BITS(block->lut, color_id * 2, 2, bc_color_id); } - - output += sizeof(bc1_color); } + state->errcode = IMAGING_CODEC_END; + return block_count * sizeof(bc1_color); +} - return output - buf; +int +ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + switch (state->state) { + case 1: { + return encode_bc1(im, state, buf, bytes); + } + default: { + state->errcode = IMAGING_CODEC_CONFIG; + return 0; + } + } } From 8e11a6144a11fc8b0a2c732fda92ef367eadb734 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 00:14:46 +0000 Subject: [PATCH 08/33] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/VtfImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 179c0a85fa3..e140b0b586a 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -13,7 +13,7 @@ import struct from enum import IntEnum, IntFlag from io import BytesIO -from math import log, ceil +from math import ceil, log from typing import NamedTuple from . import Image, ImageFile From 1c3a1df63221d9624b47db5e5576f004b5a81dde Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Fri, 12 Aug 2022 03:16:58 +0300 Subject: [PATCH 09/33] Add back accidentally removed static from decode_565 --- src/libImaging/Bcn.h | 2 -- src/libImaging/BcnDecode.c | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libImaging/Bcn.h b/src/libImaging/Bcn.h index 743c5d12123..f142df9f321 100644 --- a/src/libImaging/Bcn.h +++ b/src/libImaging/Bcn.h @@ -28,5 +28,3 @@ typedef struct { INT8 a0, a1; UINT8 lut[6]; } bc5s_alpha; - -rgba decode_565(UINT16 x); \ No newline at end of file diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 9163d83355f..69c3178152d 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -28,7 +28,7 @@ bc1_color_load(bc1_color *dst, const UINT8 *src) { dst->lut = LOAD32(src + 4); } -rgba +static rgba decode_565(UINT16 x) { rgba c; int r, g, b; From 1ce95d1b9f4a0f2feaa8ab05ffc29f28d430f814 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Fri, 12 Aug 2022 14:29:30 +0300 Subject: [PATCH 10/33] Fix variable type --- src/libImaging/BcnEncode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 4d814479938..c001973d61e 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -163,7 +163,7 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { opaque[bx + by * 4] = a >= 127; all_colors[bx + by * 4] = color; - BOOL new_color = 1; + int new_color = 1; for (UINT16 color_id = 0; color_id < unique_count; color_id++) { if (unique_colors[color_id] == color) { color_frequency[color_id]++; From affaa62ec4a16e74fb4c80202dcf675d0cc6aa74 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Fri, 12 Aug 2022 14:56:09 +0300 Subject: [PATCH 11/33] Made loops compatible with old C standart --- src/libImaging/BcnEncode.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index c001973d61e..4f11597bdc5 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -72,12 +72,12 @@ typedef struct { } Color; static void -selection_sort(Color arr[], uint32_t n) { - uint32_t min_idx; +selection_sort(Color arr[], UINT32 n) { + UINT32 min_idx, i, j; - for (uint32_t i = 0; i < n - 1; i++) { + for (i = 0; i < n - 1; i++) { min_idx = i; - for (uint32_t j = i + 1; j < n; j++) + for (j = i + 1; j < n; j++) if (arr[j].frequency < arr[min_idx].frequency) min_idx = j; SWAP(Color, arr[min_idx], arr[i]); @@ -91,9 +91,10 @@ pick_2_major_colors( UINT16 color_count, UINT16 *color0, UINT16 *color1) { + UINT32 i; Color colors[16]; memset(colors, 0, sizeof(colors)); - for (int i = 0; i < color_count; ++i) { + for (i = 0; i < color_count; ++i) { colors[i].value = unique_colors[i]; colors[i].frequency = color_freq[i]; } @@ -110,8 +111,9 @@ static UINT8 get_closest_color_index(const UINT16 *colors, UINT16 color) { UINT16 color_error = 0xFFF8; UINT16 lowest_id = 0; + UINT32 color_id; - for (int color_id = 0; color_id < 4; color_id++) { + for (color_id = 0; color_id < 4; color_id++) { UINT8 error = rgb565_diff(colors[color_id], color); if (error == 0) { return color_id; @@ -128,6 +130,7 @@ int encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { bc1_color *blocks = (bc1_color *)buf; UINT8 no_alpha = 0; + INT32 block_index; if (strchr(im->mode, 'A') == NULL) no_alpha = 1; UINT32 block_count = (im->xsize * im->ysize) / 16; @@ -137,7 +140,7 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } memset(buf, 0, block_count * sizeof(bc1_color)); - for (int block_index = 0; block_index < block_count; block_index++) { + for (block_index = 0; block_index < block_count; block_index++) { state->x = (block_index % (im->xsize / 4)); state->y = (block_index / (im->xsize / 4)); UINT16 unique_count = 0; @@ -150,11 +153,11 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { memset(unique_colors, 0, sizeof(unique_colors)); memset(color_frequency, 0, sizeof(color_frequency)); memset(opaque, 0, sizeof(opaque)); - - for (int by = 0; by < 4; ++by) { - for (int bx = 0; bx < 4; ++bx) { - int x = (state->x * 4) + bx; - int y = (state->y * 4) + by; + UINT32 by, bx, x, y; + for (by = 0; by < 4; ++by) { + for (bx = 0; bx < 4; ++bx) { + x = (state->x * 4) + bx; + y = (state->y * 4) + by; UINT8 r = im->image[y][x * im->pixelsize + 2]; UINT8 g = im->image[y][x * im->pixelsize + 1]; UINT8 b = im->image[y][x * im->pixelsize + 0]; @@ -163,8 +166,9 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { opaque[bx + by * 4] = a >= 127; all_colors[bx + by * 4] = color; - int new_color = 1; - for (UINT16 color_id = 0; color_id < unique_count; color_id++) { + UINT8 new_color = 1; + UINT16 color_id = 1; + for (color_id = 0; color_id < unique_count; color_id++) { if (unique_colors[color_id] == color) { color_frequency[color_id]++; new_color = 0; @@ -196,7 +200,8 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { block->c0 = c0; block->c1 = c1; - for (UINT32 color_id = 0; color_id < 16; ++color_id) { + 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]); From a9364f33e5768b6cac7a81acb43ebe2983b32877 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Fri, 12 Aug 2022 15:11:11 +0300 Subject: [PATCH 12/33] Fix flake errors --- src/PIL/VtfImagePlugin.py | 75 +++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index e140b0b586a..60086c0b571 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -116,9 +116,9 @@ class VtfPF(IntEnum): ("bumpmap_scale", float), ("pixel_format", int), ("mipmap_count", int), - ("lowres_pixel_format", int), - ("lowres_width", int), - ("lowres_height", int), + ("low_pixel_format", int), + ("low_width", int), + ("low_height", int), # V 7.2+ ("depth", int), # V 7.3+ @@ -142,10 +142,15 @@ class VtfPF(IntEnum): VtfPF.UV88, ) +BLOCK_COMPRESSED = ( + VtfPF.DXT1, + VtfPF.DXT1_ONEBITALPHA, + VtfPF.DXT3, + VtfPF.DXT5) SUPPORTED_FORMATS = RGBA_FORMATS + RGB_FORMATS + LA_FORMATS + L_FORMATS -HEADER_V70_STRUCT = '> mip_id, min_res) mip_height = max(header.height >> mip_id, min_res) @@ -285,25 +286,28 @@ def _save(im, fp, filename): thumb = thumb.resize((closest_power(thumb.width), closest_power(thumb.height))) ImageFile._save(thumb, thumb_buffer, [("bcn", (0, 0) + thumb.size, 0, (1, 'DXT1'))]) - header = VTFHeader(0, width, height, flags, 1, 0, 1.0, 1.0, 1.0, 1.0, pixel_format, mipmap_count, - VtfPF.DXT1, thumb.width, thumb.height, 1, 2) + header = VTFHeader(0, width, height, flags, + 1, 0, 1.0, 1.0, 1.0, + 1.0, pixel_format, mipmap_count, VtfPF.DXT1, + thumb.width, thumb.height, + 1, 2) fp.write( b"VTF\x00" + struct.pack('<2I', *version) ) if version < (7, 2): - size = struct.calcsize(HEADER_V70_STRUCT) + 12 + size = struct.calcsize(HEADER_V70) + 12 header = header._replace(header_size=size + (16 - size % 16)) - fp.write(struct.pack(HEADER_V70_STRUCT, *header[:15])) + fp.write(struct.pack(HEADER_V70, *header[:15])) elif version == (7, 2): - size = struct.calcsize(HEADER_V72_STRUCT) + 12 + size = struct.calcsize(HEADER_V72) + 12 header = header._replace(header_size=size + (16 - size % 16)) - fp.write(struct.pack(HEADER_V72_STRUCT, *header[:16])) + fp.write(struct.pack(HEADER_V72, *header[:16])) elif version > (7, 2): - size = struct.calcsize(HEADER_V73_STRUCT) + 12 + size = struct.calcsize(HEADER_V73) + 12 header = header._replace(header_size=size + (16 - size % 16)) - fp.write(struct.pack(HEADER_V73_STRUCT, *header)) + fp.write(struct.pack(HEADER_V73, *header)) else: raise VTFException(f'Unsupported version {version}') @@ -314,16 +318,19 @@ def _save(im, fp, filename): fp.write(struct.pack('> mip_id) mip_height = max(4, height >> mip_id) mip = im.resize((mip_width, mip_height)) - ImageFile._save(mip, fp, [("bcn", (0, 0) + mip.size, (fp.tell()), (1, 'DXT1'))], mip_width * mip_height // 2) - ImageFile._save(im, fp, [("bcn", (0, 0) + im.size, (fp.tell()), (1, 'DXT1'))], im.width * im.height // 2) + buffer_size = mip_width * mip_height // 2 + extents = (0, 0) + mip.size + ImageFile._save(mip, fp, + [("bcn", extents, fp.tell(), (1, 'DXT1'))], buffer_size) + buffer_size = im.width * im.height // 2 + ImageFile._save(im, fp, + [("bcn", (0, 0) + im.size, fp.tell(), (1, 'DXT1'))], buffer_size) def _accept(prefix): From 2cc5893d87001a8ba7cc48e363a21cf251b6e3f4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 12 Aug 2022 22:47:36 +1000 Subject: [PATCH 13/33] Restored black formatting --- src/PIL/VtfImagePlugin.py | 112 ++++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 46 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 60086c0b571..d6e9aae970b 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -142,22 +142,24 @@ class VtfPF(IntEnum): VtfPF.UV88, ) -BLOCK_COMPRESSED = ( - VtfPF.DXT1, - VtfPF.DXT1_ONEBITALPHA, - VtfPF.DXT3, - VtfPF.DXT5) +BLOCK_COMPRESSED = (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5) SUPPORTED_FORMATS = RGBA_FORMATS + RGB_FORMATS + LA_FORMATS + L_FORMATS -HEADER_V70 = '> mip_id, min_res) mip_height = max(header.height >> mip_id, min_res) @@ -256,7 +264,6 @@ def _open(self): else: raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") self.tile = [tile] - # fmt: on def _save(im, fp, filename): @@ -264,14 +271,14 @@ def _save(im, fp, filename): if im.mode not in ("RGB", "RGBA"): raise OSError(f"cannot write mode {im.mode} as VTF") arguments = im.encoderinfo - pixel_format = VtfPF(arguments.get('pixel_format', VtfPF.RGBA8888)) - version = arguments.get('version', (7, 4)) + pixel_format = VtfPF(arguments.get("pixel_format", VtfPF.RGBA8888)) + version = arguments.get("version", (7, 4)) flags = CompiledVtfFlags(0) - if 'A' in im.mode: + if "A" in im.mode: if pixel_format == VtfPF.DXT1_ONEBITALPHA: flags |= CompiledVtfFlags.ONEBITALPHA elif pixel_format == VtfPF.DXT1: - im = im.convert('RGB') + im = im.convert("RGB") else: flags |= CompiledVtfFlags.EIGHTBITALPHA @@ -281,21 +288,32 @@ def _save(im, fp, filename): mipmap_count = _get_mipmap_count(width, height) thumb_buffer = BytesIO() - thumb = im.convert('RGB') + thumb = im.convert("RGB") thumb.thumbnail(((min(16, width)), (min(16, height)))) thumb = thumb.resize((closest_power(thumb.width), closest_power(thumb.height))) - ImageFile._save(thumb, thumb_buffer, [("bcn", (0, 0) + thumb.size, 0, (1, 'DXT1'))]) - - header = VTFHeader(0, width, height, flags, - 1, 0, 1.0, 1.0, 1.0, - 1.0, pixel_format, mipmap_count, VtfPF.DXT1, - thumb.width, thumb.height, - 1, 2) - - fp.write( - b"VTF\x00" - + struct.pack('<2I', *version) + ImageFile._save(thumb, thumb_buffer, [("bcn", (0, 0) + thumb.size, 0, (1, "DXT1"))]) + + header = VTFHeader( + 0, + width, + height, + flags, + 1, + 0, + 1.0, + 1.0, + 1.0, + 1.0, + pixel_format, + mipmap_count, + VtfPF.DXT1, + thumb.width, + thumb.height, + 1, + 2, ) + + fp.write(b"VTF\x00" + struct.pack("<2I", *version)) if version < (7, 2): size = struct.calcsize(HEADER_V70) + 12 header = header._replace(header_size=size + (16 - size % 16)) @@ -309,15 +327,15 @@ def _save(im, fp, filename): header = header._replace(header_size=size + (16 - size % 16)) fp.write(struct.pack(HEADER_V73, *header)) else: - raise VTFException(f'Unsupported version {version}') + raise VTFException(f"Unsupported version {version}") if version > (7, 2): - fp.write(b'\x01\x00\x00\x00') - fp.write(struct.pack(' Date: Fri, 12 Aug 2022 23:19:38 +1000 Subject: [PATCH 14/33] Minor changes --- src/PIL/VtfImagePlugin.py | 14 +++++++------- src/encode.c | 2 +- src/libImaging/BcnEncode.c | 20 +++++++++++++------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index d6e9aae970b..f6f9ca46367 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -163,9 +163,9 @@ def _get_texture_size(pixel_format: VtfPF, width, height): return width * height elif pixel_format in LA_FORMATS: return width * height * 2 - elif pixel_format in (VtfPF.RGB888,): + elif pixel_format == VtfPF.RGB888: return width * height * 3 - elif pixel_format in (VtfPF.RGBA8888,): + elif pixel_format == VtfPF.RGBA8888: return width * height * 4 raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") @@ -205,7 +205,7 @@ def _open(self): 0, ) self.fp.seek(header.header_size) - elif (7, 2) <= version < (7, 3): + elif version < (7, 3): header = VTFHeader( *struct.unpack(HEADER_V72, self.fp.read(struct.calcsize(HEADER_V72))), 0, @@ -214,7 +214,7 @@ def _open(self): 0, ) self.fp.seek(header.header_size) - elif (7, 3) <= version < (7, 5): + elif version < (7, 5): header = VTFHeader( *struct.unpack(HEADER_V73, self.fp.read(struct.calcsize(HEADER_V73))) ) @@ -270,9 +270,9 @@ def _save(im, fp, filename): im: Image.Image if im.mode not in ("RGB", "RGBA"): raise OSError(f"cannot write mode {im.mode} as VTF") - arguments = im.encoderinfo - pixel_format = VtfPF(arguments.get("pixel_format", VtfPF.RGBA8888)) - version = arguments.get("version", (7, 4)) + encoderinfo = im.encoderinfo + pixel_format = VtfPF(encoderinfo.get("pixel_format", VtfPF.RGBA8888)) + version = encoderinfo.get("version", (7, 4)) flags = CompiledVtfFlags(0) if "A" in im.mode: if pixel_format == VtfPF.DXT1_ONEBITALPHA: diff --git a/src/encode.c b/src/encode.c index 58b4149893e..a2fd1a06674 100644 --- a/src/encode.c +++ b/src/encode.c @@ -376,7 +376,7 @@ get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode) } /* -------------------------------------------------------------------- */ -/* BNC */ +/* BCN */ /* -------------------------------------------------------------------- */ PyObject * diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 4f11597bdc5..c1840ab1b43 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -77,9 +77,11 @@ selection_sort(Color arr[], UINT32 n) { for (i = 0; i < n - 1; i++) { min_idx = i; - for (j = i + 1; j < n; j++) - if (arr[j].frequency < arr[min_idx].frequency) + for (j = i + 1; j < n; j++) { + if (arr[j].frequency < arr[min_idx].frequency) { min_idx = j; + } + } SWAP(Color, arr[min_idx], arr[i]); } } @@ -103,8 +105,9 @@ pick_2_major_colors( if (color_count == 1) { *color1 = colors[color_count - 1].value; - } else + } else { *color1 = colors[color_count - 2].value; + } } static UINT8 @@ -131,8 +134,9 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { bc1_color *blocks = (bc1_color *)buf; UINT8 no_alpha = 0; INT32 block_index; - if (strchr(im->mode, 'A') == NULL) + if (strchr(im->mode, 'A') == NULL) { no_alpha = 1; + } UINT32 block_count = (im->xsize * im->ysize) / 16; if (block_count * sizeof(bc1_color) > bytes) { state->errcode = IMAGING_CODEC_MEMORY; @@ -185,8 +189,9 @@ 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) + if (c0 < c1 && no_alpha) { SWAP(UINT16, c0, c1); + } UINT16 palette[4] = {c0, c1, 0, 0}; if (no_alpha) { @@ -203,10 +208,11 @@ 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) + if (opaque[color_id] || no_alpha) { bc_color_id = get_closest_color_index(palette, all_colors[color_id]); - else + } else { bc_color_id = 3; + } SET_BITS(block->lut, color_id * 2, 2, bc_color_id); } } From c6c0749a162809fca9a96aaa17bebc4b30f9d933 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Fri, 12 Aug 2022 16:57:28 +0300 Subject: [PATCH 15/33] Remove pointer casts as they may lead to UB and refactor code to not use union rgb565 structs --- src/libImaging/Bcn.h | 12 +++++ src/libImaging/BcnEncode.c | 96 +++++++++++++++++--------------------- 2 files changed, 55 insertions(+), 53 deletions(-) diff --git a/src/libImaging/Bcn.h b/src/libImaging/Bcn.h index f142df9f321..3588828e870 100644 --- a/src/libImaging/Bcn.h +++ b/src/libImaging/Bcn.h @@ -28,3 +28,15 @@ typedef struct { INT8 a0, a1; UINT8 lut[6]; } bc5s_alpha; + +#define BIT_MASK(bit_count) ((1 << (bit_count)) - 1) +#define SET_BITS(target, bit_offset, bit_count, value) \ + target |= (((value)&BIT_MASK(bit_count)) << (bit_offset)) +#define GET_BITS(source, bit_offset, bit_count) \ + ((source) & (BIT_MASK(bit_count) << (bit_offset))) >> (bit_offset) +#define SWAP(TYPE, A, B) \ + do { \ + TYPE TMP = A; \ + (A) = B; \ + (B) = TMP; \ + } while (0) diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index c1840ab1b43..3b17fd24be5 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -16,54 +16,44 @@ #include "Bcn.h" #include "math.h" -#define BIT_MASK(bit_count) ((1 << (bit_count)) - 1) -#define SET_BITS(target, bit_offset, bit_count, value) \ - target |= (((value)&BIT_MASK(bit_count)) << (bit_offset)) -#define SWAP(TYPE, A, B) \ - do { \ - TYPE TMP = A; \ - A = B; \ - B = TMP; \ - } while (0) - -typedef union { - struct { - UINT16 r : 5; - UINT16 g : 6; - UINT16 b : 5; - } color; - UINT16 value; -} rgb565; - -static UINT16 -pack_565(UINT8 r, UINT8 g, UINT8 b) { - rgb565 color; - color.color.r = r / (255 / 31); - color.color.g = g / (255 / 63); - color.color.b = b / (255 / 31); - return color.value; -} +#define PACK_SHORT_565(r, g, b) \ + ((((b) << 8) & 0xF800) | (((g) << 3) & 0x7E0) | ((r) >> 3)) + +#define UNPACK_SHORT_565(source, r, g, b) \ + (r) = GET_BITS((source), 0, 5); \ + (g) = GET_BITS((source), 5, 6); \ + (b) = GET_BITS((source), 11, 5); + +#define WRITE_SHORT(buf, value) \ + *(buf++) = value & 0xFF; \ + *(buf++) = value >> 8; -static UINT16 -rgb565_diff(UINT16 a, UINT16 b) { - rgb565 c0; - c0.value = a; - rgb565 c1; - c1.value = b; - return ((UINT16)abs(c0.color.r - c1.color.r)) + abs(c0.color.g - c1.color.g) + - abs(c0.color.b - c1.color.b); +#define WRITE_INT(buf, value) \ + WRITE_SHORT(buf, value & 0xFFFF) \ + WRITE_SHORT(buf, value >> 16) + +#define WRITE_BC1_BLOCK(buf, block) \ + WRITE_SHORT(buf, block.c0) \ + WRITE_SHORT(buf, block.c1) \ + WRITE_INT(buf, block.lut) + +static inline UINT16 +rgb565_diff(UINT16 c0, UINT16 c1) { + UINT8 r0, g0, b0, r1, g1, b1; + UNPACK_SHORT_565(c0, r0, g0, b0) + UNPACK_SHORT_565(c1, r1, g1, b1) + return ((UINT16)abs(r0 - r1)) + abs(g0 - g1) + abs(b0 - b1); } static inline UINT16 -rgb565_lerp(UINT16 a, UINT16 b, UINT8 a_fac, UINT8 b_fac) { - rgb565 c0; - c0.value = a; - rgb565 c1; - c1.value = b; - return pack_565( - (c0.color.r * a_fac + c1.color.r * b_fac) / (a_fac + b_fac), - (c0.color.g * a_fac + c1.color.g * b_fac) / (a_fac + b_fac), - (c0.color.b * a_fac + c1.color.b * b_fac) / (a_fac + b_fac)); +rgb565_lerp(UINT16 c0, UINT16 c1, UINT8 a_fac, UINT8 b_fac) { + UINT8 r0, g0, b0, r1, g1, b1; + UNPACK_SHORT_565(c0, r0, g0, b0) + UNPACK_SHORT_565(c1, r1, g1, b1) + return PACK_SHORT_565( + (r0 * a_fac + r1 * b_fac) / (a_fac + b_fac), + (g0 * a_fac + g1 * b_fac) / (a_fac + b_fac), + (b0 * a_fac + b1 * b_fac) / (a_fac + b_fac)); } typedef struct { @@ -131,13 +121,13 @@ get_closest_color_index(const UINT16 *colors, UINT16 color) { int encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { - bc1_color *blocks = (bc1_color *)buf; + UINT8* dst = buf; UINT8 no_alpha = 0; INT32 block_index; if (strchr(im->mode, 'A') == NULL) { no_alpha = 1; } - UINT32 block_count = (im->xsize * im->ysize) / 16; + INT32 block_count = (im->xsize * im->ysize) / 16; if (block_count * sizeof(bc1_color) > bytes) { state->errcode = IMAGING_CODEC_MEMORY; return 0; @@ -166,7 +156,7 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { UINT8 g = im->image[y][x * im->pixelsize + 1]; UINT8 b = im->image[y][x * im->pixelsize + 0]; UINT8 a = im->image[y][x * im->pixelsize + 3]; - UINT16 color = pack_565(r, g, b); + UINT16 color = PACK_SHORT_565(r, g, b); opaque[bx + by * 4] = a >= 127; all_colors[bx + by * 4] = color; @@ -201,10 +191,9 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { palette[2] = rgb565_lerp(c0, c1, 1, 1); palette[3] = 0; } - bc1_color *block = &blocks[block_index]; - - block->c0 = c0; - block->c1 = c1; + bc1_color block = {0}; + block.c0 = c0; + block.c1 = c1; UINT32 color_id; for (color_id = 0; color_id < 16; ++color_id) { UINT8 bc_color_id; @@ -213,11 +202,12 @@ encode_bc1(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } else { bc_color_id = 3; } - SET_BITS(block->lut, color_id * 2, 2, bc_color_id); + SET_BITS(block.lut, color_id * 2, 2, bc_color_id); } + WRITE_BC1_BLOCK(dst, block) } state->errcode = IMAGING_CODEC_END; - return block_count * sizeof(bc1_color); + return dst - buf; } int From 7285b74a619101cd66fd8bc95bbffa74e0d04ad4 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sat, 13 Aug 2022 22:37:42 +0300 Subject: [PATCH 16/33] Add support for other common pixel formats --- src/PIL/VtfImagePlugin.py | 106 ++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 27 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index f6f9ca46367..58019bea1e2 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -12,7 +12,7 @@ import struct from enum import IntEnum, IntFlag -from io import BytesIO +from io import BytesIO, BufferedIOBase from math import ceil, log from typing import NamedTuple @@ -125,7 +125,7 @@ class VtfPF(IntEnum): ("resource_count", int), ], ) -RGB_FORMATS = (VtfPF.RGB888,) +RGB_FORMATS = (VtfPF.RGB888, VtfPF.BGR888, VtfPF.UV88,) RGBA_FORMATS = ( VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, @@ -139,7 +139,6 @@ class VtfPF(IntEnum): ) LA_FORMATS = ( VtfPF.IA88, - VtfPF.UV88, ) BLOCK_COMPRESSED = (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5) @@ -153,18 +152,22 @@ 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 + pixel_format + in ( + VtfPF.DXT3, + VtfPF.DXT5, + ) + + L_FORMATS ): return width * height + elif pixel_format == VtfPF.UV88: + 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: + return width * height * 3 elif pixel_format == VtfPF.RGBA8888: return width * height * 4 raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") @@ -181,9 +184,47 @@ def _get_mipmap_count(width: int, height: int): return mip_count -def closest_power(x): +def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF): + extents = (0, 0) + im.size + if pixel_format == VtfPF.DXT1: + encoder = 'bcn' + encoder_args = (1, "DXT1") + elif pixel_format == VtfPF.DXT3: + encoder = 'bcn' + encoder_args = (3, "DXT3") + elif pixel_format == VtfPF.DXT5: + encoder = 'bcn' + encoder_args = (5, "DXT5") + elif pixel_format == VtfPF.RGB888: + encoder = 'raw' + encoder_args = ("RGB", 0, 0) + elif pixel_format == VtfPF.BGR888: + encoder = 'raw' + encoder_args = ("BGR", 0, 0) + elif pixel_format == VtfPF.RGBA8888: + encoder = 'raw' + encoder_args = ("RGBA", 0, 0) + elif pixel_format == VtfPF.I8: + encoder = 'raw' + encoder_args = ("L", 0, 0) + elif pixel_format == VtfPF.IA88: + encoder = 'raw' + encoder_args = ("LA", 0, 0) + elif pixel_format == VtfPF.UV88: + encoder = 'raw' + r, g, *_ = im.split() + im = Image.merge('LA', (r, g)) + encoder_args = ("LA", 0, 0) + else: + raise VTFException(f"Unsupported pixel format: {pixel_format!r}") + + tile = [(encoder, extents, fp.tell(), encoder_args)] + ImageFile._save(im, fp, tile, _get_texture_size(pixel_format, *im.size)) + + +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): @@ -268,12 +309,23 @@ def _open(self): def _save(im, fp, filename): im: Image.Image - if im.mode not in ("RGB", "RGBA"): + if im.mode not in ("RGB", "RGBA", 'L', 'LA'): raise OSError(f"cannot write mode {im.mode} as VTF") encoderinfo = im.encoderinfo pixel_format = VtfPF(encoderinfo.get("pixel_format", VtfPF.RGBA8888)) version = encoderinfo.get("version", (7, 4)) + generate_mips = encoderinfo.get('generate_mips', True) + flags = CompiledVtfFlags(0) + if pixel_format in RGBA_FORMATS: + im = im.convert('RGBA') + if pixel_format in RGB_FORMATS: + im = im.convert('RGB') + if pixel_format in L_FORMATS: + im = im.convert('L') + if pixel_format in LA_FORMATS: + im = im.convert('LA') + if "A" in im.mode: if pixel_format == VtfPF.DXT1_ONEBITALPHA: flags |= CompiledVtfFlags.ONEBITALPHA @@ -282,16 +334,18 @@ def _save(im, fp, filename): else: flags |= CompiledVtfFlags.EIGHTBITALPHA - im = im.resize((closest_power(im.width), closest_power(im.height))) + im = im.resize((_closest_power(im.width), _closest_power(im.height))) width, height = im.size - mipmap_count = _get_mipmap_count(width, height) + mipmap_count = 0 + if generate_mips: + mipmap_count = _get_mipmap_count(width, height) thumb_buffer = BytesIO() thumb = im.convert("RGB") thumb.thumbnail(((min(16, width)), (min(16, height)))) - thumb = thumb.resize((closest_power(thumb.width), closest_power(thumb.height))) - ImageFile._save(thumb, thumb_buffer, [("bcn", (0, 0) + thumb.size, 0, (1, "DXT1"))]) + thumb = thumb.resize((_closest_power(thumb.width), _closest_power(thumb.height))) + _write_image(thumb_buffer, thumb, VtfPF.DXT1) header = VTFHeader( 0, @@ -338,19 +392,17 @@ def _save(im, fp, filename): fp.write(b"\x00" * (16 - fp.tell() % 16)) fp.write(thumb_buffer.getbuffer()) + if pixel_format in BLOCK_COMPRESSED: + min_size = 4 + else: + min_size = 1 + for mip_id in range(mipmap_count - 1, 0, -1): - mip_width = max(4, width >> mip_id) - mip_height = max(4, height >> mip_id) + mip_width = max(min_size, width >> mip_id) + mip_height = max(min_size, height >> mip_id) mip = im.resize((mip_width, mip_height)) - buffer_size = mip_width * mip_height // 2 - extents = (0, 0) + mip.size - ImageFile._save( - mip, fp, [("bcn", extents, fp.tell(), (1, "DXT1"))], buffer_size - ) - buffer_size = im.width * im.height // 2 - ImageFile._save( - im, fp, [("bcn", (0, 0) + im.size, fp.tell(), (1, "DXT1"))], buffer_size - ) + _write_image(fp, mip, pixel_format) + _write_image(fp, im, pixel_format) def _accept(prefix): From afc0e8e1a0791ecc67fb34aa824607a71ea9d4a9 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sat, 13 Aug 2022 23:43:44 +0300 Subject: [PATCH 17/33] Fix mipmap calculation --- src/PIL/VtfImagePlugin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 58019bea1e2..76d8a002e20 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -178,10 +178,9 @@ def _get_mipmap_count(width: int, height: int): while True: mip_width = width >> mip_count mip_height = height >> mip_count - if mip_width < 1 or mip_height < 1: - break + if mip_width == 0 and mip_height == 0: + return mip_count mip_count += 1 - return mip_count def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF): From 59a43752d146853c7fb2f24490e6a8e4663ca0f4 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sat, 13 Aug 2022 23:44:28 +0300 Subject: [PATCH 18/33] Refactor vtf saving --- src/PIL/VtfImagePlugin.py | 42 +++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 76d8a002e20..580c315c6ff 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -188,27 +188,44 @@ 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') + 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,)) elif pixel_format == VtfPF.I8: encoder = 'raw' encoder_args = ("L", 0, 0) + im = im.convert('L') elif pixel_format == VtfPF.IA88: encoder = 'raw' encoder_args = ("LA", 0, 0) + im = im.convert('LA') elif pixel_format == VtfPF.UV88: encoder = 'raw' r, g, *_ = im.split() @@ -316,22 +333,17 @@ def _save(im, fp, filename): generate_mips = encoderinfo.get('generate_mips', True) flags = CompiledVtfFlags(0) - if pixel_format in RGBA_FORMATS: - im = im.convert('RGBA') - if pixel_format in RGB_FORMATS: - im = im.convert('RGB') - if pixel_format in L_FORMATS: - im = im.convert('L') - if pixel_format in LA_FORMATS: - im = im.convert('LA') - if "A" in im.mode: - if pixel_format == VtfPF.DXT1_ONEBITALPHA: - flags |= CompiledVtfFlags.ONEBITALPHA - elif pixel_format == VtfPF.DXT1: - im = im.convert("RGB") - else: - flags |= CompiledVtfFlags.EIGHTBITALPHA + if pixel_format == VtfPF.DXT1_ONEBITALPHA: + flags |= CompiledVtfFlags.ONEBITALPHA + elif pixel_format == VtfPF.A8: + 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') im = im.resize((_closest_power(im.width), _closest_power(im.height))) width, height = im.size From b7a589475daabe7143c110ed31d1e67b3b9f6da7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 13 Aug 2022 20:45:18 +0000 Subject: [PATCH 19/33] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/VtfImagePlugin.py | 76 ++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 580c315c6ff..faba60e5724 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -12,7 +12,7 @@ import struct from enum import IntEnum, IntFlag -from io import BytesIO, BufferedIOBase +from io import BufferedIOBase, BytesIO from math import ceil, log from typing import NamedTuple @@ -125,7 +125,11 @@ class VtfPF(IntEnum): ("resource_count", int), ], ) -RGB_FORMATS = (VtfPF.RGB888, VtfPF.BGR888, VtfPF.UV88,) +RGB_FORMATS = ( + VtfPF.RGB888, + VtfPF.BGR888, + VtfPF.UV88, +) RGBA_FORMATS = ( VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, @@ -137,9 +141,7 @@ class VtfPF(IntEnum): VtfPF.A8, VtfPF.I8, ) -LA_FORMATS = ( - VtfPF.IA88, -) +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 @@ -152,12 +154,12 @@ 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 + pixel_format + in ( + VtfPF.DXT3, + VtfPF.DXT5, + ) + + L_FORMATS ): return width * height elif pixel_format == VtfPF.UV88: @@ -186,50 +188,50 @@ def _get_mipmap_count(width: int, height: int): def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF): extents = (0, 0) + im.size if pixel_format == VtfPF.DXT1: - encoder = 'bcn' + encoder = "bcn" encoder_args = (1, "DXT1") - im = im.convert('RGB') + im = im.convert("RGB") elif pixel_format == VtfPF.DXT1_ONEBITALPHA: - encoder = 'bcn' + encoder = "bcn" encoder_args = (1, "DXT1A") - im = im.convert('RGBA') + im = im.convert("RGBA") elif pixel_format == VtfPF.DXT3: - encoder = 'bcn' + encoder = "bcn" encoder_args = (3, "DXT3") - im = im.convert('RGBA') + im = im.convert("RGBA") elif pixel_format == VtfPF.DXT5: - encoder = 'bcn' + encoder = "bcn" encoder_args = (5, "DXT5") - im = im.convert('RGBA') + im = im.convert("RGBA") elif pixel_format == VtfPF.RGB888: - encoder = 'raw' + encoder = "raw" encoder_args = ("RGB", 0, 0) - im = im.convert('RGB') + im = im.convert("RGB") elif pixel_format == VtfPF.BGR888: - encoder = 'raw' + encoder = "raw" encoder_args = ("BGR", 0, 0) - im = im.convert('RGB') + im = im.convert("RGB") elif pixel_format == VtfPF.RGBA8888: - encoder = 'raw' + encoder = "raw" encoder_args = ("RGBA", 0, 0) - im = im.convert('RGBA') + im = im.convert("RGBA") elif pixel_format == VtfPF.A8: - encoder = 'raw' + encoder = "raw" encoder_args = ("L", 0, 0) *_, a = im.split() - im = Image.merge('L', (a,)) + im = Image.merge("L", (a,)) elif pixel_format == VtfPF.I8: - encoder = 'raw' + encoder = "raw" encoder_args = ("L", 0, 0) - im = im.convert('L') + im = im.convert("L") elif pixel_format == VtfPF.IA88: - encoder = 'raw' + encoder = "raw" encoder_args = ("LA", 0, 0) - im = im.convert('LA') + im = im.convert("LA") elif pixel_format == VtfPF.UV88: - encoder = 'raw' + encoder = "raw" r, g, *_ = im.split() - im = Image.merge('LA', (r, g)) + im = Image.merge("LA", (r, g)) encoder_args = ("LA", 0, 0) else: raise VTFException(f"Unsupported pixel format: {pixel_format!r}") @@ -240,7 +242,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): @@ -325,12 +327,12 @@ def _open(self): def _save(im, fp, filename): im: Image.Image - if im.mode not in ("RGB", "RGBA", 'L', 'LA'): + if im.mode not in ("RGB", "RGBA", "L", "LA"): raise OSError(f"cannot write mode {im.mode} as VTF") encoderinfo = im.encoderinfo pixel_format = VtfPF(encoderinfo.get("pixel_format", VtfPF.RGBA8888)) version = encoderinfo.get("version", (7, 4)) - generate_mips = encoderinfo.get('generate_mips', True) + generate_mips = encoderinfo.get("generate_mips", True) flags = CompiledVtfFlags(0) @@ -343,7 +345,7 @@ def _save(im, fp, filename): elif pixel_format in RGB_FORMATS + L_FORMATS: pass else: - raise VTFException('Unhandled case') + raise VTFException("Unhandled case") im = im.resize((_closest_power(im.width), _closest_power(im.height))) width, height = im.size From ed70519396faf620e6ce29da720b9e4ad0e8f5a4 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sat, 13 Aug 2022 23:46:02 +0300 Subject: [PATCH 20/33] Add better check for DXT1 with alpha --- src/libImaging/BcnEncode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 3b17fd24be5..ca35ab0af08 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -121,10 +121,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 *dst = buf; UINT8 no_alpha = 0; INT32 block_index; - if (strchr(im->mode, 'A') == NULL) { + if (strcmp(((BCNSTATE *)state->context)->pixel_format, "DXT1A") != 0) { no_alpha = 1; } INT32 block_count = (im->xsize * im->ysize) / 16; From 3e7843745b83dd4757fa526cec4f3316436df0e5 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 14 Aug 2022 01:35:00 +0300 Subject: [PATCH 21/33] Add support for few more formats --- src/PIL/VtfImagePlugin.py | 30 ++++++++++++++++++++---------- src/encode.c | 1 - 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index faba60e5724..5f39067e72e 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -125,13 +125,11 @@ class VtfPF(IntEnum): ("resource_count", int), ], ) -RGB_FORMATS = ( - VtfPF.RGB888, - VtfPF.BGR888, - VtfPF.UV88, -) +RGB_FORMATS = (VtfPF.DXT1, + VtfPF.RGB888, + VtfPF.BGR888, + VtfPF.UV88,) RGBA_FORMATS = ( - VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5, @@ -230,8 +228,13 @@ def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF): im = im.convert("LA") elif pixel_format == VtfPF.UV88: encoder = "raw" - r, g, *_ = im.split() - im = Image.merge("LA", (r, g)) + if im.mode == "RGB" or im.mode == "RGBA": + r, g, *_ = im.split() + im = Image.merge('LA', (r, g)) + elif im.mode == "LA": + pass + else: + raise VTFException(f'Cannot encode {im.mode} as {pixel_format}') encoder_args = ("LA", 0, 0) else: raise VTFException(f"Unsupported pixel format: {pixel_format!r}") @@ -283,8 +286,9 @@ def _open(self): # flags = CompiledVtfFlags(header.flags) pixel_format = VtfPF(header.pixel_format) low_format = VtfPF(header.low_pixel_format) - - if pixel_format in RGB_FORMATS: + if pixel_format == VtfPF.DXT1: # Special case for DXT1 + self.mode = "RGBA" + elif pixel_format in RGB_FORMATS: self.mode = "RGB" elif pixel_format in RGBA_FORMATS: self.mode = "RGBA" @@ -316,6 +320,12 @@ def _open(self): tile = ("raw", (0, 0) + self.size, data_start, ("RGBA", 0, 1)) elif pixel_format in (VtfPF.RGB888,): tile = ("raw", (0, 0) + self.size, data_start, ("RGB", 0, 1)) + elif pixel_format in (VtfPF.BGR888,): + tile = ("raw", (0, 0) + self.size, data_start, ("BGR", 0, 1)) + elif pixel_format in (VtfPF.BGRA8888,): + tile = ("raw", (0, 0) + self.size, data_start, ("BGRA", 0, 1)) + elif pixel_format in (VtfPF.UV88,): + tile = ("raw", (0, 0) + self.size, data_start, ("LA", 0, 1)) elif pixel_format in L_FORMATS: tile = ("raw", (0, 0) + self.size, data_start, ("L", 0, 1)) elif pixel_format in LA_FORMATS: diff --git a/src/encode.c b/src/encode.c index a2fd1a06674..4e2964c25f2 100644 --- a/src/encode.c +++ b/src/encode.c @@ -384,7 +384,6 @@ PyImaging_BcnEncoderNew(PyObject *self, PyObject *args) { ImagingEncoderObject *encoder; char *mode; - char *actual; int n = 0; char *pixel_format = ""; if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) { From f368f30597d1e824dbb930c848c4f83a4aa147c0 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 14 Aug 2022 01:35:26 +0300 Subject: [PATCH 22/33] Add basic read tests for VTF --- Tests/images/vtf_a8.png | Bin 0 -> 3223 bytes Tests/images/vtf_a8.vtf | Bin 0 -> 87605 bytes Tests/images/vtf_bgr888.png | Bin 0 -> 6312 bytes Tests/images/vtf_bgr888.vtf | Bin 0 -> 262367 bytes Tests/images/vtf_dxt1.png | Bin 0 -> 6312 bytes Tests/images/vtf_dxt1.vtf | Bin 0 -> 43928 bytes Tests/images/vtf_dxt1A.png | Bin 0 -> 9739 bytes Tests/images/vtf_dxt1A.vtf | Bin 0 -> 43928 bytes Tests/images/vtf_i8.png | Bin 0 -> 4622 bytes Tests/images/vtf_i8.vtf | Bin 0 -> 87605 bytes Tests/images/vtf_ia88.png | Bin 0 -> 7565 bytes Tests/images/vtf_ia88.vtf | Bin 0 -> 174986 bytes Tests/images/vtf_rgb888.png | Bin 0 -> 6312 bytes Tests/images/vtf_rgb888.vtf | Bin 0 -> 262367 bytes Tests/images/vtf_rgba8888.png | Bin 0 -> 9739 bytes Tests/images/vtf_rgba8888.vtf | Bin 0 -> 349748 bytes Tests/test_file_vtf.py | 93 ++++++++++++++++++++++++++++++++++ 17 files changed, 93 insertions(+) create mode 100644 Tests/images/vtf_a8.png create mode 100644 Tests/images/vtf_a8.vtf create mode 100644 Tests/images/vtf_bgr888.png create mode 100644 Tests/images/vtf_bgr888.vtf create mode 100644 Tests/images/vtf_dxt1.png create mode 100644 Tests/images/vtf_dxt1.vtf create mode 100644 Tests/images/vtf_dxt1A.png create mode 100644 Tests/images/vtf_dxt1A.vtf create mode 100644 Tests/images/vtf_i8.png create mode 100644 Tests/images/vtf_i8.vtf create mode 100644 Tests/images/vtf_ia88.png create mode 100644 Tests/images/vtf_ia88.vtf create mode 100644 Tests/images/vtf_rgb888.png create mode 100644 Tests/images/vtf_rgb888.vtf create mode 100644 Tests/images/vtf_rgba8888.png create mode 100644 Tests/images/vtf_rgba8888.vtf create mode 100644 Tests/test_file_vtf.py diff --git a/Tests/images/vtf_a8.png b/Tests/images/vtf_a8.png new file mode 100644 index 0000000000000000000000000000000000000000..2411de7e2230a795d73301b89c2aa8d458b5ab60 GIT binary patch literal 3223 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 uwX8i!4IwA2dT+EB=g^XK8N%G literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_a8.vtf b/Tests/images/vtf_a8.vtf new file mode 100644 index 0000000000000000000000000000000000000000..e2bf3501fe1c9538a25ec75a6ae66ae3d5247da4 GIT binary patch literal 87605 zcmeHQ3%J$Pm0ssw@8zw4qSC^YPEiY}({cDpeT-g}N-MR@h_|&AtESf=TCNa@}+amfPMgQ0Q)zG{ev9)Z;ZL)+p<#t_&>93*(v?m-vL9| zpP@r7u{N`~x|s!Y*uPJ)e{ZmVe4E1(-QC?zx7CW}tg{g#a=D?`0d#hb7-7nFb>+IN z)c~IaIQ7)rF!;ba#)jbmwv~nYVvo-0WEeBv zGmHyfGbJ>(iea$-g$?D)7f<^ou6qt(3eGS3*pg19Y2WCJ-fq9f5qJ=uFuIOqCRMjt zM-+@+c$R11KWLd@yffrdp8qL~o-pC)+}OR^2fTKv=fQ>Td@ux@@t)CWz$8KVqGc9noTQ?A5o1)^4Z?r-bZP< z@`-E|)_g9@y=o8yv;QwIj!T+f~g?gxo_;MK#p}Fk$W-bDMjl!pF?FcpF*o z<6q~X-ywYK1o%mv0_NM5+xWHu;EQ5C^XU;z+o zyb<68?Bh3v!=c+ygDu0jv+Tml0cN6V!#EpYAu3q6YW0geYn>p!U%mP+UXblG4=6oA zM`5LSePOv+{V`?%IY?KfJa1nq;*+6X50x($xYkPEV6Pn%}SzI)bb_V!dTE7tH>ZT=wn-zqG=+S{n@{#!F@If%E1p9N`uRjoOk9iV+- zqS=1%0Q2wA&Hg+1ACvpT{BzB}@MHh&7n<#IkKb+X;AG!J_rp(^?T+f(KGNLx+vwMT z`^{$X&xSG2O7!?`&j<`a$1{ep-nuwB+9V}Dws#IS+kcuxj6Fw~skTRaG-je}%tr8C zv(X|eS>@WAy;MGDHX8JbDQ+J~e`DAlyHl))VGksKWB6*z=3d*{E3M3QJEQg2R(Z%) zTli&mG3Rd#+mL5ebye)kzExVPEg@^{!acmf^%j6rw8yYLcE{}G?cW-1cA!6o?Xmj} z-rOQ~tb-n>z&$m~RM-BPBgBX!+jPgUJ$8@nbJ}koWwzHH!)r`oSnf18jQLiP-WayW zZvPi>`-iQQaI{t&w#Tj*arTb03|Nc%q|)}-y-}=QZnf7L!}i$icgqH0`=NC=WSB-A zw#TlePX41YhV8K{H?>N<$FMziiRK_4712d@&K|o|dtf&p+&63N!ccw@PL{_g0n17`EbWu6hGS?Bx!t{pcNHshG3J?%3U8 zsh#`tVO9sOW7rhV9yO)Rk6g(c%p4kE=!Qwe$}&Z-Eh;J$8>lkpl~U@G+|c zmoaRQ-KDq@PP5Brw$WpFx5XJceuvxO^ad-6&|~-ktN%$P&ELcB39yYE!?UgGtFTHj zeW@it`DgP`v$|uG*aqi*W3`6|aha3eaQbAneM6@CwUYc?Q)2S$$ zclg#Pt!F!IBjSm{IQDiMea9gO#1eyX>}AhLeh2A~B?hC|JDNR%`MmX5j%~z{m-#V@ zZjWQH`7~7NG~@u639GOyLb12qdcgfhNPqM(7{}fT*5vBz)@-nCL`US0ujAOe*z##* zI&whdF&M?(eox!S^fsjb$cSSwj=d)oZ2j+k)G?s#-mu1*x*r^Stw*X%vGX1MQO95u zdk_30H}QVN2BVF^IQD*_MmTpJg&c4*wr}@ zdC0%`tbhn(Fp9l7;c@*d&;W=o2IJWKi4PEd;}6aQvJhPi#_9%8sjhcxv;(jsa1{V0@N+M}%)wzU=6SIdzpGWl`)M_r}@cr_XXZ5l}HsA)1p6$bp!8rEv%MMxpTh>aAsu7{aU>tjEmmRVJUqudZ=(#NCy9CAF zA+^g6xtF!*N6<}9Zs}fvW3L;o9b=FKLX5#U_M*!U+2{pGe|Rw%pIG>^L)O0m-Hq$~ zja(nB1jpW4u3hXs7CGQDS5Y68DE1yeJh04rLo40j$X&d&u|1Bx{TGVUZuPmD=nM!e z2IJU^-0PhTi3i({ntQ90B*KZoIQB{^+zWaeJMeaE9DCi>NwsdVO4~FPdnE-P1!2Ts z9D6;~$<f|x+J&st1 zOZT${-wO#8gK_L#Mk}IfW*`RyiNQGb_9xc?Q`!yVPIM&*0b($Yy(f|Dh-v-rjYbYQ zzB*M-i`UoS*lWK$q+Y@+e<9M}>HEDqIQ9m6v=GmV`;Wmm_D+#zTfDHF^}=w-6NO=&f}$8`^rBd+ox$0Ke6w<<54F=G~Sot zG%ww>tf!}^dUHo>E<;*y#R}G;yF)#%IK4-ZVhgTB3)s3Giuj6xgfV4bOQa72bN^FZc-%mbMRG7n@P$UKmFAoD=xfy@J$ z2Qm+29>_e9c_8yZ=7G!unFlftWFE*oka-~UK<0tW1DOZX>j5_TpI+k|w|qZ;_@Tny z)m-oe&IvQE&XSNKs&cqS#s|0E_9G@y{G7eSPft<(3Kn zwckHaR1AOK#-FC~?PaU^qsF3|Mp!qAf{9;zqTQ?iq}N+N>K6a?`dMpD{OqIejMZoy ze)get`}nsS0b&l&Vzp-cv*Ul#$Fx8DwDH@&e=YX=Z+p7<8T->1E1rF&Q9w6PrV*Q#%~qV;Uyr`&A-zDU z<;!#m?Y}2~I*(Uob>@N01DOXh4`d$5Jdk-H^T0Ut_8V35)Yf*CBB@1Ik`hw(nTOP%O_YRp1u%-;--)ra_dKmur#5x?OSt9el?+lgPo zj35ii!vek6E^5<5=^wv6QNS!@9tcHO5LRZ2A*^`6h)PU_ih(nhUzXdt49{1%AXgvO>ig+Vj97HZ=YU=n`c{$1OJxe68D4m+$9#&axKPzFRlR- z60?l_aG}7-v=j%XxG(#D&3p&4kZLIoOmN@B*{#0E1k@p1q zz!dkj4)M1!Xq0Xt4oq-gd#dLpgjq=bBFOJ-BHMHcxN4azY-5kB6^Mx$iNq4XsLks8 zzPakB3%@7AuH^={%6(vh`<`5N<<|BZWHAxw17QJNcW!L zzWsJ^Shv&=i-{KDzy$Z5&fyy$4is_Mav^>!zNRep1jT*tax6KwhFDA-?TDl|b-##- z=Z5DUNCZ6wXax>TaNqe3e4{vmd!=sQ0vwp)zKWyQWoH3z1+?`;x( z<4g=1rBls;3GO?1w~YS|gjq;FC(EX$gt)Mor2w#Juz=dhm^X=2y6qa^z`!Xs?ICZo zumnn{m;)2sSB+>r!Ym|zDf=oVC0zoJT1I_UR~lIp5EEZb30lcaiu)2P9PpRXu?JGi zfeG$API8o8@GJ(6(`mgAjKB1b@KmQgWG@h08B)rD3GU0MJ!Jg*OQ-zlJW zGAZQ16!%5b9yQ#x#54!8kWw?oTC*f3ZWaL4GQ-fchivxGYI1dn^l{)*>kw44c-lk8 z-*=F@6{L;>Q`}dW_K+PgkHDWg4oq-gaoR)1|1g0cZjogZT|#i*B_2jsMiUO0?9nh` z)dcs2Oq?SS+)8 z_!p=pQ7PfT6!#^jJ!A(^OQKT3feG$QO?$}r$t6*#;K0Sh)aUQzGEkzV@^N(!kO~e= zaNpb;1X@M$5(0lJI55F|>1hwy0h=iNDd4~q_jQ@}kR3oRiAn(nCb+NLw18?v6T?q*KOKEwvAj8rAMLV4r43XOkBSHmp=>6AO^soq(2W#a9{Un57{@j5cuI% zSvFB61oyqrr;+56sL%TBC}wf$85WOe582AE5CfoXi!7U<5`z1BO?$`)2o?qKU0F6c zB^3Adn)Z+pkV~SDm1UDtLLD{FX%88~0^$TnGzTWQ@88JqJWI$WQ3>Y26!-msCt8KT z5ySvUFb5{Mug6{Cb+N8w1=KIm>Q8&f z2+GuwsE?Fo;Uvhm3$)5(U4KWt&_=aNoae$Smq76;vxo8`9KhV~YF!dt)6F zlnR1!%Eqj5X-#n7e%oEzXjENJMZlzSVCq0o_BUFdFb+(fsEy*J+9!+y6Wo`wK7{f_ z-3O+)ZwNT4E)vCoAE2y^uslf|n3}DL5I!V`15?~LO8Af<4oq<0NZ~_*I55F|BgUT? z4oq?1i18t$#2RLsXv2$OJzx{1*<6@AApZjtLToVI2 zPZdqzz8wEwc^VgkKr^^6cfiIN@Oi0d2KVLo7kL>MeSK57FUOw%4&2LaY0AF&_ko+k zeYvxn&VkA0s@%e8_f6-(6!*P7+{iPHa$~bO@C3DCVXsyEPqi9=CUanl`+hmx!*A>z zOgNy)9GK$1_k@h!m`UJ=rgC74`}%#{-`iW%(*@8}4oq?1uXzLVDN#Ix8UW4Yz!dl0 z;1ivO_}3}?P2|A42C9u3S=IpLQ{+*#Drn|?V2b+&f8>dF3i)Ovc#v)sMNP%|a>r@J zF?l))y^9ECa9$MJ=sqho2TZAiu*40=ptbCCn*O6 ze7-uM8BQ~T`@&0(NSLYR6#gk;YSHhD;=Z5K@4}ZP`Hhq&abSx3M)tPeiPQjS3J0dR zFa3UJUq@RZOs)!V3J0dR?@0>F!ztQAtgQxU1_!3N@2YUXs(K;swKahQQ{4A}Z7R#d zDZ8Eu03VHjaC?|cao^8-v}>e#JN16(xH&KbL2=(mAIv<98UQhKV8fue?|@y69AK}u zKcVms-s9CYAI7e~cc@gU^985Xjdl73Ex~I5r^uI$FQuw2~>Jo7C48?teuXW@-d+i^g@WTsU zO+&1wxbHQHx88AUnZGIfI}N-6FOPeq|k>~kT7KSmBrao@ibO^qZ+ytlNLue{oI_jbD$Hsvv?ijOeVqSsOQW8uIQ_l+s~Fwec;K{+4>4oq_27{5|FmBJqb2d21h|D6qu zX?g@?|& zr+df~AmrzcB~vKwJ4cd?vLHW{}a6L!?y=!+~ayh=1M3EVw)=%KHhE+hD#Ac@cVzEf+7lr-in3<(gvEbIbslQNQ zA}ahAOvQ1r%M}%V4Kxg{-}eaAQDr$dpSREv)|&3m_x0tAiUGi2szZZWu8`L;Ns#$} DPGD}f literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_bgr888.png b/Tests/images/vtf_bgr888.png new file mode 100644 index 0000000000000000000000000000000000000000..7e8acdaf8891f56a52c4daba93b982d3a1462288 GIT binary patch literal 6312 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 literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_dxt1.png b/Tests/images/vtf_dxt1.png new file mode 100644 index 0000000000000000000000000000000000000000..7e8acdaf8891f56a52c4daba93b982d3a1462288 GIT binary patch literal 6312 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 O;M4b)*Kd={qV#9xK;-QJ literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_dxt1A.png b/Tests/images/vtf_dxt1A.png new file mode 100644 index 0000000000000000000000000000000000000000..95f6f79411cda24613d1b24536f0b13cda328f3a GIT binary patch literal 9739 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 Tj=ULH*nGG2xIyQ3j{f{V>{|rI literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_i8.png b/Tests/images/vtf_i8.png new file mode 100644 index 0000000000000000000000000000000000000000..790e5e7e8ea0cf8f940011f840e9facbccf16aed GIT binary patch literal 4622 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 literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_i8.vtf b/Tests/images/vtf_i8.vtf new file mode 100644 index 0000000000000000000000000000000000000000..f5e949285f05042d69134843428f4c6711040f95 GIT binary patch literal 87605 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 literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_ia88.png b/Tests/images/vtf_ia88.png new file mode 100644 index 0000000000000000000000000000000000000000..a6e96a5510a99fa7878df8d24a44a4bce51099b1 GIT binary patch literal 7565 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 literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_ia88.vtf b/Tests/images/vtf_ia88.vtf new file mode 100644 index 0000000000000000000000000000000000000000..c691ca1267f6dbea64fa618710a047f746b6c07f GIT binary patch literal 174986 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 literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_rgb888.png b/Tests/images/vtf_rgb888.png new file mode 100644 index 0000000000000000000000000000000000000000..7e8acdaf8891f56a52c4daba93b982d3a1462288 GIT binary patch literal 6312 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

huMUV#nTu|6e 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 literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_rgba8888.png b/Tests/images/vtf_rgba8888.png new file mode 100644 index 0000000000000000000000000000000000000000..95f6f79411cda24613d1b24536f0b13cda328f3a GIT binary patch literal 9739 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%0DBZxQJWc2L5zD=KBc#jmkXoD9S3fyaYbS3mn? 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_vtf.py b/Tests/test_file_vtf.py new file mode 100644 index 00000000000..a0f24f45d97 --- /dev/null +++ b/Tests/test_file_vtf.py @@ -0,0 +1,93 @@ +from typing import Tuple + +import pytest + +from PIL import Image +from PIL.VtfImagePlugin import _closest_power, _get_mipmap_count, _get_texture_size +from PIL.VtfImagePlugin import VtfPF +from .helper import assert_image_similar, assert_image_equal + + +@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): + 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): + 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): + assert _get_texture_size(pixel_format, *size) == expected_size + + +@pytest.mark.parametrize(("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_ia88.png", + "Tests/images/vtf_ia88.vtf", "LA", + 0.0), + # ( + # "Tests/images/vtf_RG.png", + # "Tests/images/vtf_RG.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_rgba8888.png", "Tests/images/vtf_rgba8888.vtf", "RGBA", + 0.1), + ], ) +def test_vtf_loading(etalon_path: str, file_path: str, expected_mode: str, + epsilon: float): + e = Image.open(etalon_path) + f = Image.open(file_path) + assert f.mode == expected_mode + e = e.convert(expected_mode) + if epsilon == 0: + assert_image_equal(e, f) + else: + assert_image_similar(e, f, epsilon) From 53e1831617dfa79138aa243b97781923718627f1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 13 Aug 2022 22:41:15 +0000 Subject: [PATCH 23/33] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_vtf.py | 138 ++++++++++++++++++++------------------ src/PIL/VtfImagePlugin.py | 14 ++-- 2 files changed, 79 insertions(+), 73 deletions(-) diff --git a/Tests/test_file_vtf.py b/Tests/test_file_vtf.py index a0f24f45d97..f857ba2c02f 100644 --- a/Tests/test_file_vtf.py +++ b/Tests/test_file_vtf.py @@ -3,86 +3,90 @@ import pytest from PIL import Image -from PIL.VtfImagePlugin import _closest_power, _get_mipmap_count, _get_texture_size -from PIL.VtfImagePlugin import VtfPF -from .helper import assert_image_similar, assert_image_equal +from PIL.VtfImagePlugin import ( + 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), - ], ) + +@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): 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), - ], ) +@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): 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): +@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 +): assert _get_texture_size(pixel_format, *size) == expected_size -@pytest.mark.parametrize(("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_ia88.png", - "Tests/images/vtf_ia88.vtf", "LA", - 0.0), - # ( - # "Tests/images/vtf_RG.png", - # "Tests/images/vtf_RG.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_rgba8888.png", "Tests/images/vtf_rgba8888.vtf", "RGBA", - 0.1), - ], ) -def test_vtf_loading(etalon_path: str, file_path: str, expected_mode: str, - epsilon: float): +@pytest.mark.parametrize( + ("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_ia88.png", "Tests/images/vtf_ia88.vtf", "LA", 0.0), + # ( + # "Tests/images/vtf_RG.png", + # "Tests/images/vtf_RG.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_rgba8888.png", "Tests/images/vtf_rgba8888.vtf", "RGBA", 0.1), + ], +) +def test_vtf_loading( + etalon_path: str, file_path: str, expected_mode: str, epsilon: float +): e = Image.open(etalon_path) f = Image.open(file_path) assert f.mode == expected_mode diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 5f39067e72e..b6ecd628131 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -125,10 +125,12 @@ class VtfPF(IntEnum): ("resource_count", int), ], ) -RGB_FORMATS = (VtfPF.DXT1, - VtfPF.RGB888, - VtfPF.BGR888, - VtfPF.UV88,) +RGB_FORMATS = ( + VtfPF.DXT1, + VtfPF.RGB888, + VtfPF.BGR888, + VtfPF.UV88, +) RGBA_FORMATS = ( VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, @@ -230,11 +232,11 @@ def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF): encoder = "raw" if im.mode == "RGB" or im.mode == "RGBA": r, g, *_ = im.split() - im = Image.merge('LA', (r, g)) + im = Image.merge("LA", (r, g)) elif im.mode == "LA": pass else: - raise VTFException(f'Cannot encode {im.mode} as {pixel_format}') + raise VTFException(f"Cannot encode {im.mode} as {pixel_format}") encoder_args = ("LA", 0, 0) else: raise VTFException(f"Unsupported pixel format: {pixel_format!r}") From 6b781a623bbc0a2d211facda1bd83d27fda2d509 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 14 Aug 2022 16:21:46 +0300 Subject: [PATCH 24/33] Add raw encoders/decoders for 2 channel RG format --- Tests/test_file_vtf.py | 10 +++++----- src/PIL/VtfImagePlugin.py | 11 ++--------- src/libImaging/Pack.c | 15 ++++++++++++++- src/libImaging/Unpack.c | 15 +++++++++++++++ 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/Tests/test_file_vtf.py b/Tests/test_file_vtf.py index f857ba2c02f..d466cb725dc 100644 --- a/Tests/test_file_vtf.py +++ b/Tests/test_file_vtf.py @@ -74,14 +74,14 @@ def test_get_texture_size( ("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_ia88.png", "Tests/images/vtf_ia88.vtf", "LA", 0.0), - # ( - # "Tests/images/vtf_RG.png", - # "Tests/images/vtf_RG.vtf", "RGB", - # 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_rgba8888.png", "Tests/images/vtf_rgba8888.vtf", "RGBA", 0.1), + ("Tests/images/vtf_rgba8888.png", "Tests/images/vtf_rgba8888.vtf", "RGBA", 0), ], ) def test_vtf_loading( diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index b6ecd628131..ba7e572605e 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -230,14 +230,7 @@ def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF): im = im.convert("LA") elif pixel_format == VtfPF.UV88: encoder = "raw" - if im.mode == "RGB" or im.mode == "RGBA": - r, g, *_ = im.split() - im = Image.merge("LA", (r, g)) - elif im.mode == "LA": - pass - else: - raise VTFException(f"Cannot encode {im.mode} as {pixel_format}") - encoder_args = ("LA", 0, 0) + encoder_args = ("RG", 0, 0) else: raise VTFException(f"Unsupported pixel format: {pixel_format!r}") @@ -327,7 +320,7 @@ def _open(self): elif pixel_format in (VtfPF.BGRA8888,): tile = ("raw", (0, 0) + self.size, data_start, ("BGRA", 0, 1)) elif pixel_format in (VtfPF.UV88,): - tile = ("raw", (0, 0) + self.size, data_start, ("LA", 0, 1)) + tile = ("raw", (0, 0) + self.size, data_start, ("RG", 0, 1)) elif pixel_format in L_FORMATS: tile = ("raw", (0, 0) + self.size, data_start, ("L", 0, 1)) elif pixel_format in LA_FORMATS: diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index 01760e742be..1fef6435f4f 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -242,6 +242,17 @@ packLA(UINT8 *out, const UINT8 *in, int pixels) { in += 4; } } +static void +packRG(UINT8 *out, const UINT8 *in, int pixels) { + int i; + /* LA, pixel interleaved */ + for (i = 0; i < pixels; i++) { + out[0] = in[R]; + out[1] = in[G]; + out += 2; + in += 4; + } +} static void packLAL(UINT8 *out, const UINT8 *in, int pixels) { @@ -491,7 +502,7 @@ copy3(UINT8 *out, const UINT8 *in, int pixels) { static void copy4(UINT8 *out, const UINT8 *in, int pixels) { /* RGBA, CMYK quadruples */ - memcpy(out, in, 4 * pixels); + memcpy(out, in, pixels * 4); } static void @@ -580,6 +591,7 @@ static struct { {"RGB", "BGRX", 32, ImagingPackBGRX}, {"RGB", "XBGR", 32, ImagingPackXBGR}, {"RGB", "RGB;L", 24, packRGBL}, + {"RGB", "RG", 16, packRG}, {"RGB", "R", 8, band0}, {"RGB", "G", 8, band1}, {"RGB", "B", 8, band2}, @@ -592,6 +604,7 @@ static struct { {"RGBA", "BGRA", 32, ImagingPackBGRA}, {"RGBA", "ABGR", 32, ImagingPackABGR}, {"RGBA", "BGRa", 32, ImagingPackBGRa}, + {"RGBA", "RG", 16, packRG}, {"RGBA", "R", 8, band0}, {"RGBA", "G", 8, band1}, {"RGBA", "B", 8, band2}, diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index e426ed74fce..6d97c2f199a 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -424,6 +424,18 @@ unpackLA(UINT8 *_out, const UINT8 *in, int pixels) { } } +static void +unpackRG(UINT8 *_out, const UINT8 *in, int pixels) { + int i; + /* LA, pixel interleaved */ + for (i = 0; i < pixels; i++) { + _out[R] = in[0]; + _out[G] = in[1]; + in += 2; + _out += 4; + } +} + static void unpackLAL(UINT8 *_out, const UINT8 *in, int pixels) { int i; @@ -1547,6 +1559,9 @@ static struct { {"PA", "PA", 16, unpackLA}, {"PA", "PA;L", 16, unpackLAL}, + /*2 channel to RGB/RGBA*/ + {"RGB", "RG", 16, unpackRG}, + {"RGBA", "RG", 16, unpackRG}, /* true colour */ {"RGB", "RGB", 24, ImagingUnpackRGB}, {"RGB", "RGB;L", 24, unpackRGBL}, From 9ff852f06bb3c601950141bdef9ee152ddb5cf57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Aug 2022 13:22:54 +0000 Subject: [PATCH 25/33] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_vtf.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Tests/test_file_vtf.py b/Tests/test_file_vtf.py index d466cb725dc..021765d1626 100644 --- a/Tests/test_file_vtf.py +++ b/Tests/test_file_vtf.py @@ -74,10 +74,7 @@ def test_get_texture_size( ("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_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_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), From a634a44a9c1e4c024fd51f582f245ed9c18f52be Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 14 Aug 2022 16:39:53 +0300 Subject: [PATCH 26/33] Add missing images --- Tests/images/vtf_uv88.png | Bin 0 -> 4852 bytes Tests/images/vtf_uv88.vtf | Bin 0 -> 174986 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/vtf_uv88.png create mode 100644 Tests/images/vtf_uv88.vtf diff --git a/Tests/images/vtf_uv88.png b/Tests/images/vtf_uv88.png new file mode 100644 index 0000000000000000000000000000000000000000..8689f14ca8c0f589533359686f5cea03c2bec6d5 GIT binary patch literal 4852 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 literal 0 HcmV?d00001 diff --git a/Tests/images/vtf_uv88.vtf b/Tests/images/vtf_uv88.vtf new file mode 100644 index 0000000000000000000000000000000000000000..043437caa840fbac48e6109186541f29286ea882 GIT binary patch literal 174986 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 literal 0 HcmV?d00001 From c3457f10eb6b56fcd3f9b795a79cd8df672dd5c6 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 14 Aug 2022 21:11:18 +0300 Subject: [PATCH 27/33] Add switch to DXT1 encoder to select block mode (With alpha/Without alpha) --- Tests/images/vtf_a8.png | Bin 3223 -> 4221 bytes Tests/images/vtf_a8.vtf | Bin 87605 -> 87605 bytes Tests/images/vtf_bgr888.vtf | Bin 262367 -> 262367 bytes Tests/images/vtf_dxt1.vtf | Bin 43928 -> 43928 bytes Tests/images/vtf_dxt1A.vtf | Bin 43928 -> 43928 bytes Tests/images/vtf_i8.vtf | Bin 87605 -> 87605 bytes Tests/images/vtf_ia88.vtf | Bin 174986 -> 174986 bytes Tests/images/vtf_rgb888.vtf | Bin 262367 -> 262367 bytes Tests/images/vtf_rgba8888.vtf | Bin 349748 -> 349748 bytes Tests/images/vtf_uv88.vtf | Bin 174986 -> 174986 bytes Tests/test_file_vtf.py | 38 ++++++++++-- src/PIL/VtfImagePlugin.py | 113 ++++++++++++---------------------- src/libImaging/BcnEncode.c | 36 ++++++----- 13 files changed, 94 insertions(+), 93 deletions(-) diff --git a/Tests/images/vtf_a8.png b/Tests/images/vtf_a8.png index 2411de7e2230a795d73301b89c2aa8d458b5ab60..1e95446f151993e688cd7f2605e3e70f018e5387 100644 GIT binary patch literal 4221 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 uwX8i!4IwA2dT+EB=g^XK8N%G diff --git a/Tests/images/vtf_a8.vtf b/Tests/images/vtf_a8.vtf index e2bf3501fe1c9538a25ec75a6ae66ae3d5247da4..4705e9e0915feed5579abf026f04048da5f1bdc8 100644 GIT binary patch delta 100 zcmdnGhIQ*2)(I^kwziDS0y7vG`1njk6Brm692}VAffNL2FfbfG%q+}ck-%VY?+}mx p6b%oD$^j{JE(V76>seVRurn}(hAJ?CU_Ty}=k83IN3h63hSq delta 100 zcmdnGhIQ*2)(I^kqN0q90y7vG*w{ox6Brm692^)!ffNL2FfceeG72(SBrs@cIXEN$ lMMDv?4CY)6469bLvQA(Ji7POG delta 108 zcmccLC~&_~U_wiXs3;?&zzhZkHa1bw1O^5M2M5MbAO!&$3=EErjDidn2@G0V4h{)G t(NKgegEU!RtP|Kl;tC8Pc}7MCm&O~dHyB%QFty%bo_d3Y9RTeB6i)yE diff --git a/Tests/images/vtf_dxt1.vtf b/Tests/images/vtf_dxt1.vtf index 6efc2d6596f88e43444dde4bee2ee460b95e3332..f74c3756b0a4ad4e562c0d9f77c3e2f916bd664e 100644 GIT binary patch delta 805 zcmZ8fO-K|`9RI&JGdsJ^eu$gjy0e7&b=W$%xHu%$iqUEbxGk-$6VSD9U}JN=B+jm>QoZDS)fh-(Sq<@e)E6t@%x&W-K5z~nz>^S z4H2O>3lI#(>rDXAG$D;}u(|;X1tAOx6NZO1-NfBg>hBrCKn=j^sw8D(0K#VQl^B-alHW5eVRiuE*P-OO?t_nAMV#f^BcJbELiFf29N}Ei8~g@sJXm zQpVU9(nsTQ7NHSwZx2+a(LTlJlOz>oEX!gx9TL*%G+U<^X&}HN!l@3^)D1_T_f)HJ z!I4dmpGuOnkdUje(E^vnG{3dA0!X)J5DlLMk4^eN9-Os!J@KA z6@_)mF)9drRDMsD5zPH5RF-#lH~2t}m-tGKyyKd8*lDFhv@BvE|A3dRgs-^7!6OMm zQyS(m*^~M-NpWw3N=S)!H;RN-t1Q-(qZ?Z&H^_X=1#w~`nLHR;3SugiG;m0;sN!M4 z)+JghvB#~q#e9Bt78!4_T$_i)`AXX@>Sl?l&3ss$_s~j(zdQDe5RJX*>LkC}x2|@I zJq5eJ)?tz0$m=o|gZNRlZ656DCt^CC)RA(Mt@ZY!yU)GlQX5}f zw1~f01jyl^#b@TWdznuyJs`ALPA-Q+R7D$x0iZ$)s4Bm^To?KSk$m_Q delta 800 zcmZ8fO=uHA6rMNPpQIa;ns#HGrb&?6)*#VCZBtO(6cJClhitGx>v|Asy);!kh}u0^ zZ-vztO28fhrhhp|VbG%$Tf~Ei2eCJeAm~Yp2Y(KwzHJW{-(la*n>X+K=KH3y&MWKO zy*sJv0L>M~6h+r<#+YG%MQJB`8O!BhFU#62oi+@cs@A_eW-s+XrAL zDQ$;M-&}6z-0s}*+w0#SF@Wu%AJaVA`9!YO<|Eh)$9z=4v(VYK8!1ajQ&S+n{SOIC z+By6ReO%Wu!4uNb5{se1`!oPqP7;i1nmEai(@+an_ysP@nDCwIqO!H6PAhYBthlAx zO1;i3n==-TnkLJ7>`YUXv^I`aImqjEd@N@ui4U>@LCebL8$^V$z#QI(BbZVpuBzCr zW;lQ-sPDNpG&E#<3OCfn#j+S^@8o>=>pnX-!Q|v5as}d2iXpzN~reLUJ#_ON&xPxo) zR&=ycti~14#X|fx=bM|DNw$j<$!UexYU0g-KV%rb>gk3N{NB^ai$x6eev6QfY)f5H zNf*i=)v+-VI@k{$l1Us#aXH;j%XowcZ72=ZQ8^dSt}aOddN-a;+c!uFcl35iA5)un__AC4dQ-JsiY~B`*Pty+}Zi z>}3Hmmtb=U;ES+11khXp2ik*jS*%YP8ioMhq=UHf!N9Z<8&-ZCPqH&oOCGZFeN|n< zX0voWAua~DZ+HKq zjCORpTMhjb|4RAW-#&i4Ago|(Yam&Er4p_Am9wK!8|}PjN%`jkGr7aD2u0Mx@<3+tCvt zYi2~>cdk~${bFYaC4p8d zyl>-aw>!eITUuOPY*A3ruBq~c`1%jm*1q@xxdz>?1ph_gd8EGe_%W%p!0&4=F1Sb_ zvD6b^ez~>9c1!v3$1D#|QGUUJwT=*9S=X+0yZr@MG>~Esk(P&u3jaTY*i?m1u2c|fnw9l*6oM7n_O6}4oly}}HWxqdE^8C}E zO6ga5wcUPZ`PN|25dQ%xrPtK@*%Qln!txzZ6DIMrfwWMYAPfB9t#y!;o~0@{fmu8&5l z-RO@N7nxbV&%c@@xUsR#D?0spoV^iJe@O&)?o{yifdE~E$db0h@?cP211BUMc`09G-{$#IT`Bj`TBoZSU| z-skavxew`Y-LUoO>X!0H zJTIMM)P$jr4gW{LFX_Mm!3eDHDf+8lX?p9H2fxf$QI&^!yx7293(ubMZkA_gPb@3{ zeNDK?5Po0&fh3-Lp2YUke{(RtV?J@Z2@etV!YH^7$~)kE!V%ZMv;zh|EON|I4A02- z7`*}gndiCiCw=8#a{NsH)(2%CW9lw^{q>kHF0R4fB{|ZR)_$qlUB&oVjyU>ZgM=E9 z@>jII=Gr9P#66x@S(I|~do%!!>?V2SNk^jn4fk8chh_pF3}5<{Ue9%pwDHJ1-u`jB zpFQ9>d-xseqa}7Q*ozND@GhN~@^_!Ho|Akr@yJlFo;iVSa~$J5Li_-@J9y&8z_&Oe zF1-mFlZSpK&$tqq9VnUh>vr3e^AY0QXv?z)ET7}DCXzsn!*Oz~os6UFq9@z0}>yYzOq;0zWzV&diRQmKGTNrB};xu&? zr{OgwjO(J~d2@dlx0$jpZ1#SZ7_Vm}UdI7`$Onh<3RqLs{z^#M&%QCLk zK|>q{#NXf@)*tOad=~9vN~Jn}Yx52~7quHIKj=IBs9!FL*AEuIJ}h3PwV!A~>=Wdk zaaY9OA5F%moq)$SxTWKR-S3sZ$o2=rVRAlb3HW)4?@N}@ME~I<(x23S(hqL6$9$ll z)o}e|YQKP&wX$N{M*l;(kLs)91Y6v0EBv6hd8iNnpZEKSS4Z(T_K@#UHp2H9Hsan- z@IHa>Q^+?yb+q^|qYUB{ZGW17ONn5e4wvfpIGYtm)k9iCi_)&ghxCV!w*736PQJCe z`rtl)Pqy#Nf78dMmnP50@0&U{{YDy{D8~_-m8)0Zd(Y?r?EwA3<7N{`E26V0+tTOUMWO$e6%~a+%{_`aK-l(k~e9@M*St zOnuH9YWv67{;jQPxu@6HZohxkw10^D&_9XN!GOo)Lr-+eq$A}Oj{V`Y;W1i^`2(Dd z<^&F!TDAEre4jtne()V~a4hz}kIKZ#SN?9-3F`3ASxfmq#qAcQvPZ@~rPGayiuYNY zh?5|1YXfpkx(R;=^lv1_S?Ccr=@%gYq)MN}pYq^aJ{V;|50w7jLp2XQ>G$8d2F#r0 zvc<~39mhfM(f@K@a@6=-&o?NcpWbBuTutYL@#_qI#`|5VaCMucdS0H-IBJ3(h+uQG z-4$z`b(F*2upt?fmU^~ZKcFe&b?|R{Jg59wMbGmP$A;>F4{&6w!~k>)zq;^az5|8B zquS{K$7O~#7{_6ZM8$JJ|4d8wBaa@=&W;YBOxA;OGu#Qg(dlqtP^5wb1qTWa6dWix zP;j8&K*5260|f^P4ip?HI8bn);6TBFf&&Ey3Jw$;C^%4Xpx{8kfr0}C2MP`p94I(Y zaG>D8d~u+^z}XgFne!w(?+-9bf|Ro*4wF6-eN4UJye3!8=Q{p%hc7xR7d|I{xGqxj z{jezK86n4krqP)%9n3@JG3T4jroNg#j-633--hy`X;b{SI!;<&NeN=^aq3n%Z;NMF zS2bmR>uO%xu`fq`VZ?&?bAGflo(~4xh#K+BPB%wp{VXYz4YRJ2*H@T%Xjv)eDXNvW zzu|m6OkvYrwYfcwbRL0LaM1v}uv zKd58GQEG1~KViT9C}zpQ>A*k4oq81FZZLX!m-6q&?5Ti-^}M9@&~ZH;ADi{%Df=m; z^cQA*`6TY4TBe8<&i=hEwZ0qN z4Nb>Cvaf0eeOCKQ5Cp8F*DJ;#YgwKP_b26kBR2H=62~c}2;OPq7UGU|N^inDaYY#q z(LWHd3M(tzhl4etJh=RY-5%5A;AHCm%{_m9V>r1#N*t4P{IjpCt=Gxzcmn(h>odpp zB9srkuJA*(PiPBP0C;nM+nM%2+ktz^4m2;I@YPk094C_x!Kl@YJe08icw7!nCjQiW z+gAHP&CtO~`~~cO+WsLSUQ>45*!@Y)bA4*kej-!&oBX)nPM7#stGVJAYCj?eg=g81 zhUk~eHtfPWEQL_g!2S=vPed~QAI5Xxeo%3Jk00;h&vkDu{#USnN*28}xN+a z2i|5>i2GCIj9;n!_Os$|D%wGn=~q%7lD;=!?wdDCZ+>N^mas2W9Cvf9@u%LyUVO^- zPOd9Y)mPx3Z1>|lLpP9Jo`P4gj`!--4EEpn1;EdB-$0>R^gmv=lXA>etMGs9r+0m8 z3G4sUZ1wbcS`6C1UggtIbJ=U`Bes~V_%WvD?FSei&VhfQ-`n7Rbif~P08VG39fST> zi9hYv_GLf)fgF4pvEQk3S8qeRsc!1u^O#jd5 z|3BYDnkeuT9uWUptyId6f1Ue#x)(mDpO|-?Ke>NmR(9)tz~F9Z9Dj4nxIn8tTMlTw zu3yhuhm(4rZ}qH7B@g}%`dPj~<$i%l`<)parfGct$gDiwfOYrs9+hx0?#4p{z!d{625+VJ8$P5kjWsG6S{|2D>A^!K=*Q_{ZJsW`yj zZ?!ni=KJ~l^v&=)jeFmPO7$zL_vEmq^as0gLE(;)L3vGE{|@@E)18dp=V!*B{l3OO zTYOjT2RnQJSS1mZ3rfcRRQCfa-w4hI?bH9|QQ`-;Z)c-!Y zo6+t=KUEyg;FgzeDL)}LZlgy}{ST><1N8Gr`xhtL0rVQMcoobaR&s?S2j9i?lpRFA z2c2p z)R%m}TdjU8eSEKg7Q&}LqWn1R@2A;+b3VQgy}{qmZ6yc)npqA`1OD{5$=&F<%J6fF zD!c!@IHx^t@b{(9fr1MPd(QVZe#(CPQ}qk`1%9tRx(~m>-%x&UdU{@)KV=tA0{*qs z@jtnjGro)cbn(TA=hXAG=Og%k!}(i2_9>^mCjN)h4;cKPseSpsnP~^+AOBj7-;<^u zO~-$Uf9$&;q5e$imA3n^@NoCpH`7l$Ldx$ytMko&cg#>(tbc?+8;1- zKs&JCJn{L*zgk_{o7ZOI|3o)$e3$H`49ypHFh0fieZukQ2F3$;{cVrsVHrrivHmVs8vc(@IQ~L(@ZDk2C;r*&KCPzgO8N9I*3Y<{ z4exoYQ|l)}^YZ+pr~4_43z16a(#D6;^ZGrZFyoR;^6H2U^!Hba zzH$Kk^SvkBh<&FND*%!FJ$*NNZ|F(Te{Csg(23RllvnvC)!b|J-|%DI;%w^mGW!F5 z@9c;Z`h)Phi+z+d{_bLe9YB>czb6D2!uYU?|0MYRroHoz|H15j8vcea9VG`oMwQ1U z2j#N*?&QS#CBApWvKEYT)4t1`Chz(^;lHbXpW|cGt~i~4{IT9XgWelHseMKg?@EK<6Z*lHr9pH2IZYE!uLs%-({q~R|#pv?=$V2XZ)YX z?q|i{@X655neA;Md^KKJ18WbK@Hp&>XJ1nEM%hsmD2q3Qg99hvnbOoca9#ndIr@S4 z_gPIl44q&6v7RFPx=Qd(`}=LI3$`QrFV+4Z5_-b7@du%ty+LoA&l9!%>1*PH4|v~2 z#4SnrL;`)CaeVIg;%vcFwCj35x&!nBKK#M1u>&Vc|Ff;rRr(^qVZPTRKb3_=#1ynj zISBDLa zJtcHvR=dr0;=k{DTDATY-v{QbOuS7!Ec_2SpPx|Fz>IpjliNKrf?0iuDK-Yhl zc8>3XT(%eELV@|j9n2)1pOkN$&vkTd?5m>nK7lIy9rfN6dIqZOg4JagU3zikf#0Ln z-=Wq$48KF2XWpQ%M!g~+{yovdxTevdWtywhXKnM?ed5h|dZL!%nfXWkHp-#0+n`tj*uz|6)eU6ii-0}sBC5z-g(uckymQ{{fU5ip^#>@G h%fyYfGIS6gpAD(VNk5F`=XvYM{Uq(zLvCeB{|8bK&BXu! literal 43928 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 Tj=ULH*nGG2xIyQ3j{f{V>{|rI diff --git a/Tests/images/vtf_i8.vtf b/Tests/images/vtf_i8.vtf index f5e949285f05042d69134843428f4c6711040f95..d3e5e94f5bae44f6e3cf0eaa17efc063062a6c96 100644 GIT binary patch delta 100 zcmdnGhIQ*2)(I^kwziDS0y7vG`1njk6Brm692}VAffNL2FfbfG%q+}ck-%VY?+}mx p6b%oD$^j{JE(V76>seVRurn}(hAJ?CU_Ty}=k83IN3h63hSq delta 100 zcmdnGhIQ*2)(I^kqN0q90y7vG*w{ox6Brm692^)!ffNL2FfceeG72(SBrs@cIXEN$ lMMDv?4CY)6469bLvQA(Ji7POGU!RtP|Kl;tC8Pc}7MCm&O~dHyF3xVA`|_0ETlC`2YX_ diff --git a/Tests/images/vtf_rgb888.vtf b/Tests/images/vtf_rgb888.vtf index 55f70d4f72f37655e058e5caa3a8e288578f9404..b8eb744dbf5a95b35d741d98fc75d5a4f5b54550 100644 GIT binary patch delta 108 zcmccLC~&_~U_wiXtt}(7zzhZkK0Z^?1O^5M2M6YOAO!&$3=D@4GYd0VBrw?9I|L*E xMZ?3PazM(Qi-BSNdREp6> delta 108 zcmccLC~&_~U_wiXs3;?&zzhZkHa1bw1O^5M2M5MbAO!&$3=EErjDidn2@G0V4h{)G t(NKgegEU!RtP|Kl;tC8Pc}7MCm&O~dHyB%QFty%bo_d3Y9RTeB6i)yE diff --git a/Tests/images/vtf_rgba8888.vtf b/Tests/images/vtf_rgba8888.vtf index 9553f80a53702eea3c8de5b4888564888adb795e..6d8248bc6323c1a96b7a8fd5445418725a7314db 100644 GIT binary patch delta 112 zcmdn8Ms&*>(FrXfwziDS0y7vG`1njk6Brm692}VAffNL2FfbfG%q+}ck-%VY?+}mx z6b%oD$^j{JE(V76>seVRurn}(hAJ?CU`G-e7FK!PI(#x%CFi)*Gxbp#a&2 B7;69k delta 112 zcmdn8Ms&*>(FrXfqN0q90y7vG*w{ox6Brm692^)!ffNL2FfceeG72(SBrs@cIXEN$ xMMDv?4CY)6469bLvQA(Ji7POGU!RtP|Kl;tC8Pc}7MCm&O~dHyF3xVA`|_0ETlC`2YX_ diff --git a/Tests/test_file_vtf.py b/Tests/test_file_vtf.py index 021765d1626..b0e7321fe48 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 ba7e572605e..79f82d92e87 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 @@ class VtfPF(IntEnum): ("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); } From 906890bfe86adfb53d0f551dbe1c015bb457fbc2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Aug 2022 18:12:18 +0000 Subject: [PATCH 28/33] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_vtf.py | 11 +++++------ src/PIL/VtfImagePlugin.py | 29 ++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Tests/test_file_vtf.py b/Tests/test_file_vtf.py index b0e7321fe48..d84981885b5 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 @@ -82,9 +82,7 @@ def test_get_texture_size( ("Tests/images/vtf_rgba8888.png", "Tests/images/vtf_rgba8888.vtf", "RGBA", 0), ], ) -def test_vtf_read( - 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) assert f.mode == expected_mode @@ -109,8 +107,9 @@ def test_vtf_read( (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): +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) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 79f82d92e87..bea11ae16ee 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -137,7 +137,10 @@ def _get_texture_size(pixel_format: VtfPF, width, height): return width * height // 2 elif pixel_format in (VtfPF.DXT3, VtfPF.DXT5): return width * height - elif pixel_format in (VtfPF.A8, VtfPF.I8,): + elif pixel_format in ( + VtfPF.A8, + VtfPF.I8, + ): return width * height elif pixel_format in (VtfPF.UV88, VtfPF.IA88): return width * height * 2 @@ -205,7 +208,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): @@ -246,8 +249,15 @@ def _open(self): # flags = CompiledVtfFlags(header.flags) pixel_format = VtfPF(header.pixel_format) low_format = VtfPF(header.low_pixel_format) - if pixel_format in (VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT1, VtfPF.DXT3, VtfPF.DXT5, - VtfPF.RGBA8888, VtfPF.BGRA8888,VtfPF.A8): + 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 (VtfPF.RGB888, VtfPF.BGR888, VtfPF.UV88): self.mode = "RGB" @@ -309,9 +319,14 @@ def _save(im, fp, filename): if pixel_format == VtfPF.DXT1_ONEBITALPHA: flags |= CompiledVtfFlags.ONEBITALPHA - elif pixel_format in (VtfPF.DXT3, VtfPF.DXT5, - VtfPF.RGBA8888, VtfPF.BGRA8888, - VtfPF.A8, VtfPF.IA88): + elif pixel_format in ( + VtfPF.DXT3, + VtfPF.DXT5, + VtfPF.RGBA8888, + VtfPF.BGRA8888, + VtfPF.A8, + VtfPF.IA88, + ): flags |= CompiledVtfFlags.EIGHTBITALPHA else: pass From d5e330e6aae07f59afaf8d88fde923087c57375e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 08:45:23 +0000 Subject: [PATCH 29/33] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_vtf.py | 6 +++--- src/PIL/VtfImagePlugin.py | 44 ++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/Tests/test_file_vtf.py b/Tests/test_file_vtf.py index d84981885b5..62a6ad47d87 100644 --- a/Tests/test_file_vtf.py +++ b/Tests/test_file_vtf.py @@ -1,4 +1,4 @@ -from typing import Tuple +from __future__ import annotations import pytest @@ -42,7 +42,7 @@ def test_closest_power(size: int, expected_size: int): ((1024, 1), 11), ], ) -def test_get_mipmap_count(size: Tuple[int, int], expected_count: int): +def test_get_mipmap_count(size: tuple[int, int], expected_count: int): assert _get_mipmap_count(*size) == expected_count @@ -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 diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index bea11ae16ee..8d5e0de4e74 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -9,6 +9,7 @@ Full text of the CC0 license: https://creativecommons.org/publicdomain/zero/1.0/ """ +from __future__ import annotations import struct from enum import IntEnum, IntFlag @@ -101,30 +102,25 @@ class VtfPF(IntEnum): # UVLX8888 = 26 -VTFHeader = NamedTuple( - "VTFHeader", - [ - ("header_size", int), - ("width", int), - ("height", int), - ("flags", int), - ("frames", int), - ("first_frames", int), - ("reflectivity_r", float), - ("reflectivity_g", float), - ("reflectivity_b", float), - ("bumpmap_scale", float), - ("pixel_format", int), - ("mipmap_count", int), - ("low_pixel_format", int), - ("low_width", int), - ("low_height", int), - # V 7.2+ - ("depth", int), - # V 7.3+ - ("resource_count", int), - ], -) +class VTFHeader(NamedTuple): + header_size: int + width: int + height: int + flags: int + frames: int + first_frames: int + reflectivity_r: float + reflectivity_g: float + reflectivity_b: float + bumpmap_scale: float + pixel_format: int + mipmap_count: int + low_pixel_format: int + low_width: int + low_height: int + depth: int + resource_count: int + BLOCK_COMPRESSED = (VtfPF.DXT1, VtfPF.DXT1_ONEBITALPHA, VtfPF.DXT3, VtfPF.DXT5) HEADER_V70 = " Date: Sun, 24 Dec 2023 20:03:33 +1100 Subject: [PATCH 30/33] mode is read-only --- src/PIL/VtfImagePlugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 8d5e0de4e74..9ff6d8369cf 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -254,13 +254,13 @@ def _open(self): VtfPF.BGRA8888, VtfPF.A8, ): - self.mode = "RGBA" + self._mode = "RGBA" elif pixel_format in (VtfPF.RGB888, VtfPF.BGR888, VtfPF.UV88): - self.mode = "RGB" + self._mode = "RGB" elif pixel_format == VtfPF.I8: - self.mode = "L" + self._mode = "L" elif pixel_format == VtfPF.IA88: - self.mode = "LA" + self._mode = "LA" else: raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") From 0ed7d5c044ccaf450f5de6e6d3d776a62f2f6edf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 24 Dec 2023 20:06:06 +1100 Subject: [PATCH 31/33] Assign exception string to variable first --- src/PIL/VtfImagePlugin.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index 9ff6d8369cf..be2d35ff6c3 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -144,7 +144,8 @@ def _get_texture_size(pixel_format: VtfPF, width, height): return width * height * 3 elif pixel_format == VtfPF.RGBA8888: return width * height * 4 - raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") + msg = f"Unsupported VTF pixel format: {pixel_format}" + raise VTFException(msg) def _get_mipmap_count(width: int, height: int): @@ -196,7 +197,8 @@ def _write_image(fp: BufferedIOBase, im: Image.Image, pixel_format: VtfPF): encoder = "raw" encoder_args = ("RG", 0, 0) else: - raise VTFException(f"Unsupported pixel format: {pixel_format!r}") + msg = f"Unsupported pixel format: {pixel_format!r}" + raise VTFException(msg) tile = [(encoder, extents, fp.tell(), encoder_args)] ImageFile._save(im, fp, tile, _get_texture_size(pixel_format, *im.size)) @@ -213,7 +215,8 @@ class VtfImageFile(ImageFile.ImageFile): def _open(self): if not _accept(self.fp.read(12)): - raise SyntaxError("not a VTF file") + msg = "not a VTF file" + raise SyntaxError(msg) self.fp.seek(4) version = struct.unpack("<2I", self.fp.read(8)) if version <= (7, 2): @@ -241,7 +244,8 @@ def _open(self): ) self.fp.seek(header.header_size) else: - raise VTFException(f"Unsupported VTF version: {version}") + msg = f"Unsupported VTF version: {version}" + raise VTFException(msg) # flags = CompiledVtfFlags(header.flags) pixel_format = VtfPF(header.pixel_format) low_format = VtfPF(header.low_pixel_format) @@ -262,7 +266,8 @@ def _open(self): elif pixel_format == VtfPF.IA88: self._mode = "LA" else: - raise VTFException(f"Unsupported VTF pixel format: {pixel_format}") + msg = f"Unsupported VTF pixel format: {pixel_format}" + raise VTFException(msg) self._size = (header.width, header.height) @@ -298,14 +303,16 @@ def _open(self): 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}") + msg = f"Unsupported VTF pixel format: {pixel_format}" + raise VTFException(msg) self.tile = [tile] def _save(im, fp, filename): im: Image.Image if im.mode not in ("RGB", "RGBA", "L", "LA"): - raise OSError(f"cannot write mode {im.mode} as VTF") + msg = f"cannot write mode {im.mode} as VTF" + raise OSError(msg) encoderinfo = im.encoderinfo pixel_format = VtfPF(encoderinfo.get("pixel_format", VtfPF.RGBA8888)) version = encoderinfo.get("version", (7, 4)) @@ -373,7 +380,8 @@ def _save(im, fp, filename): header = header._replace(header_size=size + (16 - size % 16)) fp.write(struct.pack(HEADER_V73, *header)) else: - raise VTFException(f"Unsupported version {version}") + msg = f"Unsupported version {version}" + raise VTFException(msg) if version > (7, 2): fp.write(b"\x01\x00\x00\x00") From 44d70b8b253381c6c1b95f3acb4ad4cb81cc1132 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 10:15:49 +0000 Subject: [PATCH 32/33] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/VtfImagePlugin.py | 1 + src/libImaging/Bcn.h | 6 +++--- src/libImaging/BcnDecode.c | 2 -- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/PIL/VtfImagePlugin.py b/src/PIL/VtfImagePlugin.py index be2d35ff6c3..8aa9c43243b 100644 --- a/src/PIL/VtfImagePlugin.py +++ b/src/PIL/VtfImagePlugin.py @@ -9,6 +9,7 @@ Full text of the CC0 license: https://creativecommons.org/publicdomain/zero/1.0/ """ + from __future__ import annotations import struct diff --git a/src/libImaging/Bcn.h b/src/libImaging/Bcn.h index 3588828e870..5ab67a1925c 100644 --- a/src/libImaging/Bcn.h +++ b/src/libImaging/Bcn.h @@ -31,12 +31,12 @@ typedef struct { #define BIT_MASK(bit_count) ((1 << (bit_count)) - 1) #define SET_BITS(target, bit_offset, bit_count, value) \ - target |= (((value)&BIT_MASK(bit_count)) << (bit_offset)) + target |= (((value) & BIT_MASK(bit_count)) << (bit_offset)) #define GET_BITS(source, bit_offset, bit_count) \ ((source) & (BIT_MASK(bit_count) << (bit_offset))) >> (bit_offset) #define SWAP(TYPE, A, B) \ do { \ TYPE TMP = A; \ - (A) = B; \ - (B) = TMP; \ + (A) = B; \ + (B) = TMP; \ } while (0) diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 2ee367e6ab7..a1781e05763 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -15,8 +15,6 @@ #include "Bcn.h" - - #define LOAD16(p) (p)[0] | ((p)[1] << 8) #define LOAD32(p) (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24) From f41910c406bcabc85b3a921885050afb1be543b6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:55:33 +0000 Subject: [PATCH 33/33] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/libImaging/BcnEncode.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 299d22adbab..807f5cafae3 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -53,7 +53,8 @@ rgb565_lerp(UINT16 c0, UINT16 c1, UINT8 a_fac, UINT8 b_fac) { return PACK_SHORT_565( (r0 * a_fac + r1 * b_fac) / (a_fac + b_fac), (g0 * a_fac + g1 * b_fac) / (a_fac + b_fac), - (b0 * a_fac + b1 * b_fac) / (a_fac + b_fac)); + (b0 * a_fac + b1 * b_fac) / (a_fac + b_fac) + ); } typedef struct { @@ -82,7 +83,8 @@ pick_2_major_colors( const UINT8 *color_freq, UINT16 color_count, UINT16 *color0, - UINT16 *color1) { + UINT16 *color1 +) { UINT32 i; Color colors[16]; memset(colors, 0, sizeof(colors));