From f2d91fcdaaafa38b1b98895fc839139e38a23f8e Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sat, 6 Aug 2022 20:26:42 +0300 Subject: [PATCH 01/58] Add IntEnum/IntFlag for most of DDS members --- src/PIL/DdsImagePlugin.py | 418 +++++++++++++++++++++++++++----------- 1 file changed, 298 insertions(+), 120 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index a946daeaa6b..5069fc233f0 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -11,6 +11,7 @@ """ import struct +from enum import IntFlag, IntEnum from io import BytesIO from . import Image, ImageFile @@ -19,68 +20,45 @@ # Magic ("DDS ") DDS_MAGIC = 0x20534444 -# DDS flags -DDSD_CAPS = 0x1 -DDSD_HEIGHT = 0x2 -DDSD_WIDTH = 0x4 -DDSD_PITCH = 0x8 -DDSD_PIXELFORMAT = 0x1000 -DDSD_MIPMAPCOUNT = 0x20000 -DDSD_LINEARSIZE = 0x80000 -DDSD_DEPTH = 0x800000 - -# DDS caps -DDSCAPS_COMPLEX = 0x8 -DDSCAPS_TEXTURE = 0x1000 -DDSCAPS_MIPMAP = 0x400000 - -DDSCAPS2_CUBEMAP = 0x200 -DDSCAPS2_CUBEMAP_POSITIVEX = 0x400 -DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800 -DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000 -DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000 -DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000 -DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000 -DDSCAPS2_VOLUME = 0x200000 - -# Pixel Format -DDPF_ALPHAPIXELS = 0x1 -DDPF_ALPHA = 0x2 -DDPF_FOURCC = 0x4 -DDPF_PALETTEINDEXED8 = 0x20 -DDPF_RGB = 0x40 -DDPF_LUMINANCE = 0x20000 +# DDS flags +class DDSD(IntFlag): + CAPS = 0x1 + HEIGHT = 0x2 + WIDTH = 0x4 + PITCH = 0x8 + PIXELFORMAT = 0x1000 + MIPMAPCOUNT = 0x20000 + LINEARSIZE = 0x80000 + DEPTH = 0x800000 -# dds.h -DDS_FOURCC = DDPF_FOURCC -DDS_RGB = DDPF_RGB -DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS -DDS_LUMINANCE = DDPF_LUMINANCE -DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS -DDS_ALPHA = DDPF_ALPHA -DDS_PAL8 = DDPF_PALETTEINDEXED8 +# DDS caps +class DDSCAPS(IntFlag): + COMPLEX = 0x8 + TEXTURE = 0x1000 + MIPMAP = 0x400000 -DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT -DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT -DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH -DDS_HEADER_FLAGS_PITCH = DDSD_PITCH -DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE -DDS_HEIGHT = DDSD_HEIGHT -DDS_WIDTH = DDSD_WIDTH +class DDSCAPS2(IntFlag): + CUBEMAP = 0x200 + CUBEMAP_POSITIVEX = 0x400 + CUBEMAP_NEGATIVEX = 0x800 + CUBEMAP_POSITIVEY = 0x1000 + CUBEMAP_NEGATIVEY = 0x2000 + CUBEMAP_POSITIVEZ = 0x4000 + CUBEMAP_NEGATIVEZ = 0x8000 + VOLUME = 0x200000 -DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE -DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP -DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX -DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX -DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX -DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY -DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY -DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ -DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ +# Pixel Format +class DDPF(IntFlag): + ALPHAPIXELS = 0x1 + ALPHA = 0x2 + FOURCC = 0x4 + PALETTEINDEXED8 = 0x20 + RGB = 0x40 + LUMINANCE = 0x20000 # DXT1 @@ -94,18 +72,215 @@ # dxgiformat.h - -DXGI_FORMAT_R8G8B8A8_TYPELESS = 27 -DXGI_FORMAT_R8G8B8A8_UNORM = 28 -DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29 -DXGI_FORMAT_BC5_TYPELESS = 82 -DXGI_FORMAT_BC5_UNORM = 83 -DXGI_FORMAT_BC5_SNORM = 84 -DXGI_FORMAT_BC6H_UF16 = 95 -DXGI_FORMAT_BC6H_SF16 = 96 -DXGI_FORMAT_BC7_TYPELESS = 97 -DXGI_FORMAT_BC7_UNORM = 98 -DXGI_FORMAT_BC7_UNORM_SRGB = 99 +class DXGI_FORMAT(IntEnum): + UNKNOWN = 0, + R32G32B32A32_TYPELESS = 1, + R32G32B32A32_FLOAT = 2, + R32G32B32A32_UINT = 3, + R32G32B32A32_SINT = 4, + R32G32B32_TYPELESS = 5, + R32G32B32_FLOAT = 6, + R32G32B32_UINT = 7, + R32G32B32_SINT = 8, + R16G16B16A16_TYPELESS = 9, + R16G16B16A16_FLOAT = 10, + R16G16B16A16_UNORM = 11, + R16G16B16A16_UINT = 12, + R16G16B16A16_SNORM = 13, + R16G16B16A16_SINT = 14, + R32G32_TYPELESS = 15, + R32G32_FLOAT = 16, + R32G32_UINT = 17, + R32G32_SINT = 18, + R32G8X24_TYPELESS = 19, + D32_FLOAT_S8X24_UINT = 20, + R32_FLOAT_X8X24_TYPELESS = 21, + X32_TYPELESS_G8X24_UINT = 22, + R10G10B10A2_TYPELESS = 23, + R10G10B10A2_UNORM = 24, + R10G10B10A2_UINT = 25, + R11G11B10_FLOAT = 26, + R8G8B8A8_TYPELESS = 27, + R8G8B8A8_UNORM = 28, + R8G8B8A8_UNORM_SRGB = 29, + R8G8B8A8_UINT = 30, + R8G8B8A8_SNORM = 31, + R8G8B8A8_SINT = 32, + R16G16_TYPELESS = 33, + R16G16_FLOAT = 34, + R16G16_UNORM = 35, + R16G16_UINT = 36, + R16G16_SNORM = 37, + R16G16_SINT = 38, + R32_TYPELESS = 39, + D32_FLOAT = 40, + R32_FLOAT = 41, + R32_UINT = 42, + R32_SINT = 43, + R24G8_TYPELESS = 44, + D24_UNORM_S8_UINT = 45, + R24_UNORM_X8_TYPELESS = 46, + X24_TYPELESS_G8_UINT = 47, + R8G8_TYPELESS = 48, + R8G8_UNORM = 49, + R8G8_UINT = 50, + R8G8_SNORM = 51, + R8G8_SINT = 52, + R16_TYPELESS = 53, + R16_FLOAT = 54, + D16_UNORM = 55, + R16_UNORM = 56, + R16_UINT = 57, + R16_SNORM = 58, + R16_SINT = 59, + R8_TYPELESS = 60, + R8_UNORM = 61, + R8_UINT = 62, + R8_SNORM = 63, + R8_SINT = 64, + A8_UNORM = 65, + R1_UNORM = 66, + R9G9B9E5_SHAREDEXP = 67, + R8G8_B8G8_UNORM = 68, + G8R8_G8B8_UNORM = 69, + BC1_TYPELESS = 70, + BC1_UNORM = 71, + BC1_UNORM_SRGB = 72, + BC2_TYPELESS = 73, + BC2_UNORM = 74, + BC2_UNORM_SRGB = 75, + BC3_TYPELESS = 76, + BC3_UNORM = 77, + BC3_UNORM_SRGB = 78, + BC4_TYPELESS = 79, + BC4_UNORM = 80, + BC4_SNORM = 81, + BC5_TYPELESS = 82, + BC5_UNORM = 83, + BC5_SNORM = 84, + B5G6R5_UNORM = 85, + B5G5R5A1_UNORM = 86, + B8G8R8A8_UNORM = 87, + B8G8R8X8_UNORM = 88, + R10G10B10_XR_BIAS_A2_UNORM = 89, + B8G8R8A8_TYPELESS = 90, + B8G8R8A8_UNORM_SRGB = 91, + B8G8R8X8_TYPELESS = 92, + B8G8R8X8_UNORM_SRGB = 93, + BC6H_TYPELESS = 94, + BC6H_UF16 = 95, + BC6H_SF16 = 96, + BC7_TYPELESS = 97, + BC7_UNORM = 98, + BC7_UNORM_SRGB = 99, + AYUV = 100, + Y410 = 101, + Y416 = 102, + NV12 = 103, + P010 = 104, + P016 = 105, + _420_OPAQUE = 106, + YUY2 = 107, + Y210 = 108, + Y216 = 109, + NV11 = 110, + AI44 = 111, + IA44 = 112, + P8 = 113, + A8P8 = 114, + B4G4R4A4_UNORM = 115, + P208 = 130, + V208 = 131, + V408 = 132, + SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 133 + SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 134 + INVALID = -1 + + @classmethod + def _missing_(cls, value: object): + return cls.INVALID + + +def make_fourcc(name): + return struct.unpack('I', name.encode('ascii'))[0] + + +class D3DFMT(IntEnum): + UNKNOWN = 0 + R8G8B8 = 20 + A8R8G8B8 = 21 + X8R8G8B8 = 22 + R5G6B5 = 23 + X1R5G5B5 = 24 + A1R5G5B5 = 25 + A4R4G4B4 = 26 + R3G3B2 = 27 + A8 = 28 + A8R3G3B2 = 29 + X4R4G4B4 = 30 + A2B10G10R10 = 31 + A8B8G8R8 = 32 + X8B8G8R8 = 33 + G16R16 = 34 + A2R10G10B10 = 35 + A16B16G16R16 = 36 + A8P8 = 40 + P8 = 41 + L8 = 50 + A8L8 = 51 + A4L4 = 52 + V8U8 = 60 + L6V5U5 = 61 + X8L8V8U8 = 62 + Q8W8V8U8 = 63 + V16U16 = 64 + A2W10V10U10 = 67 + D16_LOCKABLE = 70 + D32 = 71 + D15S1 = 73 + D24S8 = 75 + D24X8 = 77 + D24X4S4 = 79 + D16 = 80 + D32F_LOCKABLE = 82 + D24FS8 = 83 + D32_LOCKABLE = 84 + S8_LOCKABLE = 85 + L16 = 81 + VERTEXDATA = 100 + INDEX16 = 101 + INDEX32 = 102 + Q16W16V16U16 = 110 + R16F = 111 + G16R16F = 112 + A16B16G16R16F = 113 + R32F = 114 + G32R32F = 115 + A32B32G32R32F = 116 + CxV8U8 = 117 + A1 = 118 + A2B10G10R10_XR_BIAS = 119 + BINARYBUFFER = 199 + + UYVY = make_fourcc('UYVY') + R8G8_B8G8 = make_fourcc('RGBG') + YUY2 = make_fourcc('YUY2') + G8R8_G8B8 = make_fourcc('GRGB') + DXT1 = make_fourcc('DXT1') + DXT2 = make_fourcc('DXT2') + DXT3 = make_fourcc('DXT3') + DXT4 = make_fourcc('DXT4') + DXT5 = make_fourcc('DXT5') + DX10 = make_fourcc('DX10') + BC5S = make_fourcc('BC5S') + ATI1 = make_fourcc('ATI1') + ATI2 = make_fourcc('ATI2') + MULTI2_ARGB8 = make_fourcc('MET1') + INVALID = -1 + + @classmethod + def _missing_(cls, value: object): + return cls.INVALID class DdsImageFile(ImageFile.ImageFile): @@ -126,7 +301,8 @@ def _open(self): raise OSError(msg) header = BytesIO(header_bytes) - flags, height, width = struct.unpack("<3I", header.read(12)) + flags_, height, width = struct.unpack("<3I", header.read(12)) + flags = DDSD(flags_) self._size = (width, height) self.mode = "RGBA" @@ -134,90 +310,94 @@ def _open(self): struct.unpack("<11I", header.read(44)) # reserved # pixel format - pfsize, pfflags = struct.unpack("<2I", header.read(8)) - fourcc = header.read(4) - (bitcount,) = struct.unpack(" Date: Sat, 6 Aug 2022 23:22:16 +0300 Subject: [PATCH 02/58] Add Tile namedtuple for code readability. Add typing info to Image.tile --- src/PIL/DdsImagePlugin.py | 34 +++++++++++++++++----------------- src/PIL/Image.py | 10 ++++++++++ src/PIL/ImageFile.py | 13 +++++++------ 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 5069fc233f0..d211cc99411 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -343,24 +343,24 @@ def _open(self): data_start = header_size + 4 if fourcc == D3DFMT.DXT1: self.pixel_format = "DXT1" - tile = ("bcn", (0, 0) + self.size, data_start, (1, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (1, self.pixel_format)) elif fourcc == D3DFMT.DXT3: self.pixel_format = "DXT3" - tile = ("bcn", (0, 0) + self.size, data_start, (2, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (2, self.pixel_format)) elif fourcc == D3DFMT.DXT5: self.pixel_format = "DXT5" - tile = ("bcn", (0, 0) + self.size, data_start, (3, self.pixel_format)) - elif fourcc == D3DFMT.BC5S: - self.pixel_format = "BC5S" - tile = ("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) - self.mode = "RGB" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (3, self.pixel_format)) elif fourcc == D3DFMT.ATI1: self.pixel_format = "BC4" - tile = ("bcn", (0, 0) + self.size, data_start, (4, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (4, self.pixel_format)) self.mode = "L" + elif fourcc == D3DFMT.BC5S: + self.pixel_format = "BC5S" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + self.mode = "RGB" elif fourcc == D3DFMT.ATI2: self.pixel_format = "BC5" - tile = ("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) self.mode = "RGB" elif fourcc == D3DFMT.DX10: data_start += 20 @@ -369,33 +369,33 @@ def _open(self): self.fp.read(16) if dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM): self.pixel_format = "BC5" - tile = ("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) self.mode = "RGB" elif dxgi_format == DXGI_FORMAT.BC5_SNORM: self.pixel_format = "BC5S" - tile = ("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) self.mode = "RGB" elif dxgi_format == DXGI_FORMAT.BC6H_UF16: self.pixel_format = "BC6H" - tile = ("bcn", (0, 0) + self.size, data_start, (6, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (6, self.pixel_format)) self.mode = "RGB" elif dxgi_format == DXGI_FORMAT.BC6H_SF16: self.pixel_format = "BC6HS" - tile = ("bcn", (0, 0) + self.size, data_start, (6, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (6, self.pixel_format)) self.mode = "RGB" elif dxgi_format in (DXGI_FORMAT.BC7_TYPELESS, DXGI_FORMAT.BC7_UNORM): self.pixel_format = "BC7" - tile = ("bcn", (0, 0) + self.size, data_start, (7, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (7, self.pixel_format)) elif dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: self.pixel_format = "BC7" self.info["gamma"] = 1 / 2.2 - tile = ("bcn", (0, 0) + self.size, data_start, (7, self.pixel_format)) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (7, self.pixel_format)) elif dxgi_format in ( DXGI_FORMAT.R8G8B8A8_TYPELESS, DXGI_FORMAT.R8G8B8A8_UNORM, DXGI_FORMAT.R8G8B8A8_UNORM_SRGB, ): - tile = ("raw", (0, 0) + self.size, 0, ("RGBA", 0, 1)) + tile = Image.Tile("raw", (0, 0) + self.size, 0, ("RGBA", 0, 1)) if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB: self.info["gamma"] = 1 / 2.2 else: @@ -457,7 +457,7 @@ def _save(im, fp, filename): if im.mode == "RGBA": r, g, b, a = im.split() im = Image.merge("RGBA", (a, r, g, b)) - ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) + ImageFile._save(im, fp, [Image.Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) def _accept(prefix): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index a785292f8f8..15d368acf27 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -38,6 +38,7 @@ from collections.abc import Callable, MutableMapping from enum import IntEnum from pathlib import Path +from typing import NamedTuple, Tuple, List try: import defusedxml.ElementTree as ElementTree @@ -206,6 +207,14 @@ class Quantize(IntEnum): RLE = core.RLE FIXED = core.FIXED +Tile = NamedTuple('Tile', + [ + ('encoder_name', str), + ('extents', Tuple[int, int, int, int]), + ('offset', int), + ('tile_args', Tuple) + ] + ) # -------------------------------------------------------------------- # Registries @@ -692,6 +701,7 @@ def __getstate__(self): def __setstate__(self, state): Image.__init__(self) + self.tile: List[Tile] = [] info, mode, size, palette, data = state self.info = info self.mode = mode diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 8e4f7dfb2c8..d3ab1126cce 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -31,6 +31,7 @@ import itertools import struct import sys +from typing import List from . import Image from ._util import is_path @@ -521,13 +522,13 @@ def _save(im, fp, tile, bufsize=0): fp.flush() -def _encode_tile(im, fp, tile, bufsize, fh, exc=None): - for e, b, o, a in tile: - if o > 0: - fp.seek(o) - encoder = Image._getencoder(im.mode, e, a, im.encoderconfig) +def _encode_tile(im, fp, tile: List[Image.Tile], bufsize, fh, exc=None): + for encoder_name, extents, offset, tile_args in tile: + if offset > 0: + fp.seek(offset) + encoder = Image._getencoder(im.mode, encoder_name, tile_args, im.encoderconfig) try: - encoder.setimage(im.im, b) + encoder.setimage(im.im, extents) if encoder.pushes_fd: encoder.setfd(fp) errcode = encoder.encode_to_pyfd()[1] From 3f77a9e235ec4f3b3f323f474077e5334ad02768 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sat, 6 Aug 2022 23:22:38 +0300 Subject: [PATCH 03/58] Remove unused constants --- src/PIL/DdsImagePlugin.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index d211cc99411..0af43465572 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -61,16 +61,6 @@ class DDPF(IntFlag): LUMINANCE = 0x20000 -# DXT1 -DXT1_FOURCC = 0x31545844 - -# DXT3 -DXT3_FOURCC = 0x33545844 - -# DXT5 -DXT5_FOURCC = 0x35545844 - - # dxgiformat.h class DXGI_FORMAT(IntEnum): UNKNOWN = 0, From 19887374ae3f02f7e5d0a70ab97ebc41f1a70028 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 7 Aug 2022 00:01:57 +0300 Subject: [PATCH 04/58] Add support for single channel textures --- Tests/test_file_dds.py | 1 + src/PIL/DdsImagePlugin.py | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index cac4108a8f0..2df69e7c306 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -309,6 +309,7 @@ def test_save_unsupported_mode(tmp_path): ("LA", "Tests/images/uncompressed_la.png"), ("RGB", "Tests/images/hopper.png"), ("RGBA", "Tests/images/pil123rgba.png"), + ("L", "Tests/images/ATI1.dds"), ], ) def test_save(mode, test_file, tmp_path): diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 0af43465572..132b69091d9 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -321,12 +321,18 @@ def _open(self): elif pfflags & DDPF.RGB: # Texture contains uncompressed RGB data masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} - rawmode = "" - if bitcount == 32: - rawmode += masks[0xFF000000] - else: + if bitcount == 8: + self.mode = "L" + rawmode = "L" + elif bitcount == 24: self.mode = "RGB" - rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF] + rawmode = masks[0xFF0000] + masks[0xFF00] + masks[0xFF] + elif bitcount == 32: + self.mode = "RGBA" + rawmode = masks[0xFF000000] + masks[0xFF0000] + masks[0xFF00] + masks[0xFF] + else: + raise OSError(f'Unsupported bitcount {bitcount} for DDS texture') + self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] elif pfflags & DDPF.FOURCC: @@ -404,6 +410,8 @@ def load_seek(self, pos): def _save(im, fp, filename): + if im.mode not in ("RGB", "RGBA", "L"): + raise OSError(f"cannot write mode {im.mode} as DDS") if im.mode not in ("RGB", "RGBA", "L", "LA"): msg = f"cannot write mode {im.mode} as DDS" raise OSError(msg) From 3602e358182edb0bfc8fb307a1638ba6d51ef92f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 6 Aug 2022 21:05:58 +0000 Subject: [PATCH 05/58] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/DdsImagePlugin.py | 354 +++++++++++++++++++++----------------- src/PIL/Image.py | 19 +- src/PIL/ImageFile.py | 7 +- 3 files changed, 212 insertions(+), 168 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 132b69091d9..592de26e054 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -11,7 +11,7 @@ """ import struct -from enum import IntFlag, IntEnum +from enum import IntEnum, IntFlag from io import BytesIO from . import Image, ImageFile @@ -63,125 +63,125 @@ class DDPF(IntFlag): # dxgiformat.h class DXGI_FORMAT(IntEnum): - UNKNOWN = 0, - R32G32B32A32_TYPELESS = 1, - R32G32B32A32_FLOAT = 2, - R32G32B32A32_UINT = 3, - R32G32B32A32_SINT = 4, - R32G32B32_TYPELESS = 5, - R32G32B32_FLOAT = 6, - R32G32B32_UINT = 7, - R32G32B32_SINT = 8, - R16G16B16A16_TYPELESS = 9, - R16G16B16A16_FLOAT = 10, - R16G16B16A16_UNORM = 11, - R16G16B16A16_UINT = 12, - R16G16B16A16_SNORM = 13, - R16G16B16A16_SINT = 14, - R32G32_TYPELESS = 15, - R32G32_FLOAT = 16, - R32G32_UINT = 17, - R32G32_SINT = 18, - R32G8X24_TYPELESS = 19, - D32_FLOAT_S8X24_UINT = 20, - R32_FLOAT_X8X24_TYPELESS = 21, - X32_TYPELESS_G8X24_UINT = 22, - R10G10B10A2_TYPELESS = 23, - R10G10B10A2_UNORM = 24, - R10G10B10A2_UINT = 25, - R11G11B10_FLOAT = 26, - R8G8B8A8_TYPELESS = 27, - R8G8B8A8_UNORM = 28, - R8G8B8A8_UNORM_SRGB = 29, - R8G8B8A8_UINT = 30, - R8G8B8A8_SNORM = 31, - R8G8B8A8_SINT = 32, - R16G16_TYPELESS = 33, - R16G16_FLOAT = 34, - R16G16_UNORM = 35, - R16G16_UINT = 36, - R16G16_SNORM = 37, - R16G16_SINT = 38, - R32_TYPELESS = 39, - D32_FLOAT = 40, - R32_FLOAT = 41, - R32_UINT = 42, - R32_SINT = 43, - R24G8_TYPELESS = 44, - D24_UNORM_S8_UINT = 45, - R24_UNORM_X8_TYPELESS = 46, - X24_TYPELESS_G8_UINT = 47, - R8G8_TYPELESS = 48, - R8G8_UNORM = 49, - R8G8_UINT = 50, - R8G8_SNORM = 51, - R8G8_SINT = 52, - R16_TYPELESS = 53, - R16_FLOAT = 54, - D16_UNORM = 55, - R16_UNORM = 56, - R16_UINT = 57, - R16_SNORM = 58, - R16_SINT = 59, - R8_TYPELESS = 60, - R8_UNORM = 61, - R8_UINT = 62, - R8_SNORM = 63, - R8_SINT = 64, - A8_UNORM = 65, - R1_UNORM = 66, - R9G9B9E5_SHAREDEXP = 67, - R8G8_B8G8_UNORM = 68, - G8R8_G8B8_UNORM = 69, - BC1_TYPELESS = 70, - BC1_UNORM = 71, - BC1_UNORM_SRGB = 72, - BC2_TYPELESS = 73, - BC2_UNORM = 74, - BC2_UNORM_SRGB = 75, - BC3_TYPELESS = 76, - BC3_UNORM = 77, - BC3_UNORM_SRGB = 78, - BC4_TYPELESS = 79, - BC4_UNORM = 80, - BC4_SNORM = 81, - BC5_TYPELESS = 82, - BC5_UNORM = 83, - BC5_SNORM = 84, - B5G6R5_UNORM = 85, - B5G5R5A1_UNORM = 86, - B8G8R8A8_UNORM = 87, - B8G8R8X8_UNORM = 88, - R10G10B10_XR_BIAS_A2_UNORM = 89, - B8G8R8A8_TYPELESS = 90, - B8G8R8A8_UNORM_SRGB = 91, - B8G8R8X8_TYPELESS = 92, - B8G8R8X8_UNORM_SRGB = 93, - BC6H_TYPELESS = 94, - BC6H_UF16 = 95, - BC6H_SF16 = 96, - BC7_TYPELESS = 97, - BC7_UNORM = 98, - BC7_UNORM_SRGB = 99, - AYUV = 100, - Y410 = 101, - Y416 = 102, - NV12 = 103, - P010 = 104, - P016 = 105, - _420_OPAQUE = 106, - YUY2 = 107, - Y210 = 108, - Y216 = 109, - NV11 = 110, - AI44 = 111, - IA44 = 112, - P8 = 113, - A8P8 = 114, - B4G4R4A4_UNORM = 115, - P208 = 130, - V208 = 131, - V408 = 132, + UNKNOWN = (0,) + R32G32B32A32_TYPELESS = (1,) + R32G32B32A32_FLOAT = (2,) + R32G32B32A32_UINT = (3,) + R32G32B32A32_SINT = (4,) + R32G32B32_TYPELESS = (5,) + R32G32B32_FLOAT = (6,) + R32G32B32_UINT = (7,) + R32G32B32_SINT = (8,) + R16G16B16A16_TYPELESS = (9,) + R16G16B16A16_FLOAT = (10,) + R16G16B16A16_UNORM = (11,) + R16G16B16A16_UINT = (12,) + R16G16B16A16_SNORM = (13,) + R16G16B16A16_SINT = (14,) + R32G32_TYPELESS = (15,) + R32G32_FLOAT = (16,) + R32G32_UINT = (17,) + R32G32_SINT = (18,) + R32G8X24_TYPELESS = (19,) + D32_FLOAT_S8X24_UINT = (20,) + R32_FLOAT_X8X24_TYPELESS = (21,) + X32_TYPELESS_G8X24_UINT = (22,) + R10G10B10A2_TYPELESS = (23,) + R10G10B10A2_UNORM = (24,) + R10G10B10A2_UINT = (25,) + R11G11B10_FLOAT = (26,) + R8G8B8A8_TYPELESS = (27,) + R8G8B8A8_UNORM = (28,) + R8G8B8A8_UNORM_SRGB = (29,) + R8G8B8A8_UINT = (30,) + R8G8B8A8_SNORM = (31,) + R8G8B8A8_SINT = (32,) + R16G16_TYPELESS = (33,) + R16G16_FLOAT = (34,) + R16G16_UNORM = (35,) + R16G16_UINT = (36,) + R16G16_SNORM = (37,) + R16G16_SINT = (38,) + R32_TYPELESS = (39,) + D32_FLOAT = (40,) + R32_FLOAT = (41,) + R32_UINT = (42,) + R32_SINT = (43,) + R24G8_TYPELESS = (44,) + D24_UNORM_S8_UINT = (45,) + R24_UNORM_X8_TYPELESS = (46,) + X24_TYPELESS_G8_UINT = (47,) + R8G8_TYPELESS = (48,) + R8G8_UNORM = (49,) + R8G8_UINT = (50,) + R8G8_SNORM = (51,) + R8G8_SINT = (52,) + R16_TYPELESS = (53,) + R16_FLOAT = (54,) + D16_UNORM = (55,) + R16_UNORM = (56,) + R16_UINT = (57,) + R16_SNORM = (58,) + R16_SINT = (59,) + R8_TYPELESS = (60,) + R8_UNORM = (61,) + R8_UINT = (62,) + R8_SNORM = (63,) + R8_SINT = (64,) + A8_UNORM = (65,) + R1_UNORM = (66,) + R9G9B9E5_SHAREDEXP = (67,) + R8G8_B8G8_UNORM = (68,) + G8R8_G8B8_UNORM = (69,) + BC1_TYPELESS = (70,) + BC1_UNORM = (71,) + BC1_UNORM_SRGB = (72,) + BC2_TYPELESS = (73,) + BC2_UNORM = (74,) + BC2_UNORM_SRGB = (75,) + BC3_TYPELESS = (76,) + BC3_UNORM = (77,) + BC3_UNORM_SRGB = (78,) + BC4_TYPELESS = (79,) + BC4_UNORM = (80,) + BC4_SNORM = (81,) + BC5_TYPELESS = (82,) + BC5_UNORM = (83,) + BC5_SNORM = (84,) + B5G6R5_UNORM = (85,) + B5G5R5A1_UNORM = (86,) + B8G8R8A8_UNORM = (87,) + B8G8R8X8_UNORM = (88,) + R10G10B10_XR_BIAS_A2_UNORM = (89,) + B8G8R8A8_TYPELESS = (90,) + B8G8R8A8_UNORM_SRGB = (91,) + B8G8R8X8_TYPELESS = (92,) + B8G8R8X8_UNORM_SRGB = (93,) + BC6H_TYPELESS = (94,) + BC6H_UF16 = (95,) + BC6H_SF16 = (96,) + BC7_TYPELESS = (97,) + BC7_UNORM = (98,) + BC7_UNORM_SRGB = (99,) + AYUV = (100,) + Y410 = (101,) + Y416 = (102,) + NV12 = (103,) + P010 = (104,) + P016 = (105,) + _420_OPAQUE = (106,) + YUY2 = (107,) + Y210 = (108,) + Y216 = (109,) + NV11 = (110,) + AI44 = (111,) + IA44 = (112,) + P8 = (113,) + A8P8 = (114,) + B4G4R4A4_UNORM = (115,) + P208 = (130,) + V208 = (131,) + V408 = (132,) SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 133 SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 134 INVALID = -1 @@ -192,7 +192,7 @@ def _missing_(cls, value: object): def make_fourcc(name): - return struct.unpack('I', name.encode('ascii'))[0] + return struct.unpack("I", name.encode("ascii"))[0] class D3DFMT(IntEnum): @@ -252,20 +252,20 @@ class D3DFMT(IntEnum): A2B10G10R10_XR_BIAS = 119 BINARYBUFFER = 199 - UYVY = make_fourcc('UYVY') - R8G8_B8G8 = make_fourcc('RGBG') - YUY2 = make_fourcc('YUY2') - G8R8_G8B8 = make_fourcc('GRGB') - DXT1 = make_fourcc('DXT1') - DXT2 = make_fourcc('DXT2') - DXT3 = make_fourcc('DXT3') - DXT4 = make_fourcc('DXT4') - DXT5 = make_fourcc('DXT5') - DX10 = make_fourcc('DX10') - BC5S = make_fourcc('BC5S') - ATI1 = make_fourcc('ATI1') - ATI2 = make_fourcc('ATI2') - MULTI2_ARGB8 = make_fourcc('MET1') + UYVY = make_fourcc("UYVY") + R8G8_B8G8 = make_fourcc("RGBG") + YUY2 = make_fourcc("YUY2") + G8R8_G8B8 = make_fourcc("GRGB") + DXT1 = make_fourcc("DXT1") + DXT2 = make_fourcc("DXT2") + DXT3 = make_fourcc("DXT3") + DXT4 = make_fourcc("DXT4") + DXT5 = make_fourcc("DXT5") + DX10 = make_fourcc("DX10") + BC5S = make_fourcc("BC5S") + ATI1 = make_fourcc("ATI1") + ATI2 = make_fourcc("ATI2") + MULTI2_ARGB8 = make_fourcc("MET1") INVALID = -1 @classmethod @@ -305,9 +305,21 @@ def _open(self): fourcc = D3DFMT(fourcc_) masks = struct.unpack("<4I", header.read(16)) if flags & DDSD.CAPS: - (caps1_, caps2_, caps3, caps4, _,) = struct.unpack("<5I", header.read(20)) + ( + caps1_, + caps2_, + caps3, + caps4, + _, + ) = struct.unpack("<5I", header.read(20)) else: - (caps1_, caps2_, caps3, caps4, _,) = 0, 0, 0, 0, 0 + (caps1_, caps2_, caps3, caps4, _,) = ( + 0, + 0, + 0, + 0, + 0, + ) caps1 = DDSCAPS(caps1_) caps2 = DDSCAPS2(caps2_) if pfflags & DDPF.LUMINANCE: @@ -329,34 +341,47 @@ def _open(self): rawmode = masks[0xFF0000] + masks[0xFF00] + masks[0xFF] elif bitcount == 32: self.mode = "RGBA" - rawmode = masks[0xFF000000] + masks[0xFF0000] + masks[0xFF00] + masks[0xFF] + rawmode = ( + masks[0xFF000000] + masks[0xFF0000] + masks[0xFF00] + masks[0xFF] + ) else: - raise OSError(f'Unsupported bitcount {bitcount} for DDS texture') - + raise OSError(f"Unsupported bitcount {bitcount} for DDS texture") self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] elif pfflags & DDPF.FOURCC: data_start = header_size + 4 if fourcc == D3DFMT.DXT1: self.pixel_format = "DXT1" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (1, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (1, self.pixel_format) + ) elif fourcc == D3DFMT.DXT3: self.pixel_format = "DXT3" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (2, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (2, self.pixel_format) + ) elif fourcc == D3DFMT.DXT5: self.pixel_format = "DXT5" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (3, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (3, self.pixel_format) + ) elif fourcc == D3DFMT.ATI1: self.pixel_format = "BC4" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (4, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (4, self.pixel_format) + ) self.mode = "L" elif fourcc == D3DFMT.BC5S: self.pixel_format = "BC5S" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) + ) self.mode = "RGB" elif fourcc == D3DFMT.ATI2: self.pixel_format = "BC5" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) + ) self.mode = "RGB" elif fourcc == D3DFMT.DX10: data_start += 20 @@ -365,27 +390,39 @@ def _open(self): self.fp.read(16) if dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM): self.pixel_format = "BC5" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) + ) self.mode = "RGB" elif dxgi_format == DXGI_FORMAT.BC5_SNORM: self.pixel_format = "BC5S" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) + ) self.mode = "RGB" elif dxgi_format == DXGI_FORMAT.BC6H_UF16: self.pixel_format = "BC6H" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (6, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (6, self.pixel_format) + ) self.mode = "RGB" elif dxgi_format == DXGI_FORMAT.BC6H_SF16: self.pixel_format = "BC6HS" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (6, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (6, self.pixel_format) + ) self.mode = "RGB" elif dxgi_format in (DXGI_FORMAT.BC7_TYPELESS, DXGI_FORMAT.BC7_UNORM): self.pixel_format = "BC7" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (7, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (7, self.pixel_format) + ) elif dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: self.pixel_format = "BC7" self.info["gamma"] = 1 / 2.2 - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (7, self.pixel_format)) + tile = Image.Tile( + "bcn", (0, 0) + self.size, data_start, (7, self.pixel_format) + ) elif dxgi_format in ( DXGI_FORMAT.R8G8B8A8_TYPELESS, DXGI_FORMAT.R8G8B8A8_UNORM, @@ -403,15 +440,14 @@ def _open(self): self.tile = [tile] else: - raise NotImplementedError(f'Unknown pixel format flags {repr(pfflags)}') + msg = f"Unknown pixel format flags {repr(pfflags)}" + raise NotImplementedError(msg) def load_seek(self, pos): pass def _save(im, fp, filename): - if im.mode not in ("RGB", "RGBA", "L"): - raise OSError(f"cannot write mode {im.mode} as DDS") if im.mode not in ("RGB", "RGBA", "L", "LA"): msg = f"cannot write mode {im.mode} as DDS" raise OSError(msg) @@ -434,7 +470,9 @@ def _save(im, fp, filename): fp.write( o32(DDS_MAGIC) + o32(124) # header size - + o32(DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT) # flags + + o32( + DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT + ) # flags + o32(im.height) + o32(im.width) + o32((im.width * bitcount + 7) // 8) # pitch @@ -455,7 +493,9 @@ def _save(im, fp, filename): if im.mode == "RGBA": r, g, b, a = im.split() im = Image.merge("RGBA", (a, r, g, b)) - ImageFile._save(im, fp, [Image.Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) + ImageFile._save( + im, fp, [Image.Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))] + ) def _accept(prefix): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 15d368acf27..7919202f7ae 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -38,7 +38,7 @@ from collections.abc import Callable, MutableMapping from enum import IntEnum from pathlib import Path -from typing import NamedTuple, Tuple, List +from typing import List, NamedTuple, Tuple try: import defusedxml.ElementTree as ElementTree @@ -207,14 +207,15 @@ class Quantize(IntEnum): RLE = core.RLE FIXED = core.FIXED -Tile = NamedTuple('Tile', - [ - ('encoder_name', str), - ('extents', Tuple[int, int, int, int]), - ('offset', int), - ('tile_args', Tuple) - ] - ) +Tile = NamedTuple( + "Tile", + [ + ("encoder_name", str), + ("extents", Tuple[int, int, int, int]), + ("offset", int), + ("tile_args", Tuple), + ], +) # -------------------------------------------------------------------- # Registries diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index d3ab1126cce..4194a7c356e 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -536,7 +536,9 @@ def _encode_tile(im, fp, tile: List[Image.Tile], bufsize, fh, exc=None): if exc: # compress to Python file-compatible object while True: - errcode, data = encoder.encode(bufsize)[1:] + errcode, data = encoder.encode( + bufsize + )[1:] fp.write(data) if errcode: break @@ -544,7 +546,8 @@ def _encode_tile(im, fp, tile: List[Image.Tile], bufsize, fh, exc=None): # slight speedup: compress to real file object errcode = encoder.encode_to_file(fh, bufsize) if errcode < 0: - msg = f"encoder error {errcode} when writing image file" + msg = + f"encoder error {errcode} when writing image file" raise OSError(msg) from exc finally: encoder.cleanup() From 429dc2850e13236fc31842911403b1f8f78bb225 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 7 Aug 2022 00:11:08 +0300 Subject: [PATCH 06/58] Remove unnecessary tuple creation --- src/PIL/DdsImagePlugin.py | 238 +++++++++++++++++++------------------- 1 file changed, 119 insertions(+), 119 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 592de26e054..8175f182e25 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -63,125 +63,125 @@ class DDPF(IntFlag): # dxgiformat.h class DXGI_FORMAT(IntEnum): - UNKNOWN = (0,) - R32G32B32A32_TYPELESS = (1,) - R32G32B32A32_FLOAT = (2,) - R32G32B32A32_UINT = (3,) - R32G32B32A32_SINT = (4,) - R32G32B32_TYPELESS = (5,) - R32G32B32_FLOAT = (6,) - R32G32B32_UINT = (7,) - R32G32B32_SINT = (8,) - R16G16B16A16_TYPELESS = (9,) - R16G16B16A16_FLOAT = (10,) - R16G16B16A16_UNORM = (11,) - R16G16B16A16_UINT = (12,) - R16G16B16A16_SNORM = (13,) - R16G16B16A16_SINT = (14,) - R32G32_TYPELESS = (15,) - R32G32_FLOAT = (16,) - R32G32_UINT = (17,) - R32G32_SINT = (18,) - R32G8X24_TYPELESS = (19,) - D32_FLOAT_S8X24_UINT = (20,) - R32_FLOAT_X8X24_TYPELESS = (21,) - X32_TYPELESS_G8X24_UINT = (22,) - R10G10B10A2_TYPELESS = (23,) - R10G10B10A2_UNORM = (24,) - R10G10B10A2_UINT = (25,) - R11G11B10_FLOAT = (26,) - R8G8B8A8_TYPELESS = (27,) - R8G8B8A8_UNORM = (28,) - R8G8B8A8_UNORM_SRGB = (29,) - R8G8B8A8_UINT = (30,) - R8G8B8A8_SNORM = (31,) - R8G8B8A8_SINT = (32,) - R16G16_TYPELESS = (33,) - R16G16_FLOAT = (34,) - R16G16_UNORM = (35,) - R16G16_UINT = (36,) - R16G16_SNORM = (37,) - R16G16_SINT = (38,) - R32_TYPELESS = (39,) - D32_FLOAT = (40,) - R32_FLOAT = (41,) - R32_UINT = (42,) - R32_SINT = (43,) - R24G8_TYPELESS = (44,) - D24_UNORM_S8_UINT = (45,) - R24_UNORM_X8_TYPELESS = (46,) - X24_TYPELESS_G8_UINT = (47,) - R8G8_TYPELESS = (48,) - R8G8_UNORM = (49,) - R8G8_UINT = (50,) - R8G8_SNORM = (51,) - R8G8_SINT = (52,) - R16_TYPELESS = (53,) - R16_FLOAT = (54,) - D16_UNORM = (55,) - R16_UNORM = (56,) - R16_UINT = (57,) - R16_SNORM = (58,) - R16_SINT = (59,) - R8_TYPELESS = (60,) - R8_UNORM = (61,) - R8_UINT = (62,) - R8_SNORM = (63,) - R8_SINT = (64,) - A8_UNORM = (65,) - R1_UNORM = (66,) - R9G9B9E5_SHAREDEXP = (67,) - R8G8_B8G8_UNORM = (68,) - G8R8_G8B8_UNORM = (69,) - BC1_TYPELESS = (70,) - BC1_UNORM = (71,) - BC1_UNORM_SRGB = (72,) - BC2_TYPELESS = (73,) - BC2_UNORM = (74,) - BC2_UNORM_SRGB = (75,) - BC3_TYPELESS = (76,) - BC3_UNORM = (77,) - BC3_UNORM_SRGB = (78,) - BC4_TYPELESS = (79,) - BC4_UNORM = (80,) - BC4_SNORM = (81,) - BC5_TYPELESS = (82,) - BC5_UNORM = (83,) - BC5_SNORM = (84,) - B5G6R5_UNORM = (85,) - B5G5R5A1_UNORM = (86,) - B8G8R8A8_UNORM = (87,) - B8G8R8X8_UNORM = (88,) - R10G10B10_XR_BIAS_A2_UNORM = (89,) - B8G8R8A8_TYPELESS = (90,) - B8G8R8A8_UNORM_SRGB = (91,) - B8G8R8X8_TYPELESS = (92,) - B8G8R8X8_UNORM_SRGB = (93,) - BC6H_TYPELESS = (94,) - BC6H_UF16 = (95,) - BC6H_SF16 = (96,) - BC7_TYPELESS = (97,) - BC7_UNORM = (98,) - BC7_UNORM_SRGB = (99,) - AYUV = (100,) - Y410 = (101,) - Y416 = (102,) - NV12 = (103,) - P010 = (104,) - P016 = (105,) - _420_OPAQUE = (106,) - YUY2 = (107,) - Y210 = (108,) - Y216 = (109,) - NV11 = (110,) - AI44 = (111,) - IA44 = (112,) - P8 = (113,) - A8P8 = (114,) - B4G4R4A4_UNORM = (115,) - P208 = (130,) - V208 = (131,) - V408 = (132,) + UNKNOWN = 0 + R32G32B32A32_TYPELESS = 1 + R32G32B32A32_FLOAT = 2 + R32G32B32A32_UINT = 3 + R32G32B32A32_SINT = 4 + R32G32B32_TYPELESS = 5 + R32G32B32_FLOAT = 6 + R32G32B32_UINT = 7 + R32G32B32_SINT = 8 + R16G16B16A16_TYPELESS = 9 + R16G16B16A16_FLOAT = 10 + R16G16B16A16_UNORM = 11 + R16G16B16A16_UINT = 12 + R16G16B16A16_SNORM = 13 + R16G16B16A16_SINT = 14 + R32G32_TYPELESS = 15 + R32G32_FLOAT = 16 + R32G32_UINT = 17 + R32G32_SINT = 18 + R32G8X24_TYPELESS = 19 + D32_FLOAT_S8X24_UINT = 20 + R32_FLOAT_X8X24_TYPELESS = 21 + X32_TYPELESS_G8X24_UINT = 22 + R10G10B10A2_TYPELESS = 23 + R10G10B10A2_UNORM = 24 + R10G10B10A2_UINT = 25 + R11G11B10_FLOAT = 26 + R8G8B8A8_TYPELESS = 27 + R8G8B8A8_UNORM = 28 + R8G8B8A8_UNORM_SRGB = 29 + R8G8B8A8_UINT = 30 + R8G8B8A8_SNORM = 31 + R8G8B8A8_SINT = 32 + R16G16_TYPELESS = 33 + R16G16_FLOAT = 34 + R16G16_UNORM = 35 + R16G16_UINT = 36 + R16G16_SNORM = 37 + R16G16_SINT = 38 + R32_TYPELESS = 39 + D32_FLOAT = 40 + R32_FLOAT = 41 + R32_UINT = 42 + R32_SINT = 43 + R24G8_TYPELESS = 44 + D24_UNORM_S8_UINT = 45 + R24_UNORM_X8_TYPELESS = 46 + X24_TYPELESS_G8_UINT = 47 + R8G8_TYPELESS = 48 + R8G8_UNORM = 49 + R8G8_UINT = 50 + R8G8_SNORM = 51 + R8G8_SINT = 52 + R16_TYPELESS = 53 + R16_FLOAT = 54 + D16_UNORM = 55 + R16_UNORM = 56 + R16_UINT = 57 + R16_SNORM = 58 + R16_SINT = 59 + R8_TYPELESS = 60 + R8_UNORM = 61 + R8_UINT = 62 + R8_SNORM = 63 + R8_SINT = 64 + A8_UNORM = 65 + R1_UNORM = 66 + R9G9B9E5_SHAREDEXP = 67 + R8G8_B8G8_UNORM = 68 + G8R8_G8B8_UNORM = 69 + BC1_TYPELESS = 70 + BC1_UNORM = 71 + BC1_UNORM_SRGB = 72 + BC2_TYPELESS = 73 + BC2_UNORM = 74 + BC2_UNORM_SRGB = 75 + BC3_TYPELESS = 76 + BC3_UNORM = 77 + BC3_UNORM_SRGB = 78 + BC4_TYPELESS = 79 + BC4_UNORM = 80 + BC4_SNORM = 81 + BC5_TYPELESS = 82 + BC5_UNORM = 83 + BC5_SNORM = 84 + B5G6R5_UNORM = 85 + B5G5R5A1_UNORM = 86 + B8G8R8A8_UNORM = 87 + B8G8R8X8_UNORM = 88 + R10G10B10_XR_BIAS_A2_UNORM = 89 + B8G8R8A8_TYPELESS = 90 + B8G8R8A8_UNORM_SRGB = 91 + B8G8R8X8_TYPELESS = 92 + B8G8R8X8_UNORM_SRGB = 93 + BC6H_TYPELESS = 94 + BC6H_UF16 = 95 + BC6H_SF16 = 96 + BC7_TYPELESS = 97 + BC7_UNORM = 98 + BC7_UNORM_SRGB = 99 + AYUV = 100 + Y410 = 101 + Y416 = 102 + NV12 = 103 + P010 = 104 + P016 = 105 + _420_OPAQUE = 106 + YUY2 = 107 + Y210 = 108 + Y216 = 109 + NV11 = 110 + AI44 = 111 + IA44 = 112 + P8 = 113 + A8P8 = 114 + B4G4R4A4_UNORM = 115 + P208 = 130 + V208 = 131 + V408 = 132 SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 133 SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 134 INVALID = -1 From 8e8a67ef3eb5e3a123fc8ce9dcb957c6b95d633f Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Sun, 7 Aug 2022 00:51:40 +0300 Subject: [PATCH 07/58] Fix incorect file name --- Tests/test_file_dds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 2df69e7c306..ec8434d7572 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -309,7 +309,7 @@ def test_save_unsupported_mode(tmp_path): ("LA", "Tests/images/uncompressed_la.png"), ("RGB", "Tests/images/hopper.png"), ("RGBA", "Tests/images/pil123rgba.png"), - ("L", "Tests/images/ATI1.dds"), + ("L", "Tests/images/ati1.dds"), ], ) def test_save(mode, test_file, tmp_path): From de8b89c82363b5ba4385c611b2ff1fa9d0e5552d Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Mon, 8 Aug 2022 02:21:58 +0300 Subject: [PATCH 08/58] Add support for writing LA dds textures --- Tests/images/l.dds | Bin 0 -> 16512 bytes Tests/images/l.png | Bin 0 -> 861 bytes Tests/images/rgb.dds | Bin 0 -> 49280 bytes Tests/images/rgb.png | Bin 0 -> 862 bytes Tests/images/rgba.dds | Bin 0 -> 65664 bytes Tests/images/rgba.png | Bin 0 -> 1302 bytes Tests/test_file_dds.py | 16 ++++ src/PIL/DdsImagePlugin.py | 188 ++++++++++++++++---------------------- 8 files changed, 94 insertions(+), 110 deletions(-) create mode 100644 Tests/images/l.dds create mode 100644 Tests/images/l.png create mode 100644 Tests/images/rgb.dds create mode 100644 Tests/images/rgb.png create mode 100644 Tests/images/rgba.dds create mode 100644 Tests/images/rgba.png diff --git a/Tests/images/l.dds b/Tests/images/l.dds new file mode 100644 index 0000000000000000000000000000000000000000..b82282587ec30665fceafced278f1c59b3402ed1 GIT binary patch literal 16512 zcmeH}-EHGA5QLRH-P>I{2-3o};0}Uxa}TK}#ia!we>Mbmxid@IFa*O(Adcw~@eMx; zf=;LR*MHl#{rikdie4wCtD@J5$rZg$Odd=pyeTl@O@Rr&tAImS3LLsp;L!Id0QjK*;D-W$ zUsiB1AL2y-+`b5a+g}qv@T~yBw*myeRN!Df#TNl|`$YiV{(^u9=Lg$A2l~GQ{ow&5 zpBqU3+`zrxe;YskeE#v{zyA5p51_m@(gG!?cVO_^Sz~$wl>F9wR-n}<1zJu7v^@NP z24p2HAUP}$lKTZm^U(>6`arZRRKVo%R5fre zR}Gw8HE{BUpFaA4SDyNS`QJWZP6L6%3Ic}}1b$%!MXwXnRnhCjXRluPu1rA*)aOis!0Q^t@@IwK>FDp2h5Ah-ZZeIkz?XL+S_*Q`6TLFS!DsV8L z;)?*f{UU&Fe?h>5^Mmbw1o}^b{_p^j&kdx1Zs6YUzl|S%KL7afU;q5)2T)!cX@Qc{ zJ1}_dtTDX-N`7lTE70nb0xc&3S{{Bt1F{kpkQ^2W$^C+(`RD{jeIQ`uK)}f3FOCAW z$Z4Q*r-91D$yC6smsv0<#s|HT48aVmFPj7v|D_{D6`BNV-r-8s>1%bl~ z0>7|=qSuM(s_1oMaz(EblLylYZwgF!Q((gHD&WwS0*9^?IP^UV0DdR{_@MycmlYh$ zhjsv0<#s|HT4 t8aVmFPf`J1DHUKY6=0r08b||aAPuB}G>`_;KpIE`X&?=xfi!T4f&Vw%wVD6` literal 0 HcmV?d00001 diff --git a/Tests/images/l.png b/Tests/images/l.png new file mode 100644 index 0000000000000000000000000000000000000000..9d22a26a446d3dbdfd8f9c931ea466f6c6424e90 GIT binary patch literal 861 zcmeAS@N?(olHy`uVBq!ia0vp^4Is<`Bp9BB+KDqTFspdFIEGZrc^h?ls*<5Vur)JB zqlg-7P(pKyh(}`Z41qR*8_gT%*K1u(y=E67zCX-RIr5$RbYFRmk9~(3o90{0iTo?q zZokIPVc}P`n#F#$cTRI}Z`b*cHdxb-Qkj7%xN9XC&Z5~v}sUc zQF_LyDDi8i`mdSklfSkId~vT6XS8VuNn~8YHGhS2Q^L#RQrG)UyL@3X&}x&GB>;Tib{;+mb; z9E?oS*}aKX$Uxa5;eK2CjmcMXGaH^hIH47DKd5laQEeT@eua-ZO-D9&R()gjOuir% zvW=}bGBx``)z5EfnVi1_vX=2LOsg_|ey?EeT|36sB|Uh!dQU<13AVi4r5g)!U-!1I-}Y|HguFw3`xCAHw!QqqenxjbTuMSUDiOXag;?62vFzD;OU-N3|GNKs$H|s>F&hgWVD=Cdb6BTa*mH8 zSvtelvqpRLzjllcxGA-Tb?VxK$@jOLdwX91dKc*H#c{mLBht%bs(#FW&Mb`zE`Q|r X-moxli&n>DP`>eW^>bP0l+XkK*RXjv literal 0 HcmV?d00001 diff --git a/Tests/images/rgb.dds b/Tests/images/rgb.dds new file mode 100644 index 0000000000000000000000000000000000000000..ad0e630b479f580abc8fb8237c8c460918aa008e GIT binary patch literal 49280 zcmeI1U3L>O3`WBW;E~5Jg@^8d?eM_Pi_>%*WhC2@EX~B>%PA)wjddkm=LhKf`|r0u zm&@h*kIUt6dH($Ktt=dmx03VgEAQv$^T*|K`?*C+4fms>=)z57iIiH`Y?nJwk#ymv z9hgY_?X0~Wb&&Mdlj6N?sq*Oj%C(C7wMmJj+nxw-X~nabj%`}&ikXmf^Hbn;O*nVa zY2E8~Z8wr8o`!E1j=4jJoLox{O(JRf33#bwaEwkkxt2PcMAGyV@KVXh7#%NU{n%Ux zl0pu}KNgF>qJsslw?+ez6m<}OE0g$4rwUwe9S0&Q>LC19ChM7w6u#d62_6o<0n_nW z(g`<7%|U{ayCuB&z3vMnUfZWv>8u^AcH5h% z{CN4t9)W#ymC@=)wcFlAOf=>_iRoUyVGZ5a3nJ%)Ddvt}57 zWZHW&3=IE|viDc1@~`mZ0=XYPx9<1OBCnJ6!2jT|;D7uM8YrB)4gc^z^Bpm^H$%tg z|LFY*{{usV+q3>=^EK~|sSo&Za;l+HUNfS5&viU^vLR*&-GtU zDCr35*=yeDjy;v%6X@X9lzV~P75vL5m}~uk9)1K5^n@?T9bXn$U&)TezCikQ;1P(u zIvli8M1JHE$OUqv@P9wST+>Z@$PqN@L7ze${#{_}oui5O3LJ6IeG1CvG!Dc)O1}RT zv<1@lIgY^8lc={JH1R!0U@LIMZsjSsTj>k0cinvLDfkN@$ zQKA+eq$#E~M~M*=lWrDiJV;ZXWX+o#MogU#X?(*|@lDNGH}lz*5oIAIjcRT6HnobMFWF;Om{@=%1z^mr|`w1Q9(ZAWMtRa zB#oyZ%-@xZz2wt{L~4^EG!8k8UkfEI_-G-K+HDApLk{EDLKzD_S74+x97yA+1Na%v zDU%Ns7&*XCV7DAaMJ26y(ZC4&_fIWQ^{b-#0w?djs_jL?FY`0D)5Xo0ufmXtlL&R32(v(2H%RBm;?X;}AWrKSIt?;hNmC?13r(%HW{ zzcU(c!2k@v01UtY48Q;kzyJ)u01UtY48Q;kzyJ)u01UtY48Q;kzyJ)u01UtY48Q;k azyJ)u01UtY48Q;kzyJ)u01Uvuyn%m65T+~u literal 0 HcmV?d00001 diff --git a/Tests/images/rgb.png b/Tests/images/rgb.png new file mode 100644 index 0000000000000000000000000000000000000000..d0de36d82a9d9b082ec3fd255fc42886698c2649 GIT binary patch literal 862 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^U{>{XaSW-L^LF;dpfv^ptmZ%e z*B@gKbQCh1OPc~>jD zxZRUqi&<5!Jo6X7W>+5ak4dX8T`%&`UAI&54yRW#he$7YeKOv`@3$Ds|DO2_TNaw< z{N%P+u`et7`|K&KJ_X!d%l6H2%wMkG$^ByibCY62!m7Q;FNuHn^|B#>LqURxqm2P1 z$e}PJtNzp6eGS+B*;$+!4qlnQ`S_`q-jA55G^>NSrIU+aF*P@3_$rorfYoA2)R zdS;vN9sOR@uQ zero={vDNVP)GyDN!^DgVuiWb7lLz|m;oBGLRg8x}Gp-HRJoWv1@$c!C*BO(eGk*GP z`aQq+^Q@15Y7X4_{d?zMEvwIr-tx-F`cJ(RFPh08pz|nAJN5f%%k%6V+Z@hMzWctx zUGSjHGv*~5<8yERRonNBndfWYxi@w4z{pyupMK@`zkhXk`$erl(Y~zZ&(G=HukWXV zJqYnQI1GGB?SCJu=a&TqMsWN&J?-BhtHXfF0AyaE{M?&=?f5=3UaAU@F5Axu^z5AH z%q|x2+PFOA>#JTE3cgko-9Nk_A9MutdWXZKIf$h9}xaC2z;Q@GpCXd~g$#Yfqgp zMU-n#ohUz#{qK>19*$^R5y185`X0V_ZAJN4=8y73>MkJMakIRBDLomEYOaOUE9yvzHuUuzApVu|!a{?#%R z^cLY}RYU$k(4Vqa9Kn7(*ZK>svJOY?$-nVk-UTn~&kB*;i#GX_e_RH@b-Vffe`kLV z_qeoXlzT98AICl`ck;4sPyRp1pX+~I26+ERuKig%j(t&%%s?7LCRyx$sd zA-~8ej|>P?^~iuIKR*M4`~sKAfA(`=0mwH)n3;ha>3n(S8laM&w+1NW7iwae17KQ~ zIe_HtasbFLQeu&TfvH$zU`T#E0|W94l_EJX96pi*Q~CZJ7|Jiz)nj}7*V;S=nv(cW zYxB@gUx9yVZY~4x7isa9&RdSM#alXWso&)N&a)u#)m|InAo0~+8_|cjpEEHufVg5U zUrjq?7FxcVc8I=`KcW5oUt!k7aqmPkK&##Stmes;$rxEIYB zV`%duiC^%L^kKj+LJ`!7Y9oA4cASD)nn zAH(ji(nAiP~Kluk{fa`zzZ#i7nhCufDa{>92e|QE0p8G}8T|JC_tFQV8N4zqdJ{TI!{e)hAz z?*AaX&Vfe;q}kStX1$m6UDg3{0I?Uz-{G@hEaF@BnHjhhPYq_>_qkt<{?s)^zlk!21LebXF31BfrO$iT=fEHW^nZ<2wb0jyDs>BTE3cgpuUQ4(!Xto=ttFPc%p4IbJFlc+bH#Kc_w|RjYdDR z4a&8rPMROewWsbzAL5$y)pe47^tB$2Xj^HHJsi=tH~MOhNgrw}=|^5ec_MXJ^F(lC((kUX z^&{US?5t|?yb*R*HBbF+c1ho)CX##6wql9&^Yp*@`&=u|t$xUVzyF%Q&qtW{)R2F< z3uE>@tR}S$yZ6(4vbM=OKGIJN`8U-`OncT7@(&CE`Sbn{&H(R!-v5yq;PYSjdme|i zA(DO8CV%n|%|PJtze_vZjpK|}`X z>_YL>N(O;p)A_F7yu*krm z{%{6H^jE1xa$q@jBnQU&-8rz-U#TxC3m)T?vf!lO%z}qLHRu2xpaXP(4$uKQKnLgm z9iRhrfDX_BIzR{L03DzMbbt=f0Xjej=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm m9iRhrfDX_BIzR{L03DzMbbt=f0Xjej=l~s{19ag1Iq(x4GQ@5G literal 0 HcmV?d00001 diff --git a/Tests/images/rgba.png b/Tests/images/rgba.png new file mode 100644 index 0000000000000000000000000000000000000000..33b41a547265ca54a15197925df3adb7cd06ba71 GIT binary patch literal 1302 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV7cq*;uumf=k46P6Yp5?v^b`2 z(6nH+7PS^@e-QB|mEZVdQm(&3V(BwM`sVSKUc( zU2*1$O=ZLVH|eeWR=f`Wd$K-k-^$3fojkC^KXl^iV znEG?gJP_WhX*i1^+@VNJ>G#X{nlbUfK{}(ja&-)ef zYW3=WZw2z=`4kx^HM4o}Nhw%nG(1@V=A29V)V99;r1ZXbjC20myPc@*{^Z#yssv|{OvCWGewv(u1>znvZimUrr{a& zL!uMr#WH6sKd@n685hs%Z2$B*FV&@HDE8r!S55-~T!CRz+Q? z!F;a1-VGkx%Qu-%m^z;?iLL z@>Bek#|-^Qcjh$vhMteP2bnV!QfodlJT|#tq7x-$@{Re$Okk)l`^o+)+dq4Y$${{L zCo6C9FR=dWEc^DQ{EGK-*pVPAz@!{eZcp6 zwv_S`1|e|Rimd-pZ_)5tTzP62P|Hj+woIVG@c1RuKl`n@qhM$ z^Z%-~m+ttdUvaL;hS6%-=A*2$7p7Oa$FGsBT2}t$u=(K%`vWZ>ttWE#8YI^^*Y6Tc z>&bu7p4;-Tj)C#>`YRI+{(H%CepY>*ap4}nLP|~W2L~w+$@4GxFFRb`kde9JKL3J@ ze-nSrdM3Yez3tkIml)z%)t~>ZV|e;f{$+;$e~;r?R?15c+^%C#{%mQh4aC{Oi%b~T zH9WcVjakC~|EX2kwtxA5>3^@^<&)eZ;=lw7;W^FmweHJ*`yV*Zq-yEG_f8h8{eqiZE1bDFjf&7aXchi$yFZ~25_jL7h JS?83{1OPRAV5k59 literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index ec8434d7572..88032e4aea3 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -320,3 +320,19 @@ def test_save(mode, test_file, tmp_path): with Image.open(out) as reloaded: assert_image_equal(im, reloaded) + + +@pytest.mark.parametrize( + ("mode", "expected_file", "input_file"), + [ + ("L", "Tests/images/l.png", "Tests/images/l.dds"), + ("LA", "Tests/images/la.png", "Tests/images/la.dds"), + ("RGB", "Tests/images/rgb.png", "Tests/images/rgb.DDS"), + ("RGBA", "Tests/images/rgba.png", "Tests/images/rgba.DDS"), + ], +) +def test_open(mode, expected_file, input_file): + with Image.open(input_file) as im: + assert im.mode == mode + with Image.open(expected_file) as im2: + assert_image_equal(im, im2) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 8175f182e25..b1a296f59ec 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -277,6 +277,7 @@ class DdsImageFile(ImageFile.ImageFile): format = "DDS" format_description = "DirectDraw Surface" + # fmt: off def _open(self): if not _accept(self.fp.read(4)): msg = "not a DDS file" @@ -294,7 +295,6 @@ def _open(self): flags_, height, width = struct.unpack("<3I", header.read(12)) flags = DDSD(flags_) self._size = (width, height) - self.mode = "RGBA" pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) struct.unpack("<11I", header.read(44)) # reserved @@ -305,21 +305,9 @@ def _open(self): fourcc = D3DFMT(fourcc_) masks = struct.unpack("<4I", header.read(16)) if flags & DDSD.CAPS: - ( - caps1_, - caps2_, - caps3, - caps4, - _, - ) = struct.unpack("<5I", header.read(20)) + (caps1_, caps2_, caps3, caps4, _,) = struct.unpack("<5I", header.read(20)) else: - (caps1_, caps2_, caps3, caps4, _,) = ( - 0, - 0, - 0, - 0, - 0, - ) + (caps1_, caps2_, caps3, caps4, _,) = (0, 0, 0, 0, 0,) caps1 = DDSCAPS(caps1_) caps2 = DDSCAPS2(caps2_) if pfflags & DDPF.LUMINANCE: @@ -333,101 +321,87 @@ def _open(self): elif pfflags & DDPF.RGB: # Texture contains uncompressed RGB data masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} - if bitcount == 8: - self.mode = "L" - rawmode = "L" - elif bitcount == 24: + if bitcount == 24: + rawmode = masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF] self.mode = "RGB" - rawmode = masks[0xFF0000] + masks[0xFF00] + masks[0xFF] - elif bitcount == 32: + self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: self.mode = "RGBA" - rawmode = ( - masks[0xFF000000] + masks[0xFF0000] + masks[0xFF00] + masks[0xFF] - ) + rawmode = (masks[0xFF000000] + masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF]) + self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] else: - raise OSError(f"Unsupported bitcount {bitcount} for DDS texture") - - self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + raise OSError(f"Unsupported bitcount {bitcount} for {pfflags} DDS texture") + elif pfflags & DDPF.LUMINANCE: + if bitcount == 8: + self.mode = "L" + self.tile = [("raw", (0, 0) + self.size, 0, ("L", 0, 1))] + elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: + self.mode = "LA" + self.tile = [("raw", (0, 0) + self.size, 0, ("LA", 0, 1))] + else: + raise OSError(f"Unsupported bitcount {bitcount} for {pfflags} DDS texture") elif pfflags & DDPF.FOURCC: data_start = header_size + 4 if fourcc == D3DFMT.DXT1: + self.mode = "RGBA" self.pixel_format = "DXT1" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (1, self.pixel_format) - ) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (1, self.pixel_format)) elif fourcc == D3DFMT.DXT3: + self.mode = "RGBA" self.pixel_format = "DXT3" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (2, self.pixel_format) - ) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (2, self.pixel_format)) elif fourcc == D3DFMT.DXT5: + self.mode = "RGBA" self.pixel_format = "DXT5" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (3, self.pixel_format) - ) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (3, self.pixel_format)) elif fourcc == D3DFMT.ATI1: - self.pixel_format = "BC4" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (4, self.pixel_format) - ) self.mode = "L" + self.pixel_format = "BC4" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (4, self.pixel_format)) elif fourcc == D3DFMT.BC5S: - self.pixel_format = "BC5S" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) - ) self.mode = "RGB" + self.pixel_format = "BC5S" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) elif fourcc == D3DFMT.ATI2: - self.pixel_format = "BC5" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) - ) self.mode = "RGB" + self.pixel_format = "BC5" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) elif fourcc == D3DFMT.DX10: data_start += 20 # ignoring flags which pertain to volume textures and cubemaps (dxgi_format,) = struct.unpack(" Date: Sun, 7 Aug 2022 23:22:49 +0000 Subject: [PATCH 09/58] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/DdsImagePlugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index b1a296f59ec..4c228bca13d 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -466,6 +466,7 @@ def _save(im, fp, filename): # fmt: on + def _accept(prefix): return prefix[:4] == b"DDS " From 34760736a51bd82f023fd398a6d075c62169fc1f Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Mon, 8 Aug 2022 02:24:55 +0300 Subject: [PATCH 10/58] Add missing LA test textures --- Tests/images/la.dds | Bin 0 -> 32896 bytes Tests/images/la.png | Bin 0 -> 1060 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/la.dds create mode 100644 Tests/images/la.png diff --git a/Tests/images/la.dds b/Tests/images/la.dds new file mode 100644 index 0000000000000000000000000000000000000000..30bf93576fd17f80397a1a016c3ee2306e4bf28a GIT binary patch literal 32896 zcmeI4;f>T#420j&0MG%V1zNZc5Z&;DdMJ(-j@^xtFtTUtJV%OdaU|H-`0+4X(6pEBpi3mQLUN zhAVA5p%B0!$UPjuA;|dw9Djv6iQ(a(_u$>I1Ojy6clnLvvWhAUCVm}G%is+ZHu896>c61SC68$csOrpOQNl+^8 zJtZg=cfSOs;_k2AQNU1G`clA9So{<)6l&b7JH(^~&%jUNQoik;evw#x!ROi zq3`pbTr({T)*H&H70aUD|CJNy-#_6Imjze<{;7TgzW@DKCa}DDa=}tu{POEfinTX? zb<;2RP!+ZuVEt!g0Fef~lfiTo-0z~AB=^@93cU%shYJiz?KiL{q5Ws5 z8>+dGpL4Ya?ey1*vMd$_D29ehEs&-Cw(-fT6JTrGTNZ_$gp0 z)VNo7h)D~cfuF*qgfYS;%Yre&CCh?!hx4B)Oj^|Za=oE=_5SH%lR^j>6v)G!66a6; z-I>IsWdU9&rx0aP^H(N77n>Bq1qTIkwJEVe-{(KMW?B}kH^rdg<&vqm`|Ee% zs!aj*l*il^(N7_%fc|P^@WKRkbP;6|{S=c5=&wg1FcjE+64r>;PeEP5`p?J!A`N&a zgXt!?-$gY^?yoBpdJ}XH7Z{S-Z(vPA`_E7}RCAMiI^aT(^Bd$Xk@KHZ^%bt;&-@J6 zasLZk$NlpT0EZy=OaO-<=Lc{Ia{fvc;JSf+I^ep2_5-dPXn#$0flZS4aDh#d`(0p@ z<>kY-L_fHp_6hgqDpmg{j=J}Ih@nI|F6h(W=Tw17T|?)^H(RZZgTdMt~Mp~@1O99%Yv(a|5Segy#M`ICa}D7a=}tu z{PW)cv+q3mQZKq&^c?)NCt#nVTrw4R|J(`K{q;L{y)ubCDI^upe|!SyuSOEp6|p15 zqyqY{=0Exl;J^MJQBYT~{$mrc{xdS}qD_)_a#2l^`*Snk{<>mdO+xoDuqL7X@dUL0 z4AmfSiQLl-@|MW?`3!RYbE-Dz$IbC)ZqSdL<9?jNaeq5{0zHAAKu@42&=cqh^aOeW tJ%OG;PoO8z6X*%_1bPBJfu2B5peN81=n3=$dICLxow)~&Z0 zH5EtM_}-^3Hu{@?P>lS=P97Ps&2Y`uaavk;f+X z|NQ$jeEDRv|F65_YwNN;r`k*j^g*GU zZrQreW}1D!B^c{>PdvO!Q}DcywYc$d-YXYC%pI{Ec)v#0Q1dpzO) z>}bXbHv4f(YB%FVzfuC*mB;y3?5j z1Y_6l`*Zr}^!K%%U%v1;Fm4i`z{@pZZ$nN1!{0UQ-Rxhl{Qn3%@>4BHFG zvO)N_WU1bT6BB&dj~$)VdmKdUP~ty6OIP9JK^?b8&5=DWQEzT4__ZJBudVs8`g7#( zEAQX`@v{F_RsA^p{{34ulKk8O-p@~*f)PiiFVsE|QIi7_fuGYeL8Um$eIo#hy0rW-j^kyJGGiySUreHRK;G{qW88_J1>XAU=NS zyafLm7qg!>=auF^k6h|9g(3RDz4}GE*KdJ{%kKNxA2hsSP1#&w#xU{!KZg8$-&HSO zwq{@j1*3+;H>ORFj9)hIlX{<mc)r>dSOl$mp?&q7^EYrKsANhAsLdGHG zJ20rK7CedkEnz6J*YW@;s5BYRZ}jM#$(_tSC4Y-t#^bFZ{I|zSJfrVe%H_twjVX^e z0tw}HCk}5+m~ShZQl<%b`g*RTKc>bd*xEBgAN@LH9B?8Cq8^B=a` z{7TfFtE}w|A`btsoASe`WPg^sa;d+23pTn#+UJ zi>uD)Er=RDdV4Fvzpy&-gjnM+dDD|ps Date: Wed, 10 Aug 2022 03:22:39 +0300 Subject: [PATCH 11/58] Fix file extensions in tests not matching real names --- Tests/test_file_dds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 88032e4aea3..7f85a925619 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -327,8 +327,8 @@ def test_save(mode, test_file, tmp_path): [ ("L", "Tests/images/l.png", "Tests/images/l.dds"), ("LA", "Tests/images/la.png", "Tests/images/la.dds"), - ("RGB", "Tests/images/rgb.png", "Tests/images/rgb.DDS"), - ("RGBA", "Tests/images/rgba.png", "Tests/images/rgba.DDS"), + ("RGB", "Tests/images/rgb.png", "Tests/images/rgb.dds"), + ("RGBA", "Tests/images/rgba.png", "Tests/images/rgba.dds"), ], ) def test_open(mode, expected_file, input_file): From 7c25e0bbdae1dcd36507423f128b60f259d0a977 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Wed, 24 Aug 2022 21:18:00 +0300 Subject: [PATCH 12/58] Small refactor --- src/PIL/DdsImagePlugin.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 4c228bca13d..2744045b17c 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -308,17 +308,9 @@ def _open(self): (caps1_, caps2_, caps3, caps4, _,) = struct.unpack("<5I", header.read(20)) else: (caps1_, caps2_, caps3, caps4, _,) = (0, 0, 0, 0, 0,) - caps1 = DDSCAPS(caps1_) - caps2 = DDSCAPS2(caps2_) - if pfflags & DDPF.LUMINANCE: - # Texture contains uncompressed L or LA data - if pfflags & DDPF.ALPHAPIXELS: - self.mode = "LA" - else: - self.mode = "L" - - self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))] - elif pfflags & DDPF.RGB: + _ = DDSCAPS(caps1_) + _ = DDSCAPS2(caps2_) + if pfflags & DDPF.RGB: # Texture contains uncompressed RGB data masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} if bitcount == 24: @@ -327,7 +319,10 @@ def _open(self): self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: self.mode = "RGBA" - rawmode = (masks[0xFF000000] + masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF]) + rawmode = (masks[0xFF000000] + + masks[0x00FF0000] + + masks[0x0000FF00] + + masks[0x000000FF]) self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] else: raise OSError(f"Unsupported bitcount {bitcount} for {pfflags} DDS texture") @@ -449,12 +444,14 @@ def _save(im, fp, filename): flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT + stride = (im.width * bit_count + 7) // 8 fp.write( o32(DDS_MAGIC) # header size, flags, height, width, pith, depth, mipmaps - + struct.pack(" Date: Thu, 25 Aug 2022 15:45:48 +0300 Subject: [PATCH 13/58] Small refactor --- Tests/helper.py | 36 +++++++++++++++++++-------- src/PIL/DdsImagePlugin.py | 51 ++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 69246bfcf45..bd75c32336c 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -9,30 +9,33 @@ import sysconfig import tempfile from io import BytesIO +from typing import List import pytest from packaging.version import parse as parse_version -from PIL import Image, ImageMath, features +from PIL import Image, ImageMath, features, ImageChops logger = logging.getLogger(__name__) - HAS_UPLOADER = False if os.environ.get("SHOW_ERRORS"): # local img.show for errors. HAS_UPLOADER = True + class test_image_results: @staticmethod def upload(a, b): - a.show() - b.show() + diff = ImageChops.difference(a.convert("RGB"), b.convert("RGB")) + c = concat_h([a, b, diff]) + c.show() elif "GITHUB_ACTIONS" in os.environ: HAS_UPLOADER = True + class test_image_results: @staticmethod def upload(a, b): @@ -52,6 +55,19 @@ def upload(a, b): pass +def concat_h(images: List[Image.Image]): + new_size = images[0].size + for image in images[1:]: + assert image.height == new_size[1] + new_size = (new_size[0] + image.width, new_size[1]) + dst = Image.new('RGBA', new_size) + x_offset = 0 + for image in images: + dst.paste(image, (x_offset, 0)) + x_offset += image.width + return dst + + def convert_to_comparable(a, b): new_a, new_b = a, b if a.mode == "P": @@ -75,12 +91,12 @@ def assert_deep_equal(a, b, msg=None): def assert_image(im, mode, size, msg=None): if mode is not None: assert im.mode == mode, ( - msg or f"got mode {repr(im.mode)}, expected {repr(mode)}" + msg or f"got mode {repr(im.mode)}, expected {repr(mode)}" ) if size is not None: assert im.size == size, ( - msg or f"got size {repr(im.size)}, expected {repr(size)}" + msg or f"got size {repr(im.size)}, expected {repr(size)}" ) @@ -119,8 +135,8 @@ def assert_image_similar(a, b, epsilon, msg=None): ave_diff = diff / (a.size[0] * a.size[1]) try: assert epsilon >= ave_diff, ( - (msg or "") - + f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}" + (msg or "") + + f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}" ) except Exception as e: if HAS_UPLOADER: @@ -179,8 +195,8 @@ def mark_if_feature_version(mark, feature, version_blacklist, reason=None): version_required = parse_version(version_blacklist) version_available = parse_version(features.version(feature)) if ( - version_available.major == version_required.major - and version_available.minor == version_required.minor + version_available.major == version_required.major + and version_available.minor == version_required.minor ): return mark(reason=reason) return pytest.mark.pil_noop_mark() diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 2744045b17c..6e03c7d5722 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -9,7 +9,7 @@ Full text of the CC0 license: https://creativecommons.org/publicdomain/zero/1.0/ """ - +import io import struct from enum import IntEnum, IntFlag from io import BytesIO @@ -305,99 +305,96 @@ def _open(self): fourcc = D3DFMT(fourcc_) masks = struct.unpack("<4I", header.read(16)) if flags & DDSD.CAPS: - (caps1_, caps2_, caps3, caps4, _,) = struct.unpack("<5I", header.read(20)) - else: - (caps1_, caps2_, caps3, caps4, _,) = (0, 0, 0, 0, 0,) - _ = DDSCAPS(caps1_) - _ = DDSCAPS2(caps2_) + header.seek(20, io.SEEK_CUR) + extents = (0, 0) + self.size if pfflags & DDPF.RGB: # Texture contains uncompressed RGB data masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} if bitcount == 24: rawmode = masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF] self.mode = "RGB" - self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: self.mode = "RGBA" rawmode = (masks[0xFF000000] + masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF]) - self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] else: - raise OSError(f"Unsupported bitcount {bitcount} for {pfflags} DDS texture") + raise OSError(f"Unsupported bitcount {bitcount} for {pfflags}") elif pfflags & DDPF.LUMINANCE: if bitcount == 8: self.mode = "L" - self.tile = [("raw", (0, 0) + self.size, 0, ("L", 0, 1))] + self.tile = [("raw", extents, 0, ("L", 0, 1))] elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: self.mode = "LA" - self.tile = [("raw", (0, 0) + self.size, 0, ("LA", 0, 1))] + self.tile = [("raw", extents, 0, ("LA", 0, 1))] else: - raise OSError(f"Unsupported bitcount {bitcount} for {pfflags} DDS texture") + raise OSError(f"Unsupported bitcount {bitcount} for {pfflags}") elif pfflags & DDPF.FOURCC: - data_start = header_size + 4 + data_offs = header_size + 4 if fourcc == D3DFMT.DXT1: self.mode = "RGBA" self.pixel_format = "DXT1" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (1, self.pixel_format)) + tile = Image.Tile("bcn", extents, data_offs, (1, self.pixel_format)) elif fourcc == D3DFMT.DXT3: self.mode = "RGBA" self.pixel_format = "DXT3" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (2, self.pixel_format)) + tile = Image.Tile("bcn", extents, data_offs, (2, self.pixel_format)) elif fourcc == D3DFMT.DXT5: self.mode = "RGBA" self.pixel_format = "DXT5" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (3, self.pixel_format)) + tile = Image.Tile("bcn", extents, data_offs, (3, self.pixel_format)) elif fourcc == D3DFMT.ATI1: self.mode = "L" self.pixel_format = "BC4" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (4, self.pixel_format)) + tile = Image.Tile("bcn", extents, data_offs, (4, self.pixel_format)) elif fourcc == D3DFMT.BC5S: self.mode = "RGB" self.pixel_format = "BC5S" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + tile = Image.Tile("bcn", extents, data_offs, (5, self.pixel_format)) elif fourcc == D3DFMT.ATI2: self.mode = "RGB" self.pixel_format = "BC5" - tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) + tile = Image.Tile("bcn", extents, data_offs, (5, self.pixel_format)) elif fourcc == D3DFMT.DX10: - data_start += 20 + data_offs += 20 # ignoring flags which pertain to volume textures and cubemaps (dxgi_format,) = struct.unpack(" Date: Thu, 25 Aug 2022 12:49:50 +0000 Subject: [PATCH 14/58] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/helper.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index bd75c32336c..d5f298cfb20 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -14,7 +14,7 @@ import pytest from packaging.version import parse as parse_version -from PIL import Image, ImageMath, features, ImageChops +from PIL import Image, ImageChops, ImageMath, features logger = logging.getLogger(__name__) @@ -24,7 +24,6 @@ # local img.show for errors. HAS_UPLOADER = True - class test_image_results: @staticmethod def upload(a, b): @@ -35,7 +34,6 @@ def upload(a, b): elif "GITHUB_ACTIONS" in os.environ: HAS_UPLOADER = True - class test_image_results: @staticmethod def upload(a, b): @@ -60,7 +58,7 @@ def concat_h(images: List[Image.Image]): for image in images[1:]: assert image.height == new_size[1] new_size = (new_size[0] + image.width, new_size[1]) - dst = Image.new('RGBA', new_size) + dst = Image.new("RGBA", new_size) x_offset = 0 for image in images: dst.paste(image, (x_offset, 0)) @@ -91,12 +89,12 @@ def assert_deep_equal(a, b, msg=None): def assert_image(im, mode, size, msg=None): if mode is not None: assert im.mode == mode, ( - msg or f"got mode {repr(im.mode)}, expected {repr(mode)}" + msg or f"got mode {repr(im.mode)}, expected {repr(mode)}" ) if size is not None: assert im.size == size, ( - msg or f"got size {repr(im.size)}, expected {repr(size)}" + msg or f"got size {repr(im.size)}, expected {repr(size)}" ) @@ -135,8 +133,8 @@ def assert_image_similar(a, b, epsilon, msg=None): ave_diff = diff / (a.size[0] * a.size[1]) try: assert epsilon >= ave_diff, ( - (msg or "") - + f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}" + (msg or "") + + f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}" ) except Exception as e: if HAS_UPLOADER: @@ -195,8 +193,8 @@ def mark_if_feature_version(mark, feature, version_blacklist, reason=None): version_required = parse_version(version_blacklist) version_available = parse_version(features.version(feature)) if ( - version_available.major == version_required.major - and version_available.minor == version_required.minor + version_available.major == version_required.major + and version_available.minor == version_required.minor ): return mark(reason=reason) return pytest.mark.pil_noop_mark() From 9369a4845734dc75bb8cc6dd45da53e27b60689c Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Thu, 25 Aug 2022 15:50:52 +0300 Subject: [PATCH 15/58] Revert, i didn't meant to commit it --- Tests/helper.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index d5f298cfb20..69246bfcf45 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -9,15 +9,15 @@ import sysconfig import tempfile from io import BytesIO -from typing import List import pytest from packaging.version import parse as parse_version -from PIL import Image, ImageChops, ImageMath, features +from PIL import Image, ImageMath, features logger = logging.getLogger(__name__) + HAS_UPLOADER = False if os.environ.get("SHOW_ERRORS"): @@ -27,9 +27,8 @@ class test_image_results: @staticmethod def upload(a, b): - diff = ImageChops.difference(a.convert("RGB"), b.convert("RGB")) - c = concat_h([a, b, diff]) - c.show() + a.show() + b.show() elif "GITHUB_ACTIONS" in os.environ: HAS_UPLOADER = True @@ -53,19 +52,6 @@ def upload(a, b): pass -def concat_h(images: List[Image.Image]): - new_size = images[0].size - for image in images[1:]: - assert image.height == new_size[1] - new_size = (new_size[0] + image.width, new_size[1]) - dst = Image.new("RGBA", new_size) - x_offset = 0 - for image in images: - dst.paste(image, (x_offset, 0)) - x_offset += image.width - return dst - - def convert_to_comparable(a, b): new_a, new_b = a, b if a.mode == "P": From 15c90ac9bb1bd6446e40995e412a18035813ad62 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Fri, 26 Aug 2022 23:10:44 +0300 Subject: [PATCH 16/58] Simplified save code Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/DdsImagePlugin.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 6e03c7d5722..01367d99f51 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -452,10 +452,8 @@ def _save(im, fp, filename): + rgba_mask # dwRGBABitMask + struct.pack(" Date: Sat, 8 Oct 2022 22:18:31 +1100 Subject: [PATCH 17/58] Simplified test code --- Tests/test_file_dds.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 7f85a925619..98d03e3cafd 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -302,6 +302,21 @@ def test_save_unsupported_mode(tmp_path): im.save(out) +@pytest.mark.parametrize( + ("mode", "test_file"), + ( + ("L", "Tests/images/l.dds"), + ("LA", "Tests/images/la.dds"), + ("RGB", "Tests/images/rgb.dds"), + ("RGBA", "Tests/images/rgba.dds"), + ), +) +def test_open(mode, test_file): + with Image.open(test_file) as im: + assert im.mode == mode + assert_image_equal_tofile(im, test_file.replace(".dds", ".png")) + + @pytest.mark.parametrize( ("mode", "test_file"), [ @@ -320,19 +335,3 @@ def test_save(mode, test_file, tmp_path): with Image.open(out) as reloaded: assert_image_equal(im, reloaded) - - -@pytest.mark.parametrize( - ("mode", "expected_file", "input_file"), - [ - ("L", "Tests/images/l.png", "Tests/images/l.dds"), - ("LA", "Tests/images/la.png", "Tests/images/la.dds"), - ("RGB", "Tests/images/rgb.png", "Tests/images/rgb.dds"), - ("RGBA", "Tests/images/rgba.png", "Tests/images/rgba.dds"), - ], -) -def test_open(mode, expected_file, input_file): - with Image.open(input_file) as im: - assert im.mode == mode - with Image.open(expected_file) as im2: - assert_image_equal(im, im2) From eda4192618333f67c8dcc2d6df1ca7839e4221a2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Oct 2022 22:27:53 +1100 Subject: [PATCH 18/58] Fixed typo --- src/PIL/DdsImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 01367d99f51..c98d71c241c 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -444,7 +444,7 @@ def _save(im, fp, filename): stride = (im.width * bit_count + 7) // 8 fp.write( o32(DDS_MAGIC) - # header size, flags, height, width, pith, depth, mipmaps + # header size, flags, height, width, pitch, depth, mipmaps + struct.pack(" Date: Sat, 8 Oct 2022 22:33:49 +1100 Subject: [PATCH 19/58] Restored formatting --- src/PIL/DdsImagePlugin.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index c98d71c241c..32481dcb3e5 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -277,7 +277,6 @@ class DdsImageFile(ImageFile.ImageFile): format = "DDS" format_description = "DirectDraw Surface" - # fmt: off def _open(self): if not _accept(self.fp.read(4)): msg = "not a DDS file" @@ -316,10 +315,12 @@ def _open(self): self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: self.mode = "RGBA" - rawmode = (masks[0xFF000000] + - masks[0x00FF0000] + - masks[0x0000FF00] + - masks[0x000000FF]) + rawmode = ( + masks[0xFF000000] + + masks[0x00FF0000] + + masks[0x0000FF00] + + masks[0x000000FF] + ) self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] else: raise OSError(f"Unsupported bitcount {bitcount} for {pfflags}") @@ -409,13 +410,10 @@ def _open(self): msg = f"Unknown pixel format flags {repr(pfflags)}" raise NotImplementedError(msg) - # fmt: on - def load_seek(self, pos): pass -# fmt: off def _save(im, fp, filename): if im.mode not in ("RGB", "RGBA", "L", "LA"): raise OSError(f"cannot write mode {im.mode} as DDS") @@ -430,7 +428,7 @@ def _save(im, fp, filename): bit_count = 32 r, g, b, a = im.split() im = Image.merge("RGBA", (a, r, g, b)) - elif im.mode == 'LA': + elif im.mode == "LA": pixel_flags = DDPF.LUMINANCE | DDPF.ALPHAPIXELS rgba_mask = struct.pack("<4I", 0x000000FF, 0x000000FF, 0x000000FF, 0x0000FF00) bit_count = 16 @@ -444,8 +442,16 @@ def _save(im, fp, filename): stride = (im.width * bit_count + 7) // 8 fp.write( o32(DDS_MAGIC) - # header size, flags, height, width, pitch, depth, mipmaps - + struct.pack(" Date: Sat, 8 Oct 2022 22:56:58 +1100 Subject: [PATCH 20/58] Fixed big-endian bug --- src/PIL/DdsImagePlugin.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 32481dcb3e5..7367be169f4 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -15,6 +15,7 @@ from io import BytesIO from . import Image, ImageFile +from ._binary import i32le as i32 from ._binary import o32le as o32 # Magic ("DDS ") @@ -191,10 +192,6 @@ def _missing_(cls, value: object): return cls.INVALID -def make_fourcc(name): - return struct.unpack("I", name.encode("ascii"))[0] - - class D3DFMT(IntEnum): UNKNOWN = 0 R8G8B8 = 20 @@ -252,20 +249,20 @@ class D3DFMT(IntEnum): A2B10G10R10_XR_BIAS = 119 BINARYBUFFER = 199 - UYVY = make_fourcc("UYVY") - R8G8_B8G8 = make_fourcc("RGBG") - YUY2 = make_fourcc("YUY2") - G8R8_G8B8 = make_fourcc("GRGB") - DXT1 = make_fourcc("DXT1") - DXT2 = make_fourcc("DXT2") - DXT3 = make_fourcc("DXT3") - DXT4 = make_fourcc("DXT4") - DXT5 = make_fourcc("DXT5") - DX10 = make_fourcc("DX10") - BC5S = make_fourcc("BC5S") - ATI1 = make_fourcc("ATI1") - ATI2 = make_fourcc("ATI2") - MULTI2_ARGB8 = make_fourcc("MET1") + UYVY = i32(b"UYVY") + R8G8_B8G8 = i32(b"RGBG") + YUY2 = i32(b"YUY2") + G8R8_G8B8 = i32(b"GRGB") + DXT1 = i32(b"DXT1") + DXT2 = i32(b"DXT2") + DXT3 = i32(b"DXT3") + DXT4 = i32(b"DXT4") + DXT5 = i32(b"DXT5") + DX10 = i32(b"DX10") + BC5S = i32(b"BC5S") + ATI1 = i32(b"ATI1") + ATI2 = i32(b"ATI2") + MULTI2_ARGB8 = i32(b"MET1") INVALID = -1 @classmethod From 78756cd17beeffd0479a4c2c3689cfd271f22c07 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Oct 2022 23:06:39 +1100 Subject: [PATCH 21/58] Simplified imports --- src/PIL/DdsImagePlugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 7367be169f4..ad86ff97607 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -12,7 +12,6 @@ import io import struct from enum import IntEnum, IntFlag -from io import BytesIO from . import Image, ImageFile from ._binary import i32le as i32 @@ -286,7 +285,7 @@ def _open(self): if len(header_bytes) != 120: msg = f"Incomplete header: {len(header_bytes)} bytes" raise OSError(msg) - header = BytesIO(header_bytes) + header = io.BytesIO(header_bytes) flags_, height, width = struct.unpack("<3I", header.read(12)) flags = DDSD(flags_) From cadac4aad2487e2d3c097d4ad0d71c31ce4f49fb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Oct 2022 19:50:33 +1100 Subject: [PATCH 22/58] Corrected error messages --- src/PIL/DdsImagePlugin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index ad86ff97607..69bfb8f2a03 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -319,7 +319,8 @@ def _open(self): ) self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] else: - raise OSError(f"Unsupported bitcount {bitcount} for {pfflags}") + msg = f"Unsupported bitcount {bitcount} for {pfflags_}" + raise OSError(msg) elif pfflags & DDPF.LUMINANCE: if bitcount == 8: self.mode = "L" @@ -328,7 +329,8 @@ def _open(self): self.mode = "LA" self.tile = [("raw", extents, 0, ("LA", 0, 1))] else: - raise OSError(f"Unsupported bitcount {bitcount} for {pfflags}") + msg = f"Unsupported bitcount {bitcount} for {pfflags_}" + raise OSError(msg) elif pfflags & DDPF.FOURCC: data_offs = header_size + 4 if fourcc == D3DFMT.DXT1: @@ -398,12 +400,12 @@ def _open(self): msg = f"Unimplemented DXGI format {dxgi_format}" raise NotImplementedError(msg) else: - msg = f"Unimplemented pixel format {repr(fourcc)}" + msg = f"Unimplemented pixel format {repr(fourcc_)}" raise NotImplementedError(msg) self.tile = [tile] else: - msg = f"Unknown pixel format flags {repr(pfflags)}" + msg = f"Unknown pixel format flags {repr(pfflags_)}" raise NotImplementedError(msg) def load_seek(self, pos): From bd4826591b4a9d9fdfee1528488296810daef42c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Oct 2022 20:59:22 +1100 Subject: [PATCH 23/58] Removed INVALID enum --- Tests/images/unimplemented_fourcc.dds | Bin 0 -> 32896 bytes Tests/images/unimplemented_pfflags.dds | Bin 0 -> 32896 bytes ...nted_pixel_format.dds => unknown_fourcc.dds} | Bin Tests/test_file_dds.py | 13 +++++++++++-- src/PIL/DdsImagePlugin.py | 15 ++++----------- 5 files changed, 15 insertions(+), 13 deletions(-) create mode 100755 Tests/images/unimplemented_fourcc.dds create mode 100755 Tests/images/unimplemented_pfflags.dds rename Tests/images/{unimplemented_pixel_format.dds => unknown_fourcc.dds} (100%) diff --git a/Tests/images/unimplemented_fourcc.dds b/Tests/images/unimplemented_fourcc.dds new file mode 100755 index 0000000000000000000000000000000000000000..4795573646b08ff503908e2d50ccb3a5b6bf0863 GIT binary patch literal 32896 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 Zf8OR^GO=}gj=(gg2&mz%@jM&z{vSC0Wu^cC literal 0 HcmV?d00001 diff --git a/Tests/images/unimplemented_pfflags.dds b/Tests/images/unimplemented_pfflags.dds new file mode 100755 index 0000000000000000000000000000000000000000..e3fc8344d2870490de5dfc6003d96b1afa3be8f2 GIT binary patch literal 32896 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 YZSEx#Tes&3Ok;|G8txj;voY`g0Xf`dssI20 literal 0 HcmV?d00001 diff --git a/Tests/images/unimplemented_pixel_format.dds b/Tests/images/unknown_fourcc.dds similarity index 100% rename from Tests/images/unimplemented_pixel_format.dds rename to Tests/images/unknown_fourcc.dds diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 98d03e3cafd..004a2d15660 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -289,9 +289,18 @@ def test_dxt5_colorblock_alpha_issue_4142(): assert px[2] != 0 -def test_unimplemented_pixel_format(): +@pytest.mark.parametrize( + "test_file", + ( + "Tests/images/unknown_fourcc.dds", + "Tests/images/unimplemented_fourcc.dds", + "Tests/images/unimplemented_dxgi_format.dds", + "Tests/images/unimplemented_pfflags.dds", + ), +) +def test_not_implemented(test_file): with pytest.raises(NotImplementedError): - with Image.open("Tests/images/unimplemented_pixel_format.dds"): + with Image.open(test_file): pass diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 69bfb8f2a03..6e9e1cada79 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -184,11 +184,6 @@ class DXGI_FORMAT(IntEnum): V408 = 132 SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 133 SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 134 - INVALID = -1 - - @classmethod - def _missing_(cls, value: object): - return cls.INVALID class D3DFMT(IntEnum): @@ -262,11 +257,6 @@ class D3DFMT(IntEnum): ATI1 = i32(b"ATI1") ATI2 = i32(b"ATI2") MULTI2_ARGB8 = i32(b"MET1") - INVALID = -1 - - @classmethod - def _missing_(cls, value: object): - return cls.INVALID class DdsImageFile(ImageFile.ImageFile): @@ -297,7 +287,6 @@ def _open(self): # pixel format pfsize, pfflags_, fourcc_, bitcount = struct.unpack("<4I", header.read(16)) pfflags = DDPF(pfflags_) - fourcc = D3DFMT(fourcc_) masks = struct.unpack("<4I", header.read(16)) if flags & DDSD.CAPS: header.seek(20, io.SEEK_CUR) @@ -333,6 +322,10 @@ def _open(self): raise OSError(msg) elif pfflags & DDPF.FOURCC: data_offs = header_size + 4 + try: + fourcc = D3DFMT(fourcc_) + except ValueError: + raise NotImplementedError(f"Unimplemented pixel format {repr(fourcc_)}") if fourcc == D3DFMT.DXT1: self.mode = "RGBA" self.pixel_format = "DXT1" From 9165771d5e2a34148f13963e3545ec76f40aeb21 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Thu, 15 Jun 2023 03:08:04 +0300 Subject: [PATCH 24/58] Add BC5U support. Seen in Amnesia: The Bunker --- src/PIL/DdsImagePlugin.py | 11 +++++++++-- src/PIL/ImageFile.py | 3 +-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 6e9e1cada79..8c866249913 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -254,6 +254,7 @@ class D3DFMT(IntEnum): DXT5 = i32(b"DXT5") DX10 = i32(b"DX10") BC5S = i32(b"BC5S") + BC5U = i32(b"BC5U") ATI1 = i32(b"ATI1") ATI2 = i32(b"ATI2") MULTI2_ARGB8 = i32(b"MET1") @@ -325,7 +326,8 @@ def _open(self): try: fourcc = D3DFMT(fourcc_) except ValueError: - raise NotImplementedError(f"Unimplemented pixel format {repr(fourcc_)}") + msg = f"Unimplemented pixel format {repr(fourcc_)}" + raise NotImplementedError(msg) if fourcc == D3DFMT.DXT1: self.mode = "RGBA" self.pixel_format = "DXT1" @@ -346,6 +348,10 @@ def _open(self): self.mode = "RGB" self.pixel_format = "BC5S" tile = Image.Tile("bcn", extents, data_offs, (5, self.pixel_format)) + elif fourcc == D3DFMT.BC5U: + self.mode = "RGB" + self.pixel_format = "BC5U" + tile = Image.Tile("bcn", extents, data_offs, (5, self.pixel_format)) elif fourcc == D3DFMT.ATI2: self.mode = "RGB" self.pixel_format = "BC5" @@ -407,7 +413,8 @@ def load_seek(self, pos): def _save(im, fp, filename): if im.mode not in ("RGB", "RGBA", "L", "LA"): - raise OSError(f"cannot write mode {im.mode} as DDS") + msg = f"cannot write mode {im.mode} as DDS" + raise OSError(msg) pixel_flags = DDPF.RGB if im.mode == "RGB": diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 4194a7c356e..716b2f06bad 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -546,8 +546,7 @@ def _encode_tile(im, fp, tile: List[Image.Tile], bufsize, fh, exc=None): # slight speedup: compress to real file object errcode = encoder.encode_to_file(fh, bufsize) if errcode < 0: - msg = - f"encoder error {errcode} when writing image file" + msg = f"encoder error {errcode} when writing image file" raise OSError(msg) from exc finally: encoder.cleanup() From 777c54a4d12657ef7ec84af0aea36e07b7739abd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 00:09:00 +0000 Subject: [PATCH 25/58] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/ImageFile.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 716b2f06bad..d3ab1126cce 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -536,9 +536,7 @@ def _encode_tile(im, fp, tile: List[Image.Tile], bufsize, fh, exc=None): if exc: # compress to Python file-compatible object while True: - errcode, data = encoder.encode( - bufsize - )[1:] + errcode, data = encoder.encode(bufsize)[1:] fp.write(data) if errcode: break From a5dde8b1c4abdf134e5e909370caa53bed8de50c Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Thu, 12 Oct 2023 19:09:28 +0300 Subject: [PATCH 26/58] Apply suggestion for PR --- Tests/images/{ati1.dds => mode-ati1.dds} | Bin Tests/images/{ati1.png => mode-ati1.png} | Bin Tests/images/{ati2.dds => mode-ati2.dds} | Bin Tests/images/{l.dds => mode-l.dds} | Bin Tests/images/{l.png => mode-l.png} | Bin Tests/images/{la.dds => mode-la.dds} | Bin Tests/images/{la.png => mode-la.png} | Bin Tests/images/{rgb.dds => mode-rgb.dds} | Bin Tests/images/{rgb.png => mode-rgb.png} | Bin Tests/images/{rgba.dds => mode-rgba.dds} | Bin Tests/images/{rgba.png => mode-rgba.png} | Bin Tests/test_file_dds.py | 14 +-- src/PIL/DdsImagePlugin.py | 122 +++++++++++++++++++---- src/PIL/Image.py | 20 ++-- src/PIL/ImageFile.py | 4 +- 15 files changed, 123 insertions(+), 37 deletions(-) rename Tests/images/{ati1.dds => mode-ati1.dds} (100%) rename Tests/images/{ati1.png => mode-ati1.png} (100%) rename Tests/images/{ati2.dds => mode-ati2.dds} (100%) rename Tests/images/{l.dds => mode-l.dds} (100%) rename Tests/images/{l.png => mode-l.png} (100%) rename Tests/images/{la.dds => mode-la.dds} (100%) rename Tests/images/{la.png => mode-la.png} (100%) rename Tests/images/{rgb.dds => mode-rgb.dds} (100%) rename Tests/images/{rgb.png => mode-rgb.png} (100%) rename Tests/images/{rgba.dds => mode-rgba.dds} (100%) rename Tests/images/{rgba.png => mode-rgba.png} (100%) diff --git a/Tests/images/ati1.dds b/Tests/images/mode-ati1.dds similarity index 100% rename from Tests/images/ati1.dds rename to Tests/images/mode-ati1.dds diff --git a/Tests/images/ati1.png b/Tests/images/mode-ati1.png similarity index 100% rename from Tests/images/ati1.png rename to Tests/images/mode-ati1.png diff --git a/Tests/images/ati2.dds b/Tests/images/mode-ati2.dds similarity index 100% rename from Tests/images/ati2.dds rename to Tests/images/mode-ati2.dds diff --git a/Tests/images/l.dds b/Tests/images/mode-l.dds similarity index 100% rename from Tests/images/l.dds rename to Tests/images/mode-l.dds diff --git a/Tests/images/l.png b/Tests/images/mode-l.png similarity index 100% rename from Tests/images/l.png rename to Tests/images/mode-l.png diff --git a/Tests/images/la.dds b/Tests/images/mode-la.dds similarity index 100% rename from Tests/images/la.dds rename to Tests/images/mode-la.dds diff --git a/Tests/images/la.png b/Tests/images/mode-la.png similarity index 100% rename from Tests/images/la.png rename to Tests/images/mode-la.png diff --git a/Tests/images/rgb.dds b/Tests/images/mode-rgb.dds similarity index 100% rename from Tests/images/rgb.dds rename to Tests/images/mode-rgb.dds diff --git a/Tests/images/rgb.png b/Tests/images/mode-rgb.png similarity index 100% rename from Tests/images/rgb.png rename to Tests/images/mode-rgb.png diff --git a/Tests/images/rgba.dds b/Tests/images/mode-rgba.dds similarity index 100% rename from Tests/images/rgba.dds rename to Tests/images/mode-rgba.dds diff --git a/Tests/images/rgba.png b/Tests/images/mode-rgba.png similarity index 100% rename from Tests/images/rgba.png rename to Tests/images/mode-rgba.png diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 004a2d15660..52777bd304d 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -10,8 +10,8 @@ TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" -TEST_FILE_ATI1 = "Tests/images/ati1.dds" -TEST_FILE_ATI2 = "Tests/images/ati2.dds" +TEST_FILE_ATI1 = "Tests/images/mode-ati1.dds" +TEST_FILE_ATI2 = "Tests/images/mode-ati2.dds" TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" @@ -314,10 +314,10 @@ def test_save_unsupported_mode(tmp_path): @pytest.mark.parametrize( ("mode", "test_file"), ( - ("L", "Tests/images/l.dds"), - ("LA", "Tests/images/la.dds"), - ("RGB", "Tests/images/rgb.dds"), - ("RGBA", "Tests/images/rgba.dds"), + ("L", "Tests/images/mode-l.dds"), + ("LA", "Tests/images/mode-la.dds"), + ("RGB", "Tests/images/mode-rgb.dds"), + ("RGBA", "Tests/images/mode-rgba.dds"), ), ) def test_open(mode, test_file): @@ -333,7 +333,7 @@ def test_open(mode, test_file): ("LA", "Tests/images/uncompressed_la.png"), ("RGB", "Tests/images/hopper.png"), ("RGBA", "Tests/images/pil123rgba.png"), - ("L", "Tests/images/ati1.dds"), + ("L", "Tests/images/mode-ati1.dds"), ], ) def test_save(mode, test_file, tmp_path): diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 8c866249913..3fa656f0da1 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -253,6 +253,8 @@ class D3DFMT(IntEnum): DXT4 = i32(b"DXT4") DXT5 = i32(b"DXT5") DX10 = i32(b"DX10") + BC4S = i32(b"BC4S") + BC4U = i32(b"BC4U") BC5S = i32(b"BC5S") BC5U = i32(b"BC5U") ATI1 = i32(b"ATI1") @@ -260,6 +262,81 @@ class D3DFMT(IntEnum): MULTI2_ARGB8 = i32(b"MET1") +# Backward compat layer +DDSD_CAPS = DDSD.CAPS +DDSD_HEIGHT = DDSD.HEIGHT +DDSD_WIDTH = DDSD.WIDTH +DDSD_PITCH = DDSD.PITCH +DDSD_PIXELFORMAT = DDSD.PIXELFORMAT +DDSD_MIPMAPCOUNT = DDSD.MIPMAPCOUNT +DDSD_LINEARSIZE = DDSD.LINEARSIZE +DDSD_DEPTH = DDSD.DEPTH + +DDSCAPS_COMPLEX = DDSCAPS.COMPLEX +DDSCAPS_TEXTURE = DDSCAPS.TEXTURE +DDSCAPS_MIPMAP = DDSCAPS.MIPMAP + +DDSCAPS2_CUBEMAP = DDSCAPS2.CUBEMAP +DDSCAPS2_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP_POSITIVEX +DDSCAPS2_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP_NEGATIVEX +DDSCAPS2_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP_POSITIVEY +DDSCAPS2_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP_NEGATIVEY +DDSCAPS2_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP_POSITIVEZ +DDSCAPS2_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP_NEGATIVEZ +DDSCAPS2_VOLUME = DDSCAPS2.VOLUME + +DDPF_ALPHAPIXELS = DDPF.ALPHAPIXELS +DDPF_ALPHA = DDPF.ALPHA +DDPF_FOURCC = DDPF.FOURCC +DDPF_PALETTEINDEXED8 = DDPF.PALETTEINDEXED8 +DDPF_RGB = DDPF.RGB +DDPF_LUMINANCE = DDPF.LUMINANCE + +DDS_FOURCC = DDPF_FOURCC +DDS_RGB = DDPF_RGB +DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS +DDS_LUMINANCE = DDPF_LUMINANCE +DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS +DDS_ALPHA = DDPF_ALPHA +DDS_PAL8 = DDPF_PALETTEINDEXED8 + +DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT +DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT +DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH +DDS_HEADER_FLAGS_PITCH = DDSD_PITCH +DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE + +DDS_HEIGHT = DDSD_HEIGHT +DDS_WIDTH = DDSD_WIDTH + +DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE +DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP +DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX + +DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX +DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX +DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY +DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY +DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ +DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ + +DXT1_FOURCC = D3DFMT.DXT1 +DXT3_FOURCC = D3DFMT.DXT3 +DXT5_FOURCC = D3DFMT.DXT5 + +DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS +DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM +DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB +DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS +DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM +DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM +DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16 +DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16 +DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS +DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM +DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB + + class DdsImageFile(ImageFile.ImageFile): format = "DDS" format_description = "DirectDraw Surface" @@ -311,6 +388,13 @@ def _open(self): else: msg = f"Unsupported bitcount {bitcount} for {pfflags_}" raise OSError(msg) + elif pfflags & DDPF.ALPHA: + if bitcount == 8: + self.mode = "L" + self.tile = [("raw", extents, 0, ("L", 0, 1))] + else: + msg = f"Unsupported bitcount {bitcount} for {pfflags_}" + raise OSError(msg) elif pfflags & DDPF.LUMINANCE: if bitcount == 8: self.mode = "L" @@ -331,68 +415,72 @@ def _open(self): if fourcc == D3DFMT.DXT1: self.mode = "RGBA" self.pixel_format = "DXT1" - tile = Image.Tile("bcn", extents, data_offs, (1, self.pixel_format)) + tile = Image._Tile("bcn", extents, data_offs, (1, self.pixel_format)) elif fourcc == D3DFMT.DXT3: self.mode = "RGBA" self.pixel_format = "DXT3" - tile = Image.Tile("bcn", extents, data_offs, (2, self.pixel_format)) + tile = Image._Tile("bcn", extents, data_offs, (2, self.pixel_format)) elif fourcc == D3DFMT.DXT5: self.mode = "RGBA" self.pixel_format = "DXT5" - tile = Image.Tile("bcn", extents, data_offs, (3, self.pixel_format)) - elif fourcc == D3DFMT.ATI1: + tile = Image._Tile("bcn", extents, data_offs, (3, self.pixel_format)) + elif fourcc == D3DFMT.ATI1 or fourcc == D3DFMT.BC4U: self.mode = "L" self.pixel_format = "BC4" - tile = Image.Tile("bcn", extents, data_offs, (4, self.pixel_format)) + tile = Image._Tile("bcn", extents, data_offs, (4, self.pixel_format)) elif fourcc == D3DFMT.BC5S: self.mode = "RGB" self.pixel_format = "BC5S" - tile = Image.Tile("bcn", extents, data_offs, (5, self.pixel_format)) + tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) elif fourcc == D3DFMT.BC5U: self.mode = "RGB" self.pixel_format = "BC5U" - tile = Image.Tile("bcn", extents, data_offs, (5, self.pixel_format)) + tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) elif fourcc == D3DFMT.ATI2: self.mode = "RGB" self.pixel_format = "BC5" - tile = Image.Tile("bcn", extents, data_offs, (5, self.pixel_format)) + tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) elif fourcc == D3DFMT.DX10: data_offs += 20 # ignoring flags which pertain to volume textures and cubemaps (dxgi_format,) = struct.unpack(" 0: fp.seek(offset) From bc6c973e2df78729c315686c7961fa56af9898d6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 16:19:21 +0000 Subject: [PATCH 27/58] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/DdsImagePlugin.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index c0d546b3133..460b209fc1b 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -449,35 +449,53 @@ def _open(self): # ignoring flags which pertain to volume textures and cubemaps (dxgi_format,) = struct.unpack(" Date: Thu, 12 Oct 2023 22:19:30 +0300 Subject: [PATCH 28/58] Add missing annotation import --- src/PIL/Image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c7daa87cd00..5ec7d0e77b9 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -23,6 +23,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import atexit import builtins From 69b922d138a730271a19f321957bc5329c725c4d Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Thu, 12 Oct 2023 22:26:00 +0300 Subject: [PATCH 29/58] Replace self.mode with self._mode --- src/PIL/DdsImagePlugin.py | 40 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 460b209fc1b..86cbe4c420b 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -374,10 +374,10 @@ def _open(self): masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} if bitcount == 24: rawmode = masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF] - self.mode = "RGB" + self._mode = "RGB" self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: - self.mode = "RGBA" + self._mode = "RGBA" rawmode = ( masks[0xFF000000] + masks[0x00FF0000] @@ -390,17 +390,17 @@ def _open(self): raise OSError(msg) elif pfflags & DDPF.ALPHA: if bitcount == 8: - self.mode = "L" + self._mode = "L" self.tile = [("raw", extents, 0, ("L", 0, 1))] else: msg = f"Unsupported bitcount {bitcount} for {pfflags_}" raise OSError(msg) elif pfflags & DDPF.LUMINANCE: if bitcount == 8: - self.mode = "L" + self._mode = "L" self.tile = [("raw", extents, 0, ("L", 0, 1))] elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: - self.mode = "LA" + self._mode = "LA" self.tile = [("raw", extents, 0, ("LA", 0, 1))] else: msg = f"Unsupported bitcount {bitcount} for {pfflags_}" @@ -417,31 +417,31 @@ def _open(self): msg = f"Unimplemented pixel format {repr(fourcc_)}" raise NotImplementedError(msg) if fourcc == D3DFMT.DXT1: - self.mode = "RGBA" + self._mode = "RGBA" self.pixel_format = "DXT1" tile = Image._Tile("bcn", extents, data_offs, (1, self.pixel_format)) elif fourcc == D3DFMT.DXT3: - self.mode = "RGBA" + self._mode = "RGBA" self.pixel_format = "DXT3" tile = Image._Tile("bcn", extents, data_offs, (2, self.pixel_format)) elif fourcc == D3DFMT.DXT5: - self.mode = "RGBA" + self._mode = "RGBA" self.pixel_format = "DXT5" tile = Image._Tile("bcn", extents, data_offs, (3, self.pixel_format)) elif fourcc == D3DFMT.ATI1 or fourcc == D3DFMT.BC4U: - self.mode = "L" + self._mode = "L" self.pixel_format = "BC4" tile = Image._Tile("bcn", extents, data_offs, (4, self.pixel_format)) elif fourcc == D3DFMT.BC5S: - self.mode = "RGB" + self._mode = "RGB" self.pixel_format = "BC5S" tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) elif fourcc == D3DFMT.BC5U: - self.mode = "RGB" + self._mode = "RGB" self.pixel_format = "BC5U" tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) elif fourcc == D3DFMT.ATI2: - self.mode = "RGB" + self._mode = "RGB" self.pixel_format = "BC5" tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) elif fourcc == D3DFMT.DX10: @@ -454,43 +454,43 @@ def _open(self): DXGI_FORMAT.BC1_UNORM_SRGB, DXGI_FORMAT.BC1_TYPELESS, ): - self.mode = "RGBA" + self._mode = "RGBA" self.pixel_format = "BC1" tile = Image._Tile( "bcn", extents, data_offs, (1, self.pixel_format) ) elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM): - self.mode = "RGB" + self._mode = "RGB" self.pixel_format = "BC5" tile = Image._Tile( "bcn", extents, data_offs, (5, self.pixel_format) ) elif dxgi_format == DXGI_FORMAT.BC5_SNORM: - self.mode = "RGB" + self._mode = "RGB" self.pixel_format = "BC5S" tile = Image._Tile( "bcn", extents, data_offs, (5, self.pixel_format) ) elif dxgi_format == DXGI_FORMAT.BC6H_UF16: - self.mode = "RGB" + self._mode = "RGB" self.pixel_format = "BC6H" tile = Image._Tile( "bcn", extents, data_offs, (6, self.pixel_format) ) elif dxgi_format == DXGI_FORMAT.BC6H_SF16: - self.mode = "RGB" + self._mode = "RGB" self.pixel_format = "BC6HS" tile = Image._Tile( "bcn", extents, data_offs, (6, self.pixel_format) ) elif dxgi_format in (DXGI_FORMAT.BC7_TYPELESS, DXGI_FORMAT.BC7_UNORM): - self.mode = "RGBA" + self._mode = "RGBA" self.pixel_format = "BC7" tile = Image._Tile( "bcn", extents, data_offs, (7, self.pixel_format) ) elif dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: - self.mode = "RGBA" + self._mode = "RGBA" self.pixel_format = "BC7" self.info["gamma"] = 1 / 2.2 tile = Image._Tile( @@ -501,7 +501,7 @@ def _open(self): DXGI_FORMAT.R8G8B8A8_UNORM, DXGI_FORMAT.R8G8B8A8_UNORM_SRGB, ): - self.mode = "RGBA" + self._mode = "RGBA" tile = Image._Tile("raw", extents, 0, ("RGBA", 0, 1)) if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB: self.info["gamma"] = 1 / 2.2 From 5e9a2e3000a12c52b184cf3dd3d07a925c2448a1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 17 Oct 2023 19:10:24 +1100 Subject: [PATCH 30/58] Renamed "tile_args" to "args" for consistency with existing ImageFile variable --- src/PIL/Image.py | 2 +- src/PIL/ImageFile.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 5ec7d0e77b9..b91a10c2704 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -212,7 +212,7 @@ class _Tile(NamedTuple): encoder_name: str extents: tuple[int, int, int, int] offset: int - tile_args: tuple + args: tuple # -------------------------------------------------------------------- diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 637e6e92ebd..18b26d5b4f7 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -523,10 +523,10 @@ def _save(im, fp, tile, bufsize=0): def _encode_tile(im, fp, tile: list[Image._Tile], bufsize, fh, exc=None): - for encoder_name, extents, offset, tile_args in tile: + for encoder_name, extents, offset, args in tile: if offset > 0: fp.seek(offset) - encoder = Image._getencoder(im.mode, encoder_name, tile_args, im.encoderconfig) + encoder = Image._getencoder(im.mode, encoder_name, args, im.encoderconfig) try: encoder.setimage(im.im, extents) if encoder.pushes_fd: From 51f7359c94a46ee51d9a6146660046c6b3a1176e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 17 Oct 2023 19:50:17 +1100 Subject: [PATCH 31/58] Loop over enums to create constants --- src/PIL/DdsImagePlugin.py | 96 ++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 57 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 86cbe4c420b..fc199ef7b71 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -11,6 +11,7 @@ """ import io import struct +import sys from enum import IntEnum, IntFlag from . import Image, ImageFile, ImagePalette @@ -263,62 +264,43 @@ class D3DFMT(IntEnum): # Backward compat layer -DDSD_CAPS = DDSD.CAPS -DDSD_HEIGHT = DDSD.HEIGHT -DDSD_WIDTH = DDSD.WIDTH -DDSD_PITCH = DDSD.PITCH -DDSD_PIXELFORMAT = DDSD.PIXELFORMAT -DDSD_MIPMAPCOUNT = DDSD.MIPMAPCOUNT -DDSD_LINEARSIZE = DDSD.LINEARSIZE -DDSD_DEPTH = DDSD.DEPTH - -DDSCAPS_COMPLEX = DDSCAPS.COMPLEX -DDSCAPS_TEXTURE = DDSCAPS.TEXTURE -DDSCAPS_MIPMAP = DDSCAPS.MIPMAP - -DDSCAPS2_CUBEMAP = DDSCAPS2.CUBEMAP -DDSCAPS2_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP_POSITIVEX -DDSCAPS2_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP_NEGATIVEX -DDSCAPS2_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP_POSITIVEY -DDSCAPS2_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP_NEGATIVEY -DDSCAPS2_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP_POSITIVEZ -DDSCAPS2_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP_NEGATIVEZ -DDSCAPS2_VOLUME = DDSCAPS2.VOLUME - -DDPF_ALPHAPIXELS = DDPF.ALPHAPIXELS -DDPF_ALPHA = DDPF.ALPHA -DDPF_FOURCC = DDPF.FOURCC -DDPF_PALETTEINDEXED8 = DDPF.PALETTEINDEXED8 -DDPF_RGB = DDPF.RGB -DDPF_LUMINANCE = DDPF.LUMINANCE - -DDS_FOURCC = DDPF_FOURCC -DDS_RGB = DDPF_RGB -DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS -DDS_LUMINANCE = DDPF_LUMINANCE -DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS -DDS_ALPHA = DDPF_ALPHA -DDS_PAL8 = DDPF_PALETTEINDEXED8 - -DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT -DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT -DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH -DDS_HEADER_FLAGS_PITCH = DDSD_PITCH -DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE - -DDS_HEIGHT = DDSD_HEIGHT -DDS_WIDTH = DDSD_WIDTH - -DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE -DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP -DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX - -DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX -DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX -DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY -DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY -DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ -DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ +module = sys.modules[__name__] +for item in DDSD: + setattr(module, "DDSD_" + item.name, item.value) +for item in DDSCAPS: + setattr(module, "DDSCAPS_" + item.name, item.value) +for item in DDSCAPS2: + setattr(module, "DDSCAPS2_" + item.name, item.value) +for item in DDPF: + setattr(module, "DDPF_" + item.name, item.value) + +DDS_FOURCC = DDPF.FOURCC +DDS_RGB = DDPF.RGB +DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS +DDS_LUMINANCE = DDPF.LUMINANCE +DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS +DDS_ALPHA = DDPF.ALPHA +DDS_PAL8 = DDPF.PALETTEINDEXED8 + +DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT +DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT +DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH +DDS_HEADER_FLAGS_PITCH = DDSD.PITCH +DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE + +DDS_HEIGHT = DDSD.HEIGHT +DDS_WIDTH = DDSD.WIDTH + +DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE +DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP +DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX + +DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX +DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX +DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY +DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY +DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ +DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ DXT1_FOURCC = D3DFMT.DXT1 DXT3_FOURCC = D3DFMT.DXT3 @@ -405,7 +387,7 @@ def _open(self): else: msg = f"Unsupported bitcount {bitcount} for {pfflags_}" raise OSError(msg) - elif pfflags & DDPF_PALETTEINDEXED8: + elif pfflags & DDPF.PALETTEINDEXED8: self._mode = "P" self.palette = ImagePalette.raw("RGBA", self.fp.read(1024)) self.tile = [("raw", (0, 0) + self.size, 0, "L")] From 935958c9903bce76e8996959c291c1967eac0126 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 18 Oct 2023 17:32:36 +1100 Subject: [PATCH 32/58] Removed unnecessary casting to enums --- src/PIL/DdsImagePlugin.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index fc199ef7b71..ad6561d91f8 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -337,16 +337,14 @@ def _open(self): raise OSError(msg) header = io.BytesIO(header_bytes) - flags_, height, width = struct.unpack("<3I", header.read(12)) - flags = DDSD(flags_) + flags, height, width = struct.unpack("<3I", header.read(12)) self._size = (width, height) pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) struct.unpack("<11I", header.read(44)) # reserved # pixel format - pfsize, pfflags_, fourcc_, bitcount = struct.unpack("<4I", header.read(16)) - pfflags = DDPF(pfflags_) + pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16)) masks = struct.unpack("<4I", header.read(16)) if flags & DDSD.CAPS: header.seek(20, io.SEEK_CUR) @@ -368,14 +366,14 @@ def _open(self): ) self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] else: - msg = f"Unsupported bitcount {bitcount} for {pfflags_}" + msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) elif pfflags & DDPF.ALPHA: if bitcount == 8: self._mode = "L" self.tile = [("raw", extents, 0, ("L", 0, 1))] else: - msg = f"Unsupported bitcount {bitcount} for {pfflags_}" + msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) elif pfflags & DDPF.LUMINANCE: if bitcount == 8: @@ -385,7 +383,7 @@ def _open(self): self._mode = "LA" self.tile = [("raw", extents, 0, ("LA", 0, 1))] else: - msg = f"Unsupported bitcount {bitcount} for {pfflags_}" + msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) elif pfflags & DDPF.PALETTEINDEXED8: self._mode = "P" @@ -393,11 +391,6 @@ def _open(self): self.tile = [("raw", (0, 0) + self.size, 0, "L")] elif pfflags & DDPF.FOURCC: data_offs = header_size + 4 - try: - fourcc = D3DFMT(fourcc_) - except ValueError: - msg = f"Unimplemented pixel format {repr(fourcc_)}" - raise NotImplementedError(msg) if fourcc == D3DFMT.DXT1: self._mode = "RGBA" self.pixel_format = "DXT1" @@ -491,12 +484,12 @@ def _open(self): msg = f"Unimplemented DXGI format {dxgi_format}" raise NotImplementedError(msg) else: - msg = f"Unimplemented pixel format {repr(fourcc_)}" + msg = f"Unimplemented pixel format {repr(fourcc)}" raise NotImplementedError(msg) self.tile = [tile] else: - msg = f"Unknown pixel format flags {repr(pfflags_)}" + msg = f"Unknown pixel format flags {repr(pfflags)}" raise NotImplementedError(msg) def load_seek(self, pos): From 4a4a1ee6adb1982e318ba3032ee3fa747d32f758 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Oct 2023 16:03:21 +1100 Subject: [PATCH 33/58] Simplified tile creation --- src/PIL/DdsImagePlugin.py | 47 ++++++++++++++------------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index ad6561d91f8..40276cde2af 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -394,31 +394,31 @@ def _open(self): if fourcc == D3DFMT.DXT1: self._mode = "RGBA" self.pixel_format = "DXT1" - tile = Image._Tile("bcn", extents, data_offs, (1, self.pixel_format)) + n = 1 elif fourcc == D3DFMT.DXT3: self._mode = "RGBA" self.pixel_format = "DXT3" - tile = Image._Tile("bcn", extents, data_offs, (2, self.pixel_format)) + n = 2 elif fourcc == D3DFMT.DXT5: self._mode = "RGBA" self.pixel_format = "DXT5" - tile = Image._Tile("bcn", extents, data_offs, (3, self.pixel_format)) + n = 3 elif fourcc == D3DFMT.ATI1 or fourcc == D3DFMT.BC4U: self._mode = "L" self.pixel_format = "BC4" - tile = Image._Tile("bcn", extents, data_offs, (4, self.pixel_format)) + n = 4 elif fourcc == D3DFMT.BC5S: self._mode = "RGB" self.pixel_format = "BC5S" - tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) + n = 5 elif fourcc == D3DFMT.BC5U: self._mode = "RGB" self.pixel_format = "BC5U" - tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) + n = 5 elif fourcc == D3DFMT.ATI2: self._mode = "RGB" self.pixel_format = "BC5" - tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) + n = 5 elif fourcc == D3DFMT.DX10: data_offs += 20 # ignoring flags which pertain to volume textures and cubemaps @@ -431,55 +431,42 @@ def _open(self): ): self._mode = "RGBA" self.pixel_format = "BC1" - tile = Image._Tile( - "bcn", extents, data_offs, (1, self.pixel_format) - ) + n = 1 elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM): self._mode = "RGB" self.pixel_format = "BC5" - tile = Image._Tile( - "bcn", extents, data_offs, (5, self.pixel_format) - ) + n = 5 elif dxgi_format == DXGI_FORMAT.BC5_SNORM: self._mode = "RGB" self.pixel_format = "BC5S" - tile = Image._Tile( - "bcn", extents, data_offs, (5, self.pixel_format) - ) + n = 5 elif dxgi_format == DXGI_FORMAT.BC6H_UF16: self._mode = "RGB" self.pixel_format = "BC6H" - tile = Image._Tile( - "bcn", extents, data_offs, (6, self.pixel_format) - ) + n = 6 elif dxgi_format == DXGI_FORMAT.BC6H_SF16: self._mode = "RGB" self.pixel_format = "BC6HS" - tile = Image._Tile( - "bcn", extents, data_offs, (6, self.pixel_format) - ) + n = 6 elif dxgi_format in (DXGI_FORMAT.BC7_TYPELESS, DXGI_FORMAT.BC7_UNORM): self._mode = "RGBA" self.pixel_format = "BC7" - tile = Image._Tile( - "bcn", extents, data_offs, (7, self.pixel_format) - ) + n = 7 elif dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: self._mode = "RGBA" self.pixel_format = "BC7" self.info["gamma"] = 1 / 2.2 - tile = Image._Tile( - "bcn", extents, data_offs, (7, self.pixel_format) - ) + n = 7 elif dxgi_format in ( DXGI_FORMAT.R8G8B8A8_TYPELESS, DXGI_FORMAT.R8G8B8A8_UNORM, DXGI_FORMAT.R8G8B8A8_UNORM_SRGB, ): self._mode = "RGBA" - tile = Image._Tile("raw", extents, 0, ("RGBA", 0, 1)) + self.tile = [Image._Tile("raw", extents, 0, ("RGBA", 0, 1))] if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB: self.info["gamma"] = 1 / 2.2 + return else: msg = f"Unimplemented DXGI format {dxgi_format}" raise NotImplementedError(msg) @@ -487,7 +474,7 @@ def _open(self): msg = f"Unimplemented pixel format {repr(fourcc)}" raise NotImplementedError(msg) - self.tile = [tile] + self.tile = [Image._Tile("bcn", extents, data_offs, (n, self.pixel_format))] else: msg = f"Unknown pixel format flags {repr(pfflags)}" raise NotImplementedError(msg) From 8fbb6103786e47a376059970690583adf5108aed Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Oct 2023 17:33:04 +1100 Subject: [PATCH 34/58] Derive bit count from number of modes --- src/PIL/DdsImagePlugin.py | 44 +++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 40276cde2af..5965477aac2 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -403,7 +403,7 @@ def _open(self): self._mode = "RGBA" self.pixel_format = "DXT5" n = 3 - elif fourcc == D3DFMT.ATI1 or fourcc == D3DFMT.BC4U: + elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1): self._mode = "L" self.pixel_format = "BC4" n = 4 @@ -411,11 +411,7 @@ def _open(self): self._mode = "RGB" self.pixel_format = "BC5S" n = 5 - elif fourcc == D3DFMT.BC5U: - self._mode = "RGB" - self.pixel_format = "BC5U" - n = 5 - elif fourcc == D3DFMT.ATI2: + elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2): self._mode = "RGB" self.pixel_format = "BC5" n = 5 @@ -488,32 +484,30 @@ def _save(im, fp, filename): msg = f"cannot write mode {im.mode} as DDS" raise OSError(msg) - pixel_flags = DDPF.RGB - if im.mode == "RGB": - rgba_mask = struct.pack("<4I", 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000) - bit_count = 24 - elif im.mode == "RGBA": - pixel_flags |= DDPF.ALPHAPIXELS - rgba_mask = struct.pack("<4I", 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000) - bit_count = 32 + if im.mode == "RGBA": + pixel_flags = DDPF.RGB | DDPF.ALPHAPIXELS + rgba_mask = (0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000) + r, g, b, a = im.split() im = Image.merge("RGBA", (a, r, g, b)) + elif im.mode == "RGB": + pixel_flags = DDPF.RGB + rgba_mask = (0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000) elif im.mode == "LA": pixel_flags = DDPF.LUMINANCE | DDPF.ALPHAPIXELS - rgba_mask = struct.pack("<4I", 0x000000FF, 0x000000FF, 0x000000FF, 0x0000FF00) - bit_count = 16 + rgba_mask = (0x000000FF, 0x000000FF, 0x000000FF, 0x0000FF00) else: # im.mode == "L" pixel_flags = DDPF.LUMINANCE - rgba_mask = struct.pack("<4I", 0xFF000000, 0xFF000000, 0xFF000000, 0x00000000) - bit_count = 8 + rgba_mask = (0xFF000000, 0xFF000000, 0xFF000000, 0x00000000) flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT - + bit_count = len(im.getbands()) * 8 stride = (im.width * bit_count + 7) // 8 + fp.write( o32(DDS_MAGIC) + struct.pack( - " Date: Sat, 21 Oct 2023 19:12:52 +1100 Subject: [PATCH 35/58] Simplified creating raw tiles --- src/PIL/DdsImagePlugin.py | 68 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 5965477aac2..bf3807c84bc 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -348,14 +348,14 @@ def _open(self): masks = struct.unpack("<4I", header.read(16)) if flags & DDSD.CAPS: header.seek(20, io.SEEK_CUR) - extents = (0, 0) + self.size + n = 0 + rawmode = None if pfflags & DDPF.RGB: # Texture contains uncompressed RGB data masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} if bitcount == 24: - rawmode = masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF] self._mode = "RGB" - self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] + rawmode = masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF] elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: self._mode = "RGBA" rawmode = ( @@ -364,31 +364,27 @@ def _open(self): + masks[0x0000FF00] + masks[0x000000FF] ) - self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] else: msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) + rawmode = rawmode[::-1] elif pfflags & DDPF.ALPHA: if bitcount == 8: self._mode = "L" - self.tile = [("raw", extents, 0, ("L", 0, 1))] else: msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) elif pfflags & DDPF.LUMINANCE: if bitcount == 8: self._mode = "L" - self.tile = [("raw", extents, 0, ("L", 0, 1))] elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: self._mode = "LA" - self.tile = [("raw", extents, 0, ("LA", 0, 1))] else: msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) elif pfflags & DDPF.PALETTEINDEXED8: self._mode = "P" self.palette = ImagePalette.raw("RGBA", self.fp.read(1024)) - self.tile = [("raw", (0, 0) + self.size, 0, "L")] elif pfflags & DDPF.FOURCC: data_offs = header_size + 4 if fourcc == D3DFMT.DXT1: @@ -444,36 +440,39 @@ def _open(self): self._mode = "RGB" self.pixel_format = "BC6HS" n = 6 - elif dxgi_format in (DXGI_FORMAT.BC7_TYPELESS, DXGI_FORMAT.BC7_UNORM): - self._mode = "RGBA" - self.pixel_format = "BC7" - n = 7 - elif dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: + elif dxgi_format in ( + DXGI_FORMAT.BC7_TYPELESS, + DXGI_FORMAT.BC7_UNORM, + DXGI_FORMAT.BC7_UNORM_SRGB, + ): self._mode = "RGBA" self.pixel_format = "BC7" - self.info["gamma"] = 1 / 2.2 n = 7 + if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: + self.info["gamma"] = 1 / 2.2 elif dxgi_format in ( DXGI_FORMAT.R8G8B8A8_TYPELESS, DXGI_FORMAT.R8G8B8A8_UNORM, DXGI_FORMAT.R8G8B8A8_UNORM_SRGB, ): self._mode = "RGBA" - self.tile = [Image._Tile("raw", extents, 0, ("RGBA", 0, 1))] if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB: self.info["gamma"] = 1 / 2.2 - return else: msg = f"Unimplemented DXGI format {dxgi_format}" raise NotImplementedError(msg) else: msg = f"Unimplemented pixel format {repr(fourcc)}" raise NotImplementedError(msg) + else: + msg = f"Unknown pixel format flags {pfflags}" + raise NotImplementedError(msg) + extents = (0, 0) + self.size + if n: self.tile = [Image._Tile("bcn", extents, data_offs, (n, self.pixel_format))] else: - msg = f"Unknown pixel format flags {repr(pfflags)}" - raise NotImplementedError(msg) + self.tile = [Image._Tile("raw", extents, 0, rawmode or self.mode)] def load_seek(self, pos): pass @@ -484,21 +483,25 @@ def _save(im, fp, filename): msg = f"cannot write mode {im.mode} as DDS" raise OSError(msg) - if im.mode == "RGBA": - pixel_flags = DDPF.RGB | DDPF.ALPHAPIXELS - rgba_mask = (0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000) - - r, g, b, a = im.split() - im = Image.merge("RGBA", (a, r, g, b)) - elif im.mode == "RGB": - pixel_flags = DDPF.RGB - rgba_mask = (0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000) - elif im.mode == "LA": - pixel_flags = DDPF.LUMINANCE | DDPF.ALPHAPIXELS - rgba_mask = (0x000000FF, 0x000000FF, 0x000000FF, 0x0000FF00) - else: # im.mode == "L" + alpha = im.mode[-1] == "A" + if im.mode[0] == "L": pixel_flags = DDPF.LUMINANCE - rgba_mask = (0xFF000000, 0xFF000000, 0xFF000000, 0x00000000) + rawmode = im.mode + if alpha: + rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF] + else: + rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000] + else: + pixel_flags = DDPF.RGB + rawmode = im.mode[::-1] + rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF] + + if alpha: + r, g, b, a = im.split() + im = Image.merge("RGBA", (a, r, g, b)) + if alpha: + pixel_flags |= DDPF.ALPHAPIXELS + rgba_mask.append(0xFF000000 if alpha else 0) flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT bit_count = len(im.getbands()) * 8 @@ -522,7 +525,6 @@ def _save(im, fp, filename): + struct.pack("<4I", *rgba_mask) # dwRGBABitMask + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) ) - rawmode = "LA" if im.mode == "LA" else im.mode[::-1] ImageFile._save(im, fp, [Image._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) From 93e0f39ff3614e865bf4e177e12b916d46d5a990 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Oct 2023 19:14:46 +1100 Subject: [PATCH 36/58] Removed "mode-" prefix from image names that are not modes --- Tests/images/{mode-ati1.dds => ati1.dds} | Bin Tests/images/{mode-ati1.png => ati1.png} | Bin Tests/images/{mode-ati2.dds => ati2.dds} | Bin Tests/test_file_dds.py | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename Tests/images/{mode-ati1.dds => ati1.dds} (100%) rename Tests/images/{mode-ati1.png => ati1.png} (100%) rename Tests/images/{mode-ati2.dds => ati2.dds} (100%) diff --git a/Tests/images/mode-ati1.dds b/Tests/images/ati1.dds similarity index 100% rename from Tests/images/mode-ati1.dds rename to Tests/images/ati1.dds diff --git a/Tests/images/mode-ati1.png b/Tests/images/ati1.png similarity index 100% rename from Tests/images/mode-ati1.png rename to Tests/images/ati1.png diff --git a/Tests/images/mode-ati2.dds b/Tests/images/ati2.dds similarity index 100% rename from Tests/images/mode-ati2.dds rename to Tests/images/ati2.dds diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index fbc523ed1b4..72bb2df7b9e 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -10,8 +10,8 @@ TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" -TEST_FILE_ATI1 = "Tests/images/mode-ati1.dds" -TEST_FILE_ATI2 = "Tests/images/mode-ati2.dds" +TEST_FILE_ATI1 = "Tests/images/ati1.dds" +TEST_FILE_ATI2 = "Tests/images/ati2.dds" TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" @@ -347,7 +347,7 @@ def test_open(mode, test_file): ("LA", "Tests/images/uncompressed_la.png"), ("RGB", "Tests/images/hopper.png"), ("RGBA", "Tests/images/pil123rgba.png"), - ("L", "Tests/images/mode-ati1.dds"), + ("L", TEST_FILE_ATI1), ], ) def test_save(mode, test_file, tmp_path): From f58f410b216d39b71850f14db97eb699cf75f5ee Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 25 Oct 2023 15:30:59 +1100 Subject: [PATCH 37/58] ALPHA by itself does not mean bitcount is valid --- src/PIL/DdsImagePlugin.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index bf3807c84bc..b27781f3c4c 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -368,12 +368,6 @@ def _open(self): msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) rawmode = rawmode[::-1] - elif pfflags & DDPF.ALPHA: - if bitcount == 8: - self._mode = "L" - else: - msg = f"Unsupported bitcount {bitcount} for {pfflags}" - raise OSError(msg) elif pfflags & DDPF.LUMINANCE: if bitcount == 8: self._mode = "L" From 85485229e3b1f4e71a4526007a97c9900b26a253 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 25 Oct 2023 16:24:00 +1100 Subject: [PATCH 38/58] Support RGB bitcount 8 --- Tests/images/rgb8.dds | Bin 0 -> 16512 bytes Tests/test_file_dds.py | 6 ++++++ src/PIL/DdsImagePlugin.py | 13 +++++++------ 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 Tests/images/rgb8.dds diff --git a/Tests/images/rgb8.dds b/Tests/images/rgb8.dds new file mode 100644 index 0000000000000000000000000000000000000000..8193e8e5ac64c3f6921bb59ba3a4fe33910bf028 GIT binary patch literal 16512 zcmeH}&21Yo5QR54-x7CVsD(6;gE|P@O%ATd#kmEAKVt$jocD$+L0AYH0=7iGCBDax zi^ZPLf8YLX+xGLZAA{&^`M-+$Xy7<63vfY*HW0rSs3U`_*p z!wLe26$E}|1x2qD(^b*y#N>)zCngW36W$b<@TS0o-&Me&D+LZ+DRAg}6af5C0PsTr zz%MH}m=EzH0B&Cd!0oRIAox~*;9CKLUn+1gpW=%Ey8R-6Zht|*lk@4t;7e?I^C@!$UZ=Lb+;8)<=((>pMD?W{4q0ZM*vJ}c1blL9R#0$Lva zg9c7$6p);YLU}G{hlTs3g=g`eL0fLEUTfcft}U`_*p!wLe26$E}|1x2qD(^b*y#N>)zCngW36W$b< z@TS0o-&Me&D+LZ+DRAg}6af5C0PsTrz%MH}m=EzH0B&Cd!0oRIAox~*;9CKLUn+1g zpW=%Ey8R-6Zht|*lk@4t;7e?I^C@!$UZ=Lb+;8)<=( z(>pMD?W{4q0ZM*vJ}c1blL9R#0$Lvag9c7$6p); zYLU}G{hlTs3g=g`ZA+z$;(-fccL;U`_*p!wLe2 z6$E}|1x2qD(^b*y#N>)zCngW36W$b<@TS0o-&Me&D+LZ+DRAg}6af5C0PsTrz%MH} zm=EzH0B&Cd!0oRIAox~*;9CKLUn+1gpW=%Ey8R-6Zht|*lk@4t;7e?I^C@!$UZ=Lb+;8)<=((>pMD?W{4q0ZM*vJ}c1blL9R#0$Lvag9c7$6p);YLU}G{hl vTs3g=g`cDXyizK_Tq?jkgEWu^(m)zW18E=)q=7V$2GT$pNCRo$5Ci`K#A3DB literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 72bb2df7b9e..335c4c2de7c 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -340,6 +340,12 @@ def test_open(mode, test_file): assert_image_equal_tofile(im, test_file.replace(".dds", ".png")) +def test_open_rgb8(): + with Image.open("Tests/images/rgb8.dds") as im: + assert im.mode == "L" + assert_image_equal_tofile(im, "Tests/images/mode-l.png") + + @pytest.mark.parametrize( ("mode", "test_file"), [ diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index b27781f3c4c..5c4abfba33f 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -353,21 +353,22 @@ def _open(self): if pfflags & DDPF.RGB: # Texture contains uncompressed RGB data masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} - if bitcount == 24: + if bitcount == 8: + self._mode = "L" + elif bitcount == 24: self._mode = "RGB" - rawmode = masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF] + rawmode = masks[0x000000FF] + masks[0x0000FF00] + masks[0x00FF0000] elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: self._mode = "RGBA" rawmode = ( - masks[0xFF000000] - + masks[0x00FF0000] + masks[0x000000FF] + masks[0x0000FF00] - + masks[0x000000FF] + + masks[0x00FF0000] + + masks[0xFF000000] ) else: msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) - rawmode = rawmode[::-1] elif pfflags & DDPF.LUMINANCE: if bitcount == 8: self._mode = "L" From 0051fc9cf82fbece7c4e9acbbec65d688fdd3000 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Oct 2023 16:43:43 +1100 Subject: [PATCH 39/58] Removed unnecessary seek --- src/PIL/DdsImagePlugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 5c4abfba33f..51bb0201bcf 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -346,8 +346,6 @@ def _open(self): # pixel format pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16)) masks = struct.unpack("<4I", header.read(16)) - if flags & DDSD.CAPS: - header.seek(20, io.SEEK_CUR) n = 0 rawmode = None if pfflags & DDPF.RGB: From 17be898fc41059847b7a094acdadacc648dd6e22 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Oct 2023 16:58:57 +1100 Subject: [PATCH 40/58] Added tests for unimplemented bitcount --- Tests/images/unsupported_bitcount_luminance.dds | Bin 0 -> 16512 bytes Tests/images/unsupported_bitcount_rgb.dds | Bin 0 -> 49280 bytes Tests/test_file_dds.py | 15 +++++++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 Tests/images/unsupported_bitcount_luminance.dds create mode 100644 Tests/images/unsupported_bitcount_rgb.dds diff --git a/Tests/images/unsupported_bitcount_luminance.dds b/Tests/images/unsupported_bitcount_luminance.dds new file mode 100644 index 0000000000000000000000000000000000000000..f9bb82254bc976d1cd130c690720fada74d1ec85 GIT binary patch literal 16512 zcmeH}-EHGA5QLR$;4W|X7^H=3!5swYCJ(76#ia!we>Mbmxid@IFa*O(Adcw~@eMx; zf=;Kmmyd1Re*f9F&+`B0uYBD-UfPOZ?elM6eJ=lg{ikdie4wCtD@J5$rZg$Odd=pyeTl@O@Rr&tAImS3LLsp;L!Id0QjK*;D-W$ zUsiB1AL2y-+`b5a+g}qv@T~yBw*myeRN!Df#TNl|`$YiV{(^u9=Lg$A2l~GQ{ow&5 zpBqU3+`zrxe;YskeE#v{zyA5p51_m@(gG!?cVO_^Sz~$wl>F9wR-n}<1zJu7v^@NP z24p2HAUP}$lKTZm^U(>6`arZRRKVo%R5fre zR}Gw8HE{BUpWgd`SDyNS`M*A3P6L6%3Ic}}1b$%!MXwXnRnhCjXRluPu1rA*)aOis!0Q^t@@IwK>FDp2h5Ah-ZZeIkz?XL+S_*Q`6TLFS!DsV8L z;)?*f{UU&Fe?h>5^Mmcb2l`Ke{_p^j&kdx1Zs6YUzl|S%KL7afU;q5)2T)!cX@Qc{ zJ1}_dtTDX-N`7lTE70nb0xc&3S{{Bt1F{kpkQ^2W$^C+(`RD{jeIQ`uK)}f3FOCAW z$Z4Q*r-91D$yC6smsv0<#s|HT48aVmFPp^HzE1&y-`BNV-r-8s>1%bl~ z0>7|=qSuM(s_1oMaz(EblLylYZwgF!Q((gHD&WwS0*9^?IP^UV0DdR{_@MycmlYh$ zhjsv0<#s|HT4 t8aVmFPf`J1DHUKY6=0r08b||aAPuB}G>`_;KpIE`X&?=xfi!T4f&V(0wWO3`WBWU!qNVX+enu)`gQ%*b@>q@%L571w~e!u;> z-EQB1+-`r%^T(fWW#M?dm7JendH410_P+gmT-{0y_oJfd!cAj|lv>wpmpf3Abm69* zm`MBWti2s|ko4A*;=OID^631^wTk<-Nr|M}o(OMg#j}@=ZCdM!nUHkzQ{Z(?ICs%$ z-RpL3H+xkHDXTuTj2B5C>sc&TJ?j7~VYmO7h6()0`PQpv~|9WP|PZ!QE$ zA*bT^#p18%V1etc(Lf|corK@YBtFxr0@qu|fk=uv3BQ%edZr_VueX1Khl4+X>G&+^ zgqx)1BtgmD6Hi&oJ>=5H=3I_+Kh-@Hh0?#i~Jb* zpIyoYa-(()O`~F?&E59GB7bTBb_WBU2StP3_VNdQr2Nlbj|CF1?bEAt){a%X?M+mE zy!>;Iz&^UlX!WDoZEvFTm-cU;+n}>3GVHduazBFf0*`afSlNd*419(jLp%0aGmP(< z_MQv_!~b6P@hVmR9iChu_rvG2`=hhS>tsFfKR7J-AHRbJ3a4(vKm5;pM~v;w(DC^{ zdVj+Iz|i3KtiRcO&AVgj1Ad&GYN(XgjOgBTAJ3hvNgVr|-2wl-6P&d>&WHbN{g)F; zI)Zxknm4**Pv!RnI=D6EQ6P5(|MCguTHn#bk6=en_>w&EWr6jT>{uKNq+bUff!M3V zK`TY%M;?J(AU6vCj}y!_-K2*cL6aWzDb(RV1h(Eens~3k8TZ_$plnX#K-{C``%ghz zAbp?X2uwYRdiy~W-*W`E0%z=2o`SoTzVLe2&DWlSzrabE<@Z?p5m=kUOJpH_V5<}* zYT-eeVp?;Q7%?&FW|78&H04Rwyvbq2)cKIcH#`;J)QojApIsSI7E;pqwkP2$+i+~- zBRe9Ux(*soxscx}J%5W&>W+xbxM@7`624e8Fv!PrN5rn&G@f_~Uo09G2lwL%G;XK3zzpHW@$rfpW&P` z`A~t86Z`~r%TZKR(wY|yjKF{Y)B;t%DylDV^6slj3!H1xa6dh;&sRnD1y0_5RcV29 zO&ac}2ln}@sJ_6-yRRxOaIQ(i{q(>-Ulr9CIDa?QTWN1~{=DHnVRb**{rBz7-_7oS zb^g5JAt6aot-CtE4wAH3-A{J^N!lqkd;|(F>(%*(aWXCg6^ZSw&KHLY46g1cyZ=Dd zEmX(|EqIBq&hLyCc*|``*~995<(M Date: Mon, 30 Oct 2023 19:53:51 +1100 Subject: [PATCH 41/58] Test BC1_UNORM and BC1_TYPELESS --- Tests/images/bc1.dds | Bin 0 -> 32916 bytes Tests/images/bc1_typeless.dds | Bin 0 -> 32916 bytes Tests/test_file_dds.py | 15 +++++++++++++-- src/PIL/DdsImagePlugin.py | 1 - 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100755 Tests/images/bc1.dds create mode 100755 Tests/images/bc1_typeless.dds diff --git a/Tests/images/bc1.dds b/Tests/images/bc1.dds new file mode 100755 index 0000000000000000000000000000000000000000..faec63a00ded5e10985a385d946c243f28f5a1b6 GIT binary patch literal 32916 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index b11b8db3e87..bed143656a5 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -15,6 +15,8 @@ TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" +TEST_FILE_DX10_BC1 = "Tests/images/bc1.dds" +TEST_FILE_DX10_BC1_TYPELESS = "Tests/images/bc1_typeless.dds" TEST_FILE_BC5S = "Tests/images/bc5s.dds" TEST_FILE_BC5U = "Tests/images/bc5u.dds" TEST_FILE_BC6H = "Tests/images/bc6h.dds" @@ -29,11 +31,20 @@ TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds" -def test_sanity_dxt1(): +@pytest.mark.parametrize( + "image_path", + ( + TEST_FILE_DXT1, + # hexeditted to use DX10 FourCC + TEST_FILE_DX10_BC1, + TEST_FILE_DX10_BC1_TYPELESS, + ), +) +def test_sanity_bc1(image_path): """Check DXT1 images can be opened""" with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: target = target.convert("RGBA") - with Image.open(TEST_FILE_DXT1) as im: + with Image.open(image_path) as im: im.load() assert im.format == "DDS" diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 51bb0201bcf..4670301146d 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -411,7 +411,6 @@ def _open(self): self.fp.read(16) if dxgi_format in ( DXGI_FORMAT.BC1_UNORM, - DXGI_FORMAT.BC1_UNORM_SRGB, DXGI_FORMAT.BC1_TYPELESS, ): self._mode = "RGBA" From cb554c6d0ff6e81ffcf6678f083e7c2ac6d5fdd6 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Mon, 30 Oct 2023 13:55:02 +0300 Subject: [PATCH 42/58] Update src/PIL/DdsImagePlugin.py Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/DdsImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 4670301146d..581e60a3f08 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -263,7 +263,7 @@ class D3DFMT(IntEnum): MULTI2_ARGB8 = i32(b"MET1") -# Backward compat layer +# Backward compatibility layer module = sys.modules[__name__] for item in DDSD: setattr(module, "DDSD_" + item.name, item.value) From ee8c9c352244df448b45c263bebefff768aff7bf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Oct 2023 21:12:04 +1100 Subject: [PATCH 43/58] Removed unused test images --- Tests/images/unimplemented_fourcc.dds | Bin 32896 -> 0 bytes Tests/images/unknown_fourcc.dds | Bin 32896 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100755 Tests/images/unimplemented_fourcc.dds delete mode 100755 Tests/images/unknown_fourcc.dds diff --git a/Tests/images/unimplemented_fourcc.dds b/Tests/images/unimplemented_fourcc.dds deleted file mode 100755 index 4795573646b08ff503908e2d50ccb3a5b6bf0863..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32896 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 Zf8OR^GO=}gj=(gg2&mz%@jM&z{vSC0Wu^cC diff --git a/Tests/images/unknown_fourcc.dds b/Tests/images/unknown_fourcc.dds deleted file mode 100755 index 41a343886151ed2992ba8d00edadeb6f3cac5dee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32896 zcmb82UyL2+dEV!oEm3j>qN!!x)C#a9%0fMI8Xz*SY6T4KtH=cil*7X~Tn-zF7gi*L zC_o*9D$ks%Qm|laUwJK&H#t~z7iY(foEszZ8j#suM9Ed;3wCP<<{0ipm=U^+6$tCj zv1j^w-uIh5Gb`qz3rL^N|2gyfp6C7ZeRK51FaFz={}4sdKmN7lDC+Sa`e(vF{OLdc zfBsf@@;~CAFJ8J3Kkju$f9>oi?oOAIrHsFH>B`c3)x6eN^QW<_=9R{@&UZDAZYA-R zrQcqPCa({k<8~a!>sOYZv)5~^s=C%Vuj+J_G0&6qxusuU;zK8&=XSilx&0KQKJU4u zr}Vt~^Nhv!%T!~MB)2}p7;kQF&ihX-ZSy>hdj7os85=*dH1Cg-G%5S@K5w_JQIG$) zU-mC%TUpuvalhE9$N!hH9>v2SG3xOj^}pAj&Gfu?`(;^G|BKsv%j8Gg9u9}`yWH*{ zjiT@M-|80}*Sn1Gm6eT2d?S9V|8{?QxgWjNf7RYkW4TwR8qyVmT9cVNn&H23_s|7rx#6UQ;nR5QX^l} zIPdWvZSFgKZ@)cEG!AdX#(iZnnTqdhnq_V7nlFKSE$-ERGSaB?BF?8KvrOE#0jk?2 z&lm5ye9gwBzUAE4w~hB*p4V_^mh^R;;-2Q|4e^feY{GHg-?Zobi1ScZ;?HRth{jN5!%BL1UWL%5In(`mL5xED6g_iBxG zlB|mJWHOlr?wO5c^_sY+yUu;W8;E~al$FN4a<3BilzCy??{0Rv^M%Ddy|v)}BxE_f z;^)05?KqA5U;t-vkD@mBtf;Dn`(O2bsMlrU&Bxao8Ej0G;cA=v4-V$R?!NBNN6tNp zR%YTm%^r5R&jWYy5$`(PHQggS;~ouWGt+zEUh_n8ugbaU{Ysh+#W&*oiT8T8%{+lO zMxv3oHSQHB%DAsfzwHofzGI(p}Up!Ztb(>gWotBLgfc7JWQk%==; zd&kDAN;RhY@lf2O+tXR#&is(xE7N;^BS~KEU+k|uJ>6(?muyV$^$ zAfC=0A;6!*Htw8O)4QH0y^Eu`&&xE|m~wvP^XnLRqw{7o{(<{z=uq6RVH|&rzQO-3 z={$zJeEyxiN2zl^@c9G1i+fR&#=WlhSLN^6!$90;7~tDHzIpdR@8heL>N-b{;$N1|o#|}cr)#sU?epl)e0~=rH}30O+a2zlM{yq!hZ>)E z?!|ju?t`aI?->0Dy`T5i0p z;d^{vIVHVs`~yC}(0e+3ZfSL?PkH^e)GP3z9_|ghl%VypCsRN-?#YA(;B^dd{0yKY`$*XH#V|a(0ieg zG~mmo_jHKf-|mCGiSNdp_-@?sN#h=&=b(4vuJa(hCn*yLT}P7xy%)M&5Ll&mULJoA zzSGw?e&*b>fp~vd)%kBR*1Wu{_p%$`2e+4n7R5F86F;+%po-zeSA_r zKbd5KyN}A7EU()KJe((!jV8XMck=VXeSV*5@hp$ax1@W9QhKj=1M~Sb9yUHN?kicg z)A;;H=$p7zHhTX#`P&9>V7$j8=U&&b>wPv0+&!O9hlzZCT)W=M_s!>vGBw^A$1mdF8(3zy1yO{ExyYK#TlRRypH2C?&waucj|hsah#{Crgwbmm;Ha;=I;3& z^U8d_cAqB=u=vi~-Idw5P(j?->>pJDV(K5zW1(ztV4 z%;zcNg3lK`?^XGaaZiS>_vywIuCMkfM~pvmcD>8UrT59q^Le<+-^-$Qy^n^*U3E_I zdCTi`{>7d2^dP>A`|Go;&`6lU$5qb-pSL>hhdt({aVIUaysiR3JHC^~nBF=4p3kQ@ z%;#6K=}dlKl;c_>VIUFrA#tYR{*B%%y|TWNK92iAUPmG-zn5Obc{bfJy=z`KdKd4C z&(T=#4p*OA`pgp3&UCK)yq(v{PsE*kW%SP&vB%isyL1nC-q-a0sI>TAbG{G9cdO%e z${!f_I<0kE@8fx0lh3=)?~xFTzkJ&A`L(XkXT)dmuM^MbspA^Go6pOqP45y6oR!yU ze3*G1r#^!CbABzaix2X7kMHj=@5EhksEP05J^R_iL;3tNoImPiOic6nc-{35Z|VMK zx=Q{2r@d%p&Fgnglf`*HSbBdj9C|*_glPLbhiBZG_s0DzOiXd-{13mbu}Y0U)7yMr ze8fAg^BXpXI*#yX+{H)S!DGB1&8tM*lX@5{Z;vJ`Q}Nzrf&}h^b{$tIqhtcJ zceY+4zhk7}FUjx9clIjzeDagkM(+pl{d+vV#Q_BFsrfwfS==)Mf%5t7?JbY*1bA_$ z&T*eV(7XHmPVISJO+U+ge)f7NuOrPgy^o2X=JRlG;=6diS5&!i$H%2})uZjaj(K6+ z@snYj`|atpYJ9$}cX5}`uW??)yQ)Sb8`EU-B=^VEEv=@R0*`#euGy;n4P z+J2u8S3^D@xFgKR`TT=C@xIMw*6GuJW$Ui_d{x$7zo&VJJ975;UN!OE^Z5iLHJ_(% zA)RB?p2c@gThM#Ae&2zc`+Vy4`%06~dweg7`D@DW^AU|s_>wnu^18iZ@1}8I zT^%Z~YsGitPQS|a&b;h$U(|8R>yR69o_-w-JLz4%CGLJd{Oe>@ylJyGd7a1iaDPf1 z5NG9I9iN}qx%56FzYzCr5<83U6acRGBysNB#$Ea3E4}A?bGV8>X;J)nM$PxO&Ux5$ zUcBdHo&F?k@;dQW{`ffVRh?KqzmDL49QyX=^Og_b^Z1SVe3CTz{6SvldfzGB=j&Rx zbLORU@8&!Z$6LF z8+ZCnZSFgJ&ON1}>D)8ZJK^VwxEEDodZ&$Iys=9g={wYa*!vQBVND%RK0ldyUdI>L zZSm>ooj9w!j^i@k+q~Z|jq6=rx9HQ((>zvu-!T2sw}ZF1%Wss|RmCIYK1y#4+j$*N z@9LerM%;IaZx-K4K-&5IJ9G7Q$m_`0#eFtC%)K`5*eQDdmfAW|sNXyJ{4RbW&MUk@ z0ReH|DeEwTrzn6i#{O!lY`vPIw zx(uJ&?$3iAuIkqfmc^arGK=rmh#%sOjaxp?y2|0a&UC-?VbiZ$_4++pJCx7YwdO&M z@3_ya&ZAGCRh7o9DlEPyoOknij<3y~ zm8WoT_iaKx4_ZMgRg!Hbu*?b?ymDj@3a-%=kxT&mzWRIkGNCLWnbzIZ2h>wzZdV#Mg8u58+_RNHjDK*tKX;D zq56F=u=<^XLq5;CjOX+9D7E;W#oZ@U_sHi(};Gx7a^aoQ`0*QC*z)Z zUblyVSbQ&2)4O~?`Fw_Mr2D$z4uHnzP45W4;jXd9kX`SI=ktIRZ^fC%aUbu0Y<1i^ z&y(J_&`Hqy_L=@4tItOsN3e}5`Z^kG`ZqqlYh(Y6=XE)H*8RV@@P+7I_Dw|5$>?py zOIP2r`+qq+N9Csb{r3NUrMZ1B8rl8l6w!IS8DH(6?)6@AJ+?^KlYJ+17*dF@0K|*8gsNj+qhj?{ln_v0U9h8GVP_`rhqv62&~idHfUkeSK?a zdEF-aTBLWai(7oZnRCDD_pB6m-^nj~HKh;p){_b^HG5(-$=+*RR{_ zcz?TpcuCBM`Z&=E{6C7$tlu}Eze69~^14U$|1|F7D~Y&sSfzN& z$GhC~{7`&%y~F)?;f>u6jXMSCptp=)IB#Tpz+HJA>oQG$-ts#3V{NNnx4He2>wR;J zdLEx=J}SP~>BsbCnsu2dy3U`tALmbgPhQ75?F-S>dlSC(tNpWQCzHnK#r@LNd)y1B z`|8g}(U9}^R{xEt$A9bot5?rXoa2!vr1vBIX*|!L?!R>D{7*H4AGcTiJ|VBu>#knq zmhqSF$7yUnf9*~d*5AWEtLmovJR2X{@qNPi`3N3*A8}?HeeAlA@OAFLe#P{C`t*@w zAM|?BrOOvCe1I<;Kh`74+d6CUWdEq?LR|0vb_D%z^j0Rb={e5Cr$7D83;KRpR>z_r z-MeCAe1H9nxX-I}Y~0C@{tLI6ma+8S)bAzvy61Ii$LGaWdM6CH-q{cp^lsdFI2-hi zss5|mKF`yg`#9ZK9jEyvpKtWeG%@bwhu*Ivop16wbkF`eo-cii{}Yz)$ML^*jAO zabH<$*5isw+{LZoFTLw~1id%$-SWEOsJkvR`ya)@dYp0QeSXMsbNs;v;_KoSpE&{l zhY|nDEM^AqCx2&u3G2ylz4_%Yi#RsVpO415cl_8n?zwQ`o8OE~_ZfQE``?Vd&Ugkx z)NPyvwbQR7KAGM*!IsZcP#Jd`>I>ZxVs!}|J`aW8h}rSYes z*4}S}Of9dAZQq6Ow^V%p3h`cg-+5GyEuY^%=-U|gj0tJ$vG6~K(<4v5-@D>7eYgJw zeG&4wlhM7am&xPcf8zA0%s@c>MIS{*rEAyexk|867)vg1qjS&cv}- z`w#U#+1E?&B>v3LB;eT|ndo&N)k$Bv)D$KQ{xVu9c3y%E8m zNOvxJ{da!n>^FKZMi~C}b9|XkfAgEWpX$G;3H;_!#qnQxnPWiSN$6so$Az;;wmT@f{=a zI_~j(TH0*%4L<4>FS-7izPz(#czpG@^)c~DWpa~C@fPC+K#aKe;*dbC`|WZ0`6YOx z3k7gfC$De58{17+u&x_#i$N56U*QH+3{X_EhDEiCeJyg!^;nyPZ z|a?K*A;L7LCghn3G0zMH;{j}-XwdD2?*c}`d8 z&lgs|v#w)x9BnAyPfH=!_W81`6X~7w=U_c9?5iveareFrC#b#eqRg*&9mfHD(EB^n zO%vZWrqqubYwBIa_sLqe(e!C`n=;bt_oiRRdWq$AW%{b(dtIct`8?-e@m+CO+>uoi z-_xd!6K~o!rRDJp+7(R9^BQ3u>L&z|C+o`+;{dWTaT+T+C%Gc-nZckiaUKw_S1Ub zM)kXnPovgR#XT9N$puEr>TT(quWx-irbTM?`~Km6o$sgJd*uB(^%okSZ`R{f#}^oC z*hg*L36sWM=iln~EBm`P*4lT`>e~RocsJb1E5$i_y1OpJ@!7fz+cLaQOWv$Hj+gIw zUYFLE*P#pX=4(6mM(@PuhI`}l&+t6b7>n~&nl^ejpBJCD&zJOb8lM;Mf(sfn^7ggH zG4rzVd9A~~mpv*yz7L1ir=>rqxZdbJW1ow0PrW~{Z%{>7CQ*`z{_OiE$@zNbfAacwUDOhP*CavsL>(!*~^O{#}?xY9FQTVe=%=#>K96A#Bn)&>rgF4RhdGmQ1-_G6l+oZfs+?S23 z?XT@zH<6-8>(^m~ZSL&1v3}h+y}@-8ExiZsv<*$~s?WteVx6eo!r zTf_ZxeRzIYJ&@1uep2tY&k8?ry=Th<>(?FX&p*29`>g6DH=jR>UrOg27>?;ZUu9h$ zpHF=~Zp!{Y@n-oT)mSrMjXR(3{rSNCtIoX~KS(So%mkg>eO+( z-1IIV7x#IZz}@pY@s=NGRQxyod9wO2`U}#HY4$LBKU(AU#+|s;?$hdhJ-#=6T3?67 z<}AKzzP5e7H2-J64gD(Tey|?rK3`ThH4nh%b==YJ`a7>T?o}H5zRIV)j++5YI?sp$ z8u1g~XGOzP{8`tp#l0xTp>MP3&j+7B6yJS4?qEMH3*P4Muhp*iO8X~2>GNX_T-*t> zu6Guu+VP$3S3_|hp$q9<{S?zX^G!bgPMPFQzs`IfE^Y29>jMj)w{@9=I!^p`-dyjV z&nrH)eZG1y8n)y6M)4i<`G&jlo`=!V9&(MuosT#EObh(I>D%n|RVTRKo9iar=Ns<& z+~D&&8sRq9c!l{c?km~rQ*qY&o9@}qrE$#svif~`yXnuH&u8ydWBGl}{!;0CvKI7? zQRsF-oDlbVcq^>OXv)G}Q0QgU99br2DtpdY=_lVS4BN zZA{Wc+#}A>VeY!^{N2BU-hG?bh=$6kjFU6TnYb@vx}-P@4U{o;K7@U;HXZjMJD-!Uq2N0){9JCB?05p-Mc(p%f-t2#06x}H^>dAQK1IN{ur{r984iB|YV z&RunY_i3$;v-r;XJ8#_adA@+SlNOoJugq?n&Ktd#Ol;$x49Vx)`z}IW#{q~tVcxh; zj)r~IOf+%lev9w;fz@&BD{ShxfpkxM-FV-peS%FQ_aTnI`zPW~ z+sEqi^?1m-3>=q^9dG(|Hjh|XI2m1`V<6ww>n@%b_j7-Fgg&fpfBC#VPkE~M=`VVp zHokmSReSHqnRV&?!@V*2oceXnUFT2S_voL5ye?N>M?AB2Sn|=1&ojNr=a+fM$yB4x zYuo4X1M>O5Wt;8Msd(#gjbL@XQ%H#~m*q?}vJKw2D-Ia-$oeb3asBF5jYnwYos78dg+HyoUAmyYJ$YTOe4hAl_TD{lKXc;5 z30>*3%%5(bW<5iC-`w8X-ij{R`cM?T7@eKGK55ovq7mWFZNkk9?$E=owiY>k??7})h(0W<7{&Nd5tXlDMOzy0W>d| z_?HydF92TpzIfp(U02KJ_&52%r6Ko!NBz5h-s2ukDBZqreiCV24t?|QC_2l!j(DFq zL*o&r*Lsu2tCPt3c3Ji-;=NWa6xWF^>t~o3=sNiPf_suI>bTGLbX{zRyVY?|@D5ME z{y4qYd74-qN8%>VoY$_;=gFv(*YW=1t$iQHz0PZKR~!&;F5fWj8QgW7>0^3dkf6urGM+=z9RkWb7w>uyfE+2Nik9NhEI&Y$pww!U_Fa(y zEP=@CcV2Jx`zXdI)c+m~z7A(rn@sP=KlU+t;`C|heaeZ^sJzPKd+K`Uj;3EHz3Xus ztAzM&`!0;T^laQYE*mK%Jg@5;_YIyP{x#fe%Bje+%KP(4n!6Fa@_T{;`X{eD(0gwEI?b>4J}YsT-Z^c7yXW%<+<9Ew%ld}-yw1Bgb6Slz z-^;nziPi7ec*EV}JD=C^{~I`yUB%)qKhU^`AjF$}bd^T^{(y7Ovd!p^^mWYl^HE|x zPkr3@yytZ^2BmjCo^`nnck}s-059&!*G=!yMklXp`t&KUe-{1gdVzLcC%-bEXMbL^ z9;d$i)3b;Ft;;>=J@n@@=C$Q@<0SF?PQQ2YcRigSaX!#{*>I1oevimI)wj|2mfksi zwk|_{V*9MPj;QI+7s}_U=d6C0kDK0kokm{Qz0&C2xZCsIu=sE5GKci8 z>(|Ad(+hWee(Jw(qwgo)O?>BfdtC2FH|+YjCZErC{Ce1g-%$~7e5SMChHciyoiNt9 z4tgzncSrm)=7sCsxew@bo6mCqrt`%6^A!_IJ|I6d?g&RC$7g!ydt1M*;m-6UzQdo1 zE}zd6=T17?a4*HZsC+$+?fSMJH{o@rcZ?*|aklS5$JyRbOWNi8E|N5E`~37U_q0x% zd|q*UJ>I?}-q=atp3_^x(bIw6o4!p~?+5EL7T<3-+{eD3w#@6!dK?cs_uAKGl4RHPzCzzg zyv04}-QxQQyZQnd9=3j3`#gC#)_xVniPmNI(Yy8QFjCX|0r#}Fe7+UmP48^1JV|B1 z>-*0woz(fg-_Gl@BD*QwS67l%%jcP};;nh1@j&l+ed}TLx01X0ydD>K%?s0en&#q8 z7&D*edpmcYApShf_>bzKcLdq^{8sdaeU7-#t1Ic7+~(z;&+|iX(tBlfocp}`busyY z>pkqBPYEz^R{UJl@6XbQFUN}S4~DlauhYKJ;PV#Wi)zpKr~E9~ljwWO>qCG3@P3Bn>31coTR&&P@7byYetUGw{RQ`fupNiVL4)$7E0GI`x}E=d>KG$DL%S z)sZ8oEw5u9+*5p~Z88ye<;%{!@%hx^yW&+_@5)<3d>{7MUqoYNpbAm%<8eLYb@?#q z^l3R^rgvU%{K*e|U1l~FXSiElr+H!A>y*6iq0I|%Zt8dBYH_`I4i|^!x7T+gR)4TMf+nf)}>yjj~^|)v@ z+fo089WL~7G=6OT`REs3zei83$8XYi<>j*`pFf4dks;K1dg=RZq9^XtkMMoa=J%D> zUl#i=W^izy5BWU0H}3p!gZFE{c2V`aK3DmB^qXOfmdM+`Zu{xv^Y3wD#!D|otn1hg zWpNYt8a;nWuZ!-TRVJ+c-s(3*(f^^IW2oZz8I2t09RC2*ubx8pdY^Z=f1Uliwrh>g zxA8Z1`y?;uAKQ$+r2J#>kXzAY7~5FKK60FN@AP-f_3l1TT55V<6_m%iw@OsoL^giCq9ZZ;lCT-iQksb z;|J#R99N@v@xS=BvuC|udjHHx_RU9^e4R%7!mVF>{KyfFG<0p>r}j%*9sh*)_u?z} zto}ZILg&r;Hy6blJ20PDou}J14MXF;)?6o5mg27MW8YBS{~Hl~e(pKFZuR;JHlB(* z%T@>d`DE#Z4u6mDIj?)w@_+G;lk?|&228Gsd(3fa{fX~$PkfJ`IdNb6#K}|8zs_IA7vURq<0BpNPosxqbP%@jr6<%o*uiK4*HL%^o(~ zZDieA+-aAF_?|iUm+mq@7V&+ay55P_2YMIxb*?9PBYMH&f%MJcn9oygnBF<9&OPdK zR~)zXI3}dx`pWb#tbSMgRUTi|WfgHAPVlFBaE8B7$C203f8$StwJ%ts`f7A_qa(+U zGqNs2g>KoCHMNryQR~KE6zsEdFs_44=aW!?2(3qSk|u zPoZ6d+J7(plp_}3Cw%W0ROcDDx6HQ{-byDk5a01x z%j?S8^v>yVpGU}VG7l8z{r&PqjrZ^?mQ(*Xtj`^TH+d3vS-j z_ZENsn|{*yfB)|^a#f1yJ&F<=IXyP&y!|9fpZ*>u|r+S!AC7Kq`5+ul!Yg`eW;Uos_l> zTfg?ZJg5JL&1-Sbo4k&OmgVz&fAO!$TdZHl;rqJGHt+XJug7_H{d>Re{(p{qKQ6~3 z-ly?zjXF;AckXY-XLNkr)_9z`pnb;VH`MV_bc^*L^K)EI<9*&|kHBELi|;Yc zh1M4$9M;zjrRS5;f8;UKJ8>%f7xRUC{)Og^j!b&?*PGAtJG90f0MfhiueQ%Ws?)@| z$HNz+OBI8*IvH6#XK~?q>%+eZkK@c^ z5x;o;>iLGde4cGYR=;N?qV7~2r5q$*dd>cL;?%P|Ycit}OeV=*pLPQ(AegWSH!{CMJ z3pP*MbsT=+b=+>#pFi+<_@jHcnBMW1Hush6r-$}g4F-YxQLEzucV2G&I?}h!J}Wqx zzt4#;=JUM2^gWsS?_FrV>$dV)(>p$?KCOK4>%Fg9KhN^IN7cT?cj_C}aZGFD&U_1f zn>x4pofF*d)9QW2y~b!3@tu7aobNXyGI~B={$4X0f98QWYd&ifSB>L5NyYu?z@5u^ zjr$H?+_~3@Ms_zkcU~{SFRMNK z{X%{px8?KvUghiMA-xYm{jUBT>oS}m>71(!tUo`0ki)lC$Ax^pWaFf`Gd+wub>xP4 z&uB0Q?uocR*p;8tpQp~z$k)9s-bMMq`m`hRT)VUI&4N2+=}3JW4zCdR3H@xv_dD!IP<+=s_V}LvB)=e^HVZxt=R>;Jc?x=mt9-s_eEyK$ ztLhKXKl44;JV=JM)p7Xj!_GQvF}L_mT1EecxIUfX2euyfUemV;`)$nU2lUg;=NrAV zZe-l^^j15*>->v5cH{B=pg-@|O~}t(?;hXBO@E&IwV!rpFHOwn=VOcS(t-8sg5G)C zz&$y*UPeAY;famj&F5KHY5P3k+4Qd0i~HU)8huAtSf){7S z35|MvTkmWt;a>IYHjF!8!~CA7neJ=$4I6hJSor*+j-yYJD86rUV%qt{%{<5#jaDF|n3wp2j-RD;(&Kr#3x(V0&?rO5C_^$P7aWCr1 zxtCYQ()+C&;kpUVyYjn|6CZp&ZTr0DoA~3`zV9Nf19vLMmfodj_xoYst|*Vshd!;& zv+I2yy)X9D>ixxAk309&@;bH6j63H=BmPrqWv7UsuYd&x#-_xU8X_>L~5bMP2iUpWjcsKhJsnDt%hE2aw;v|Iyrip34JF@8nhDzPK*(AfK-k-=#33|^ExhZ$A67`v!C|$pm!d2?rEARpVzz;=cm`4J84(7 z75zEqI~&OFDYLu%I{tqK;?2G&<4)Vvd|t;F^galAU7=C_Vm_azx%oUNwjJL&EOf8D zZV}&go2x5s1n#%R9XroXasOBNy&H?~)R*%42@5XGy3Ap{8+SG+h`X*w67Q1zq{jV0 z!tc;7uA2Z)SMRy^X(NnSbsXuN*YA8E~j?4wI@-@3Myoudz-^U&mV4fjL!`)$wb zblpU2pH)`k2mEef6@30^@cF>Ku7_5~kyZ!2XROPRuQM@2KA(zv%KEi&C+~6Y;kt>U zZsqf@D9<05&m&mR>+)87|7?$+eK5W2`?%io&icD_;r{MEU$S4elh0?xBXQU3HP);z zx7T5t`W-zt+`WFcejV|h-(?hU{ob^5PtE6d`5*WwzH>S9w&is%Db8~b|DPM_p7r`% zKCk69%jc)JEv|F8#$Eno+%YKAJ3lnO;NPy}4)Qwl`Gn71a6g6YW~TSMT<0S0ig&-m zOyKS7kk{G13+>)_n%=8q-+spuoAZ4aE0fTt zb?(@tT`yCQRxQ3O-wS$opZ~D3-#bzKw0s_;G433%b5Cp?mUMys{4+iIaC1FusN)jP z>p~rePqn%4!ByOElbD#^37i^9dyIQZd~WA;xA|hyyXJ-KKKJ+@yU)uHq<7_Yrh5`b z`8>ZUhjXW2ru*UR>+fOTMdS0}^EwWn{5rZ*dIyX7`yRR!_nLX@dglia!~VK2(GoM%AOuWk~#Sa*-g!T7Bc^#+CxYvREbh^0yJ@|ZL@tyvH`8?m(^15t`I$qa9 zJ9q8JQJjaG`8;2*a=jnqb*#q^luz349%!GFxNE)zy${;)9c;#(v=qKv56f{|{T^?w zJ8$PcCLxyIn|_^skGZ3F5&*?_%G40wy?)bm3F2LGK8-thwR7KW>UZB~#d&wVdw-rb z1#$jbw8H!fdUx(T&wYOXC+71-HMcsB3wYY`ov?e*x6wRE(i?EJd>-x|-`m{fTgIKy zxbG(91FVZP|4v4<>z|%%G~B~}+D-AM+;X3<^ISepT5S0|Vb=7HPngfsJv6z+xW)Ht=soy+$m_YL6(!1i5e%FZq4NB?UL*It{*77>$vH0`8LGMk! zuG_bfA4u=>*V0rzpN0BeerbB=_0B)d#aTY4k&pMhj@P^1Q`=t$F3acj1nHh>*~E9_ zUX4Qi&b$tN8{>{GcpaC9by&`~xKE$nuyvVhceb_;={;ZQ-1>EgxOaV?)4nSHye)Aa zy_0ZzUYBi&ug;Ul_pxy&9UGqHd4sIaqo)+M8KVfE==_JA{{y{SKA)1fwfiV4WY@%8s;8h(#DJiwI|pEO94`z$@7Kd(4C21V2g~Ol zaYD`K>xBI`gPwd#dSBzX+VQ<6Ke0Lv!He^>*a`W3<=in0>7B4>eVa9W&h*ab2kutK zVMiUk&x6lvU#sig>$oPqa~Q;b#dYWYVd;7&zjf}MU|WYJEOfYYBBgiona1Y_ZN00G z3qGHUcYf-W0EU&n3!p60p6Ip9BFK5+UPy&Lx` z-}m}`f8_c6%0{<-=lsd%nHHvZ{(n#2w<*&H=JR8Z^Z1|XT^}6sI`?_bL+5%K9#?)> z6g#EI_hjTgAN0<=XmjW7L*M48aev4UyNiF5*Wp*Ac3wB-_2MnRaPGC|b%DG5NO~{d zo14#oPuIuwW&wQSFYTS29)?q)Xck0k4ufrGk z#Z&62;PcBG2e6ag@BZGV#doGj;64v}|JZz9^Voczc`n`z2i$d={Kc=E7<(N@8E4$( zhwXipDW5NYr{J`8SmnFzI_?TwmCteg|HJV6Hifuz`G2K;oqnI*^SVR*I{ZTGapbY4 zcc!J+@0=&+?$^!edr0pbR)_mn(+9qvmh1X0zF*tk7Wa$`Ry5Aj)cE4_6LHq}GVTbq z&0YD2<#i+gP5mz3+RvmhNyGIr_}1g{`Krq6(4R-=>eu@E`!)VUdRH6{+!MupWI~^| z%e~vLs}pfgciVN`7oz2(eePZ4b)?M=_ewe^ok>ma#=WI?zIUT{bV^NaZIbsPz`xZmP9#9i}FdY9fpURN)E@8YvP z(k|n!zK;9+?k(55*KyLT#dVI?bkBtJ`rZ4qKg|mBdBt6E*YUOWu6ZTi2iHZWb*;RP zX>Ht35r>*OPQ3Zus#GKA)AerscItTEr@j8pG)i6XG5abvWozd1@twQQodYrM!RL$Y zz~?Q#>$t?7HfPhHH=j>g{|$P_=jqpFvss(F&Z9UNRq1-qYxXOi3vqqHopw~iy)f>) z&PE!e&R=!BUk_uvYh6##s^cbpy+osT%@@n($-9mF_FeDOa$3yiKde)YO&vGo^TnO* z$qo12u>Nj&o%E=@PWo`};df5J-O)S8E$;Yu;(48OryQ`n4i3g09ErFeCBU2BIj`LOyTt{Y~Tk*nGa;O~ro{ zC&QrkgZOUiuY50$?_phLf5`K+F6VjO8p4pz|K3ZBew}l#P3I$iKSZ}V4EK3XsJKdR z8X25B3n;qH#~JsO6D9R?*IDsy^j`D-HFTd}n^-HCYf=7n?jzRiBnJ2r3Y@BFMmhdW`}^^R}1xhs!Q9amSW_3N;2>(}xzO?)rJ zU+35Lt~yP5UD4!qrgwcW@on{O%;%{uJAK+^l-$<4e9Q7W{)f8ay`SX#4heCf-{;ms b@4EiH&Ant|>-HRhX-pAN!(HQfHs<|5>v?9Y From 5fd3035362eb8c4ab7fbe7e9e6549b3d98d0b68c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Oct 2023 21:24:42 +1100 Subject: [PATCH 44/58] Removed duplicate test --- Tests/test_file_dds.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index bed143656a5..098d7da8b66 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -210,12 +210,6 @@ def test_dx10_r8g8b8a8_unorm_srgb(): ) -def test_unimplemented_dxgi_format(): - with pytest.raises(NotImplementedError): - with Image.open("Tests/images/unimplemented_dxgi_format.dds"): - pass - - @pytest.mark.parametrize( ("mode", "size", "test_file"), [ From 881461063594ad6697cbb451e07f75c0b200c679 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Oct 2023 22:05:08 +1100 Subject: [PATCH 45/58] Only unpack masks when necessary --- src/PIL/DdsImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 581e60a3f08..30f021b7dc5 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -345,11 +345,11 @@ def _open(self): # pixel format pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16)) - masks = struct.unpack("<4I", header.read(16)) n = 0 rawmode = None if pfflags & DDPF.RGB: # Texture contains uncompressed RGB data + masks = struct.unpack("<4I", header.read(16)) masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} if bitcount == 8: self._mode = "L" From ddcbfde4468ed664e3331568d331440677e01682 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Oct 2023 22:22:37 +1100 Subject: [PATCH 46/58] Test BC4U --- Tests/images/bc4u.dds | Bin 0 -> 2896 bytes Tests/test_file_dds.py | 13 +++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 Tests/images/bc4u.dds diff --git a/Tests/images/bc4u.dds b/Tests/images/bc4u.dds new file mode 100644 index 0000000000000000000000000000000000000000..7f9f050b6f16f5b9e86979cff742697ec2d9423e GIT binary patch literal 2896 zcmeH}Ur1A77{))hH9cp}%{c|7Y_*k8hL(EaA8^!maRk9M&5H=NG8rt9@E;mXg&+{p z(Zta%M6=K?iqtD-4YjKr36YqRXv$d$VcFTbh*F*DuO`K)jX&8@gtHq!VpQJqyr@<6 zSd*3{CH9u#QSD~Q48KRsX_qoedOaCa@$1BBFCXHQ1h2D<=s4{=%e;#iM$~KIdvBkO z_mQF#micpuf!xMXsN*=zYpA#SN5%T0pYZAk<1U>TQTop4iQ|@)<p!;swTXx2P^AR+c~E4$Ik4A>vNhk9Q^T2VHR6Oz94 zgyvx%F$&Da>Y*9cVnD7-R_(8k5f;?#U$u=Dksl?c{AU{Z3i1y3Ncl zHw~J)ctL82PVX;#F0iRP_3z^ft$5ucErRep3YU@YZ@Ws@ zS6@SU{xn@@v5~SMgZf8P>3xclk|A}She7xrh093yClW$5@$@(EKmX0o!3O^d^#|$N fBSjrNLv|K@1~JCK7_*6v30+r||G2(fORv8Htp&&- literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 098d7da8b66..b1fcab249d4 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -17,6 +17,7 @@ TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" TEST_FILE_DX10_BC1 = "Tests/images/bc1.dds" TEST_FILE_DX10_BC1_TYPELESS = "Tests/images/bc1_typeless.dds" +TEST_FILE_BC4U = "Tests/images/bc4u.dds" TEST_FILE_BC5S = "Tests/images/bc5s.dds" TEST_FILE_BC5U = "Tests/images/bc5u.dds" TEST_FILE_BC6H = "Tests/images/bc6h.dds" @@ -80,10 +81,18 @@ def test_sanity_dxt5(): assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png")) -def test_sanity_ati1(): +@pytest.mark.parametrize( + "image_path", + ( + TEST_FILE_ATI1, + # hexeditted to use BC4U FourCC + TEST_FILE_BC4U, + ), +) +def test_sanity_ati1_bc4u(image_path): """Check ATI1 images can be opened""" - with Image.open(TEST_FILE_ATI1) as im: + with Image.open(image_path) as im: im.load() assert im.format == "DDS" From 940224eaadb27cf4fbc21583b13da912dca3b425 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Mon, 30 Oct 2023 14:32:20 +0300 Subject: [PATCH 47/58] Remove wrong test_save input --- Tests/test_file_dds.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index bed143656a5..def548a34c5 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -375,7 +375,6 @@ def test_open_rgb8(): ("LA", "Tests/images/uncompressed_la.png"), ("RGB", "Tests/images/hopper.png"), ("RGBA", "Tests/images/pil123rgba.png"), - ("L", TEST_FILE_ATI1), ], ) def test_save(mode, test_file, tmp_path): From c7fbfdc795caf82ace4845f56dc32a3f335dbfdb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Oct 2023 22:44:52 +1100 Subject: [PATCH 48/58] Removed test_open --- Tests/images/mode-l.dds | Bin 16512 -> 0 bytes Tests/images/mode-la.dds | Bin 32896 -> 0 bytes Tests/images/mode-la.png | Bin 1060 -> 0 bytes Tests/images/mode-rgb.dds | Bin 49280 -> 0 bytes Tests/images/mode-rgb.png | Bin 862 -> 0 bytes Tests/images/mode-rgba.dds | Bin 65664 -> 0 bytes Tests/images/mode-rgba.png | Bin 1302 -> 0 bytes Tests/images/{mode-l.png => rgb8.png} | Bin Tests/test_file_dds.py | 17 +---------------- 9 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 Tests/images/mode-l.dds delete mode 100644 Tests/images/mode-la.dds delete mode 100644 Tests/images/mode-la.png delete mode 100644 Tests/images/mode-rgb.dds delete mode 100644 Tests/images/mode-rgb.png delete mode 100644 Tests/images/mode-rgba.dds delete mode 100644 Tests/images/mode-rgba.png rename Tests/images/{mode-l.png => rgb8.png} (100%) diff --git a/Tests/images/mode-l.dds b/Tests/images/mode-l.dds deleted file mode 100644 index b82282587ec30665fceafced278f1c59b3402ed1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16512 zcmeH}-EHGA5QLRH-P>I{2-3o};0}Uxa}TK}#ia!we>Mbmxid@IFa*O(Adcw~@eMx; zf=;LR*MHl#{rikdie4wCtD@J5$rZg$Odd=pyeTl@O@Rr&tAImS3LLsp;L!Id0QjK*;D-W$ zUsiB1AL2y-+`b5a+g}qv@T~yBw*myeRN!Df#TNl|`$YiV{(^u9=Lg$A2l~GQ{ow&5 zpBqU3+`zrxe;YskeE#v{zyA5p51_m@(gG!?cVO_^Sz~$wl>F9wR-n}<1zJu7v^@NP z24p2HAUP}$lKTZm^U(>6`arZRRKVo%R5fre zR}Gw8HE{BUpFaA4SDyNS`QJWZP6L6%3Ic}}1b$%!MXwXnRnhCjXRluPu1rA*)aOis!0Q^t@@IwK>FDp2h5Ah-ZZeIkz?XL+S_*Q`6TLFS!DsV8L z;)?*f{UU&Fe?h>5^Mmbw1o}^b{_p^j&kdx1Zs6YUzl|S%KL7afU;q5)2T)!cX@Qc{ zJ1}_dtTDX-N`7lTE70nb0xc&3S{{Bt1F{kpkQ^2W$^C+(`RD{jeIQ`uK)}f3FOCAW z$Z4Q*r-91D$yC6smsv0<#s|HT48aVmFPj7v|D_{D6`BNV-r-8s>1%bl~ z0>7|=qSuM(s_1oMaz(EblLylYZwgF!Q((gHD&WwS0*9^?IP^UV0DdR{_@MycmlYh$ zhjsv0<#s|HT4 t8aVmFPf`J1DHUKY6=0r08b||aAPuB}G>`_;KpIE`X&?=xfi!T4f&Vw%wVD6` diff --git a/Tests/images/mode-la.dds b/Tests/images/mode-la.dds deleted file mode 100644 index 30bf93576fd17f80397a1a016c3ee2306e4bf28a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32896 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 azyJ)u01UtY48Q;kzyJ)u01Uvuyn%m65T+~u diff --git a/Tests/images/mode-rgb.png b/Tests/images/mode-rgb.png deleted file mode 100644 index d0de36d82a9d9b082ec3fd255fc42886698c2649..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^U{>{XaSW-L^LF;dpfv^ptmZ%e z*B@gKbQCh1OPc~>jD zxZRUqi&<5!Jo6X7W>+5ak4dX8T`%&`UAI&54yRW#he$7YeKOv`@3$Ds|DO2_TNaw< z{N%P+u`et7`|K&KJ_X!d%l6H2%wMkG$^ByibCY62!m7Q;FNuHn^|B#>LqURxqm2P1 z$e}PJtNzp6eGS+B*;$+!4qlnQ`S_`q-jA55G^>NSrIU+aF*P@3_$rorfYoA2)R zdS;vN9sOR@uQ zero={vDNVP)GyDN!^DgVuiWb7lLz|m;oBGLRg8x}Gp-HRJoWv1@$c!C*BO(eGk*GP z`aQq+^Q@15Y7X4_{d?zMEvwIr-tx-F`cJ(RFPh08pz|nAJN5f%%k%6V+Z@hMzWctx zUGSjHGv*~5<8yERRonNBndfWYxi@w4z{pyupMK@`zkhXk`$erl(Y~zZ&(G=HukWXV zJqYnQI1GGB?SCJu=a&TqMsWN&J?-BhtHXfF0AyaE{M?&=?f5=3UaAU@F5Axu^z5AH z%q|x2+PFOA>#JTE3cgko-9Nk_A9MutdWXZKIf$h9}xaC2z;Q@GpCXd~g$#Yfqgp zMU-n#ohUz#{qK>19*$^R5y185`X0V_ZAJN4=8y73>MkJMakIRBDLomEYOaOUE9yvzHuUuzApVu|!a{?#%R z^cLY}RYU$k(4Vqa9Kn7(*ZK>svJOY?$-nVk-UTn~&kB*;i#GX_e_RH@b-Vffe`kLV z_qeoXlzT98AICl`ck;4sPyRp1pX+~I26+ERuKig%j(t&%%s?7LCRyx$sd zA-~8ej|>P?^~iuIKR*M4`~sKAfA(`=0mwH)n3;ha>3n(S8laM&w+1NW7iwae17KQ~ zIe_HtasbFLQeu&TfvH$zU`T#E0|W94l_EJX96pi*Q~CZJ7|Jiz)nj}7*V;S=nv(cW zYxB@gUx9yVZY~4x7isa9&RdSM#alXWso&)N&a)u#)m|InAo0~+8_|cjpEEHufVg5U zUrjq?7FxcVc8I=`KcW5oUt!k7aqmPkK&##Stmes;$rxEIYB zV`%duiC^%L^kKj+LJ`!7Y9oA4cASD)nn zAH(ji(nAiP~Kluk{fa`zzZ#i7nhCufDa{>92e|QE0p8G}8T|JC_tFQV8N4zqdJ{TI!{e)hAz z?*AaX&Vfe;q}kStX1$m6UDg3{0I?Uz-{G@hEaF@BnHjhhPYq_>_qkt<{?s)^zlk!21LebXF31BfrO$iT=fEHW^nZ<2wb0jyDs>BTE3cgpuUQ4(!Xto=ttFPc%p4IbJFlc+bH#Kc_w|RjYdDR z4a&8rPMROewWsbzAL5$y)pe47^tB$2Xj^HHJsi=tH~MOhNgrw}=|^5ec_MXJ^F(lC((kUX z^&{US?5t|?yb*R*HBbF+c1ho)CX##6wql9&^Yp*@`&=u|t$xUVzyF%Q&qtW{)R2F< z3uE>@tR}S$yZ6(4vbM=OKGIJN`8U-`OncT7@(&CE`Sbn{&H(R!-v5yq;PYSjdme|i zA(DO8CV%n|%|PJtze_vZjpK|}`X z>_YL>N(O;p)A_F7yu*krm z{%{6H^jE1xa$q@jBnQU&-8rz-U#TxC3m)T?vf!lO%z}qLHRu2xpaXP(4$uKQKnLgm z9iRhrfDX_BIzR{L03DzMbbt=f0Xjej=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm m9iRhrfDX_BIzR{L03DzMbbt=f0Xjej=l~s{19ag1Iq(x4GQ@5G diff --git a/Tests/images/mode-rgba.png b/Tests/images/mode-rgba.png deleted file mode 100644 index 33b41a547265ca54a15197925df3adb7cd06ba71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1302 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV7cq*;uumf=k46P6Yp5?v^b`2 z(6nH+7PS^@e-QB|mEZVdQm(&3V(BwM`sVSKUc( zU2*1$O=ZLVH|eeWR=f`Wd$K-k-^$3fojkC^KXl^iV znEG?gJP_WhX*i1^+@VNJ>G#X{nlbUfK{}(ja&-)ef zYW3=WZw2z=`4kx^HM4o}Nhw%nG(1@V=A29V)V99;r1ZXbjC20myPc@*{^Z#yssv|{OvCWGewv(u1>znvZimUrr{a& zL!uMr#WH6sKd@n685hs%Z2$B*FV&@HDE8r!S55-~T!CRz+Q? z!F;a1-VGkx%Qu-%m^z;?iLL z@>Bek#|-^Qcjh$vhMteP2bnV!QfodlJT|#tq7x-$@{Re$Okk)l`^o+)+dq4Y$${{L zCo6C9FR=dWEc^DQ{EGK-*pVPAz@!{eZcp6 zwv_S`1|e|Rimd-pZ_)5tTzP62P|Hj+woIVG@c1RuKl`n@qhM$ z^Z%-~m+ttdUvaL;hS6%-=A*2$7p7Oa$FGsBT2}t$u=(K%`vWZ>ttWE#8YI^^*Y6Tc z>&bu7p4;-Tj)C#>`YRI+{(H%CepY>*ap4}nLP|~W2L~w+$@4GxFFRb`kde9JKL3J@ ze-nSrdM3Yez3tkIml)z%)t~>ZV|e;f{$+;$e~;r?R?15c+^%C#{%mQh4aC{Oi%b~T zH9WcVjakC~|EX2kwtxA5>3^@^<&)eZ;=lw7;W^FmweHJ*`yV*Zq-yEG_f8h8{eqiZE1bDFjf&7aXchi$yFZ~25_jL7h JS?83{1OPRAV5k59 diff --git a/Tests/images/mode-l.png b/Tests/images/rgb8.png similarity index 100% rename from Tests/images/mode-l.png rename to Tests/images/rgb8.png diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index def548a34c5..5d5872b2e4c 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -347,25 +347,10 @@ def test_save_unsupported_mode(tmp_path): im.save(out) -@pytest.mark.parametrize( - ("mode", "test_file"), - ( - ("L", "Tests/images/mode-l.dds"), - ("LA", "Tests/images/mode-la.dds"), - ("RGB", "Tests/images/mode-rgb.dds"), - ("RGBA", "Tests/images/mode-rgba.dds"), - ), -) -def test_open(mode, test_file): - with Image.open(test_file) as im: - assert im.mode == mode - assert_image_equal_tofile(im, test_file.replace(".dds", ".png")) - - def test_open_rgb8(): with Image.open("Tests/images/rgb8.dds") as im: assert im.mode == "L" - assert_image_equal_tofile(im, "Tests/images/mode-l.png") + assert_image_equal_tofile(im, "Tests/images/rgb8.png") @pytest.mark.parametrize( From 662cb229c2eab6a6407b06298b3bd3989ae13d55 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 Nov 2023 20:24:00 +1100 Subject: [PATCH 49/58] Updated variable name to match tile --- src/PIL/DdsImagePlugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 30f021b7dc5..407d33d8512 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -379,7 +379,7 @@ def _open(self): self._mode = "P" self.palette = ImagePalette.raw("RGBA", self.fp.read(1024)) elif pfflags & DDPF.FOURCC: - data_offs = header_size + 4 + offset = header_size + 4 if fourcc == D3DFMT.DXT1: self._mode = "RGBA" self.pixel_format = "DXT1" @@ -405,7 +405,7 @@ def _open(self): self.pixel_format = "BC5" n = 5 elif fourcc == D3DFMT.DX10: - data_offs += 20 + offset += 20 # ignoring flags which pertain to volume textures and cubemaps (dxgi_format,) = struct.unpack(" Date: Tue, 21 Nov 2023 22:07:36 +1100 Subject: [PATCH 50/58] args may be a string or None --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 3f0863b1840..b8b8fcd8120 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -212,7 +212,7 @@ class _Tile(NamedTuple): encoder_name: str extents: tuple[int, int, int, int] offset: int - args: tuple + args: tuple | str | None # -------------------------------------------------------------------- From ab96324c1260d82fb3f654efa8f527bc28800e4b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Dec 2023 00:35:40 +1100 Subject: [PATCH 51/58] Removed support for RGB bitcount 8 --- Tests/images/rgb8.dds | Bin 16512 -> 0 bytes Tests/images/rgb8.png | Bin 861 -> 0 bytes Tests/test_file_dds.py | 8 +------- src/PIL/DdsImagePlugin.py | 4 +--- 4 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 Tests/images/rgb8.dds delete mode 100644 Tests/images/rgb8.png diff --git a/Tests/images/rgb8.dds b/Tests/images/rgb8.dds deleted file mode 100644 index 8193e8e5ac64c3f6921bb59ba3a4fe33910bf028..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16512 zcmeH}&21Yo5QR54-x7CVsD(6;gE|P@O%ATd#kmEAKVt$jocD$+L0AYH0=7iGCBDax zi^ZPLf8YLX+xGLZAA{&^`M-+$Xy7<63vfY*HW0rSs3U`_*p z!wLe26$E}|1x2qD(^b*y#N>)zCngW36W$b<@TS0o-&Me&D+LZ+DRAg}6af5C0PsTr zz%MH}m=EzH0B&Cd!0oRIAox~*;9CKLUn+1gpW=%Ey8R-6Zht|*lk@4t;7e?I^C@!$UZ=Lb+;8)<=((>pMD?W{4q0ZM*vJ}c1blL9R#0$Lva zg9c7$6p);YLU}G{hlTs3g=g`eL0fLEUTfcft}U`_*p!wLe26$E}|1x2qD(^b*y#N>)zCngW36W$b< z@TS0o-&Me&D+LZ+DRAg}6af5C0PsTrz%MH}m=EzH0B&Cd!0oRIAox~*;9CKLUn+1g zpW=%Ey8R-6Zht|*lk@4t;7e?I^C@!$UZ=Lb+;8)<=( z(>pMD?W{4q0ZM*vJ}c1blL9R#0$Lvag9c7$6p); zYLU}G{hlTs3g=g`ZA+z$;(-fccL;U`_*p!wLe2 z6$E}|1x2qD(^b*y#N>)zCngW36W$b<@TS0o-&Me&D+LZ+DRAg}6af5C0PsTrz%MH} zm=EzH0B&Cd!0oRIAox~*;9CKLUn+1gpW=%Ey8R-6Zht|*lk@4t;7e?I^C@!$UZ=Lb+;8)<=((>pMD?W{4q0ZM*vJ}c1blL9R#0$Lvag9c7$6p);YLU}G{hl vTs3g=g`cDXyizK_Tq?jkgEWu^(m)zW18E=)q=7V$2GT$pNCRo$5Ci`K#A3DB diff --git a/Tests/images/rgb8.png b/Tests/images/rgb8.png deleted file mode 100644 index 9d22a26a446d3dbdfd8f9c931ea466f6c6424e90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 861 zcmeAS@N?(olHy`uVBq!ia0vp^4Is<`Bp9BB+KDqTFspdFIEGZrc^h?ls*<5Vur)JB zqlg-7P(pKyh(}`Z41qR*8_gT%*K1u(y=E67zCX-RIr5$RbYFRmk9~(3o90{0iTo?q zZokIPVc}P`n#F#$cTRI}Z`b*cHdxb-Qkj7%xN9XC&Z5~v}sUc zQF_LyDDi8i`mdSklfSkId~vT6XS8VuNn~8YHGhS2Q^L#RQrG)UyL@3X&}x&GB>;Tib{;+mb; z9E?oS*}aKX$Uxa5;eK2CjmcMXGaH^hIH47DKd5laQEeT@eua-ZO-D9&R()gjOuir% zvW=}bGBx``)z5EfnVi1_vX=2LOsg_|ey?EeT|36sB|Uh!dQU<13AVi4r5g)!U-!1I-}Y|HguFw3`xCAHw!QqqenxjbTuMSUDiOXag;?62vFzD;OU-N3|GNKs$H|s>F&hgWVD=Cdb6BTa*mH8 zSvtelvqpRLzjllcxGA-Tb?VxK$@jOLdwX91dKc*H#c{mLBht%bs(#FW&Mb`zE`Q|r X-moxli&n>DP`>eW^>bP0l+XkK*RXjv diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index da7260cbf3b..e0f9fe7de15 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -41,7 +41,7 @@ TEST_FILE_DX10_BC1_TYPELESS, ), ) -def test_sanity_bc1(image_path): +def test_sanity_dxt1_bc1(image_path): """Check DXT1 images can be opened""" with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: target = target.convert("RGBA") @@ -350,12 +350,6 @@ def test_save_unsupported_mode(tmp_path): im.save(out) -def test_open_rgb8(): - with Image.open("Tests/images/rgb8.dds") as im: - assert im.mode == "L" - assert_image_equal_tofile(im, "Tests/images/rgb8.png") - - @pytest.mark.parametrize( ("mode", "test_file"), [ diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 407d33d8512..bdcb8da363c 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -351,9 +351,7 @@ def _open(self): # Texture contains uncompressed RGB data masks = struct.unpack("<4I", header.read(16)) masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} - if bitcount == 8: - self._mode = "L" - elif bitcount == 24: + if bitcount == 24: self._mode = "RGB" rawmode = masks[0x000000FF] + masks[0x0000FF00] + masks[0x00FF0000] elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: From 5aadeb5004588f2b4e9be73e61e978586b5192f5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Dec 2023 01:24:29 +1100 Subject: [PATCH 52/58] Moved _Tile to ImageFile --- src/PIL/DdsImagePlugin.py | 19 ++++++++++++------- src/PIL/Image.py | 10 ---------- src/PIL/ImageFile.py | 10 +++++++++- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index bdcb8da363c..e8d5a8b0e50 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -9,6 +9,7 @@ Full text of the CC0 license: https://creativecommons.org/publicdomain/zero/1.0/ """ + import io import struct import sys @@ -460,9 +461,11 @@ def _open(self): extents = (0, 0) + self.size if n: - self.tile = [Image._Tile("bcn", extents, offset, (n, self.pixel_format))] + self.tile = [ + ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format)) + ] else: - self.tile = [Image._Tile("raw", extents, 0, rawmode or self.mode)] + self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)] def load_seek(self, pos): pass @@ -494,8 +497,8 @@ def _save(im, fp, filename): rgba_mask.append(0xFF000000 if alpha else 0) flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT - bit_count = len(im.getbands()) * 8 - stride = (im.width * bit_count + 7) // 8 + bitcount = len(im.getbands()) * 8 + pitch = (im.width * bitcount + 7) // 8 fp.write( o32(DDS_MAGIC) @@ -505,17 +508,19 @@ def _save(im, fp, filename): flags, # flags im.height, im.width, - stride, # pitch + pitch, 0, # depth 0, # mipmaps ) + struct.pack("11I", *((0,) * 11)) # reserved # pfsize, pfflags, fourcc, bitcount - + struct.pack("<4I", 32, pixel_flags, 0, bit_count) + + struct.pack("<4I", 32, pixel_flags, 0, bitcount) + struct.pack("<4I", *rgba_mask) # dwRGBABitMask + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) ) - ImageFile._save(im, fp, [Image._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) + ImageFile._save( + im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))] + ) def _accept(prefix): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b8b8fcd8120..2853bd5964a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -23,7 +23,6 @@ # # See the README file for information on usage and redistribution. # -from __future__ import annotations import atexit import builtins @@ -39,7 +38,6 @@ from collections.abc import Callable, MutableMapping from enum import IntEnum from pathlib import Path -from typing import NamedTuple try: from defusedxml import ElementTree @@ -208,13 +206,6 @@ class Quantize(IntEnum): FIXED = core.FIXED -class _Tile(NamedTuple): - encoder_name: str - extents: tuple[int, int, int, int] - offset: int - args: tuple | str | None - - # -------------------------------------------------------------------- # Registries @@ -706,7 +697,6 @@ def __getstate__(self): def __setstate__(self, state): Image.__init__(self) - self.tile: list[_Tile] = [] info, mode, size, palette, data = state self.info = info self._mode = mode diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index e8455d0642f..8bca19a4b8d 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -32,6 +32,7 @@ import itertools import struct import sys +from typing import NamedTuple from . import Image from ._util import is_path @@ -78,6 +79,13 @@ def _tilesort(t): return t[2] +class _Tile(NamedTuple): + encoder_name: str + extents: tuple[int, int, int, int] + offset: int + args: tuple | str | None + + # # -------------------------------------------------------------------- # ImageFile base class @@ -521,7 +529,7 @@ def _save(im, fp, tile, bufsize=0): fp.flush() -def _encode_tile(im, fp, tile: list[Image._Tile], bufsize, fh, exc=None): +def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None): for encoder_name, extents, offset, args in tile: if offset > 0: fp.seek(offset) From e072a129743624bbd98c83fbdb863648d051a02d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Dec 2023 11:40:51 +1100 Subject: [PATCH 53/58] Corrected constant values --- src/PIL/DdsImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index e8d5a8b0e50..c09a8db3b9c 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -184,8 +184,8 @@ class DXGI_FORMAT(IntEnum): P208 = 130 V208 = 131 V408 = 132 - SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 133 - SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 134 + SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189 + SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190 class D3DFMT(IntEnum): From 2eddbc5994688c9086d04b4d62e0df5f1f254fde Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sun, 3 Dec 2023 14:25:07 +1100 Subject: [PATCH 54/58] Updated docstrings --- Tests/test_file_dds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index e0f9fe7de15..3a9cc4f1049 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -42,7 +42,7 @@ ), ) def test_sanity_dxt1_bc1(image_path): - """Check DXT1 images can be opened""" + """Check DXT1 and BC1 images can be opened""" with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: target = target.convert("RGBA") with Image.open(image_path) as im: @@ -90,7 +90,7 @@ def test_sanity_dxt5(): ), ) def test_sanity_ati1_bc4u(image_path): - """Check ATI1 images can be opened""" + """Check ATI1 and BC4U images can be opened""" with Image.open(image_path) as im: im.load() From 66c5e9ae98e3435245e089ce1c9aedf236259f4a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 5 Dec 2023 19:31:33 +1100 Subject: [PATCH 55/58] Added release notes --- docs/releasenotes/10.2.0.rst | 54 ++++++++++++++++++++++++++++++++++++ docs/releasenotes/index.rst | 1 + 2 files changed, 55 insertions(+) create mode 100644 docs/releasenotes/10.2.0.rst diff --git a/docs/releasenotes/10.2.0.rst b/docs/releasenotes/10.2.0.rst new file mode 100644 index 00000000000..a2aa0dde3f9 --- /dev/null +++ b/docs/releasenotes/10.2.0.rst @@ -0,0 +1,54 @@ +10.2.0 +------ + +Backwards Incompatible Changes +============================== + +TODO +^^^^ + +TODO + +Deprecations +============ + +TODO +^^^^ + +TODO + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +Added DdsImagePlugin enums +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``DDSD``, ``DDSCAPS``, ``DDSCAPS2``, ``DDPF``, ``DXGI_FORMAT`` and ``D3DFMT`` +enums have been added to DdsImagePlugin. + +Security +======== + +TODO +^^^^ + +TODO + +Other Changes +============= + +Added DDS BC4U and DX10 BC1 and BC4 reading +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Support has been added to read the BC4U format of DDS images. + +Support has also been added to read DX10 BC1 and BC4, whether UNORM or +TYPELESS. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 1b1c353fd3e..d8034853cc2 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -14,6 +14,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 10.2.0 10.1.0 10.0.1 10.0.0 From 9e6030f5a9095e9b908ec7f2dc9259c58262a304 Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Tue, 5 Dec 2023 13:04:19 +0300 Subject: [PATCH 56/58] Rename _420_OPAQUE to OPAQUE_420 Co-authored-by: Hugo van Kemenade --- src/PIL/DdsImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 08d6b839e4b..597666f61ea 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -171,7 +171,7 @@ class DXGI_FORMAT(IntEnum): NV12 = 103 P010 = 104 P016 = 105 - _420_OPAQUE = 106 + OPAQUE_420 = 106 YUY2 = 107 Y210 = 108 Y216 = 109 From 00c6a89177afca196cd3e4d11922db3f9afd6d01 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Dec 2023 11:10:20 +1100 Subject: [PATCH 57/58] Link to plugin --- docs/reference/plugins.rst | 8 ++++++++ docs/releasenotes/10.2.0.rst | 6 ++++-- src/PIL/DdsImagePlugin.py | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/reference/plugins.rst b/docs/reference/plugins.rst index fcf4514a84d..18cd99cf3a2 100644 --- a/docs/reference/plugins.rst +++ b/docs/reference/plugins.rst @@ -33,6 +33,14 @@ Plugin reference :undoc-members: :show-inheritance: +:mod:`~PIL.DdsImagePlugin` Module +--------------------------------- + +.. automodule:: PIL.DdsImagePlugin + :members: + :undoc-members: + :show-inheritance: + :mod:`~PIL.EpsImagePlugin` Module --------------------------------- diff --git a/docs/releasenotes/10.2.0.rst b/docs/releasenotes/10.2.0.rst index a2aa0dde3f9..4e2b67d61bf 100644 --- a/docs/releasenotes/10.2.0.rst +++ b/docs/releasenotes/10.2.0.rst @@ -31,8 +31,10 @@ API Additions Added DdsImagePlugin enums ^^^^^^^^^^^^^^^^^^^^^^^^^^ -``DDSD``, ``DDSCAPS``, ``DDSCAPS2``, ``DDPF``, ``DXGI_FORMAT`` and ``D3DFMT`` -enums have been added to DdsImagePlugin. +:py:class:`PIL.DdsImagePlugin.DDSD`, :py:class:`PIL.DdsImagePlugin.DDSCAPS`, +:py:class:`PIL.DdsImagePlugin.DDSCAPS2`, :py:class:`PIL.DdsImagePlugin.DDPF`, +:py:class:`PIL.DdsImagePlugin.DXGI_FORMAT` and :py:class:`PIL.DdsImagePlugin.D3DFMT` +enums have been added to :py:class:`PIL.DdsImagePlugin`. Security ======== diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 597666f61ea..1758c9a4d13 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -3,11 +3,11 @@ Jerome Leclanche Documentation: - https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt +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 hereby released in the public domain (CC0) Full text of the CC0 license: - https://creativecommons.org/publicdomain/zero/1.0/ +https://creativecommons.org/publicdomain/zero/1.0/ """ import io From d1a22354136981071584b99df81ebc611ec2eda8 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 6 Dec 2023 21:23:26 +1100 Subject: [PATCH 58/58] Added tilde prefix Co-authored-by: Hugo van Kemenade --- docs/releasenotes/10.2.0.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/10.2.0.rst b/docs/releasenotes/10.2.0.rst index 4e2b67d61bf..d507ed73744 100644 --- a/docs/releasenotes/10.2.0.rst +++ b/docs/releasenotes/10.2.0.rst @@ -31,9 +31,9 @@ API Additions Added DdsImagePlugin enums ^^^^^^^^^^^^^^^^^^^^^^^^^^ -:py:class:`PIL.DdsImagePlugin.DDSD`, :py:class:`PIL.DdsImagePlugin.DDSCAPS`, -:py:class:`PIL.DdsImagePlugin.DDSCAPS2`, :py:class:`PIL.DdsImagePlugin.DDPF`, -:py:class:`PIL.DdsImagePlugin.DXGI_FORMAT` and :py:class:`PIL.DdsImagePlugin.D3DFMT` +:py:class:`~PIL.DdsImagePlugin.DDSD`, :py:class:`~PIL.DdsImagePlugin.DDSCAPS`, +:py:class:`~PIL.DdsImagePlugin.DDSCAPS2`, :py:class:`~PIL.DdsImagePlugin.DDPF`, +:py:class:`~PIL.DdsImagePlugin.DXGI_FORMAT` and :py:class:`~PIL.DdsImagePlugin.D3DFMT` enums have been added to :py:class:`PIL.DdsImagePlugin`. Security