Merge pull request #10 from radarhere/improved_dds

Moved _Tile from Image to ImageFile
This commit is contained in:
REDxEYE 2023-12-03 02:40:07 +03:00 committed by GitHub
commit 9e5ff7600e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 27 additions and 32 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 861 B

View File

@ -41,7 +41,7 @@ TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
TEST_FILE_DX10_BC1_TYPELESS, TEST_FILE_DX10_BC1_TYPELESS,
), ),
) )
def test_sanity_bc1(image_path): def test_sanity_dxt1_bc1(image_path):
"""Check DXT1 images can be opened""" """Check DXT1 images can be opened"""
with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target:
target = target.convert("RGBA") target = target.convert("RGBA")
@ -350,12 +350,6 @@ def test_save_unsupported_mode(tmp_path):
im.save(out) 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( @pytest.mark.parametrize(
("mode", "test_file"), ("mode", "test_file"),
[ [

View File

@ -9,6 +9,7 @@ The contents of this file are hereby released in the public domain (CC0)
Full text of the CC0 license: Full text of the CC0 license:
https://creativecommons.org/publicdomain/zero/1.0/ https://creativecommons.org/publicdomain/zero/1.0/
""" """
import io import io
import struct import struct
import sys import sys
@ -183,8 +184,8 @@ class DXGI_FORMAT(IntEnum):
P208 = 130 P208 = 130
V208 = 131 V208 = 131
V408 = 132 V408 = 132
SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 133 SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189
SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 134 SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190
class D3DFMT(IntEnum): class D3DFMT(IntEnum):
@ -351,9 +352,7 @@ class DdsImageFile(ImageFile.ImageFile):
# Texture contains uncompressed RGB data # Texture contains uncompressed RGB data
masks = struct.unpack("<4I", header.read(16)) masks = struct.unpack("<4I", header.read(16))
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
if bitcount == 8: if bitcount == 24:
self._mode = "L"
elif bitcount == 24:
self._mode = "RGB" self._mode = "RGB"
rawmode = masks[0x000000FF] + masks[0x0000FF00] + masks[0x00FF0000] rawmode = masks[0x000000FF] + masks[0x0000FF00] + masks[0x00FF0000]
elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS:
@ -379,7 +378,7 @@ class DdsImageFile(ImageFile.ImageFile):
self._mode = "P" self._mode = "P"
self.palette = ImagePalette.raw("RGBA", self.fp.read(1024)) self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
elif pfflags & DDPF.FOURCC: elif pfflags & DDPF.FOURCC:
data_offs = header_size + 4 offset = header_size + 4
if fourcc == D3DFMT.DXT1: if fourcc == D3DFMT.DXT1:
self._mode = "RGBA" self._mode = "RGBA"
self.pixel_format = "DXT1" self.pixel_format = "DXT1"
@ -405,7 +404,7 @@ class DdsImageFile(ImageFile.ImageFile):
self.pixel_format = "BC5" self.pixel_format = "BC5"
n = 5 n = 5
elif fourcc == D3DFMT.DX10: elif fourcc == D3DFMT.DX10:
data_offs += 20 offset += 20
# ignoring flags which pertain to volume textures and cubemaps # ignoring flags which pertain to volume textures and cubemaps
(dxgi_format,) = struct.unpack("<I", self.fp.read(4)) (dxgi_format,) = struct.unpack("<I", self.fp.read(4))
self.fp.read(16) self.fp.read(16)
@ -462,9 +461,11 @@ class DdsImageFile(ImageFile.ImageFile):
extents = (0, 0) + self.size extents = (0, 0) + self.size
if n: if n:
self.tile = [Image._Tile("bcn", extents, data_offs, (n, self.pixel_format))] self.tile = [
ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
]
else: 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): def load_seek(self, pos):
pass pass
@ -496,8 +497,8 @@ def _save(im, fp, filename):
rgba_mask.append(0xFF000000 if alpha else 0) rgba_mask.append(0xFF000000 if alpha else 0)
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT
bit_count = len(im.getbands()) * 8 bitcount = len(im.getbands()) * 8
stride = (im.width * bit_count + 7) // 8 pitch = (im.width * bitcount + 7) // 8
fp.write( fp.write(
o32(DDS_MAGIC) o32(DDS_MAGIC)
@ -507,17 +508,19 @@ def _save(im, fp, filename):
flags, # flags flags, # flags
im.height, im.height,
im.width, im.width,
stride, # pitch pitch,
0, # depth 0, # depth
0, # mipmaps 0, # mipmaps
) )
+ struct.pack("11I", *((0,) * 11)) # reserved + struct.pack("11I", *((0,) * 11)) # reserved
# pfsize, pfflags, fourcc, bitcount # 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("<4I", *rgba_mask) # dwRGBABitMask
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) + 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): def _accept(prefix):

View File

@ -23,7 +23,6 @@
# #
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
from __future__ import annotations
import atexit import atexit
import builtins import builtins
@ -39,7 +38,6 @@ import warnings
from collections.abc import Callable, MutableMapping from collections.abc import Callable, MutableMapping
from enum import IntEnum from enum import IntEnum
from pathlib import Path from pathlib import Path
from typing import NamedTuple
try: try:
from defusedxml import ElementTree from defusedxml import ElementTree
@ -208,13 +206,6 @@ if hasattr(core, "DEFAULT_STRATEGY"):
FIXED = core.FIXED FIXED = core.FIXED
class _Tile(NamedTuple):
encoder_name: str
extents: tuple[int, int, int, int]
offset: int
args: tuple
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Registries # Registries
@ -706,7 +697,6 @@ class Image:
def __setstate__(self, state): def __setstate__(self, state):
Image.__init__(self) Image.__init__(self)
self.tile: list[_Tile] = []
info, mode, size, palette, data = state info, mode, size, palette, data = state
self.info = info self.info = info
self._mode = mode self._mode = mode

View File

@ -32,6 +32,7 @@ import io
import itertools import itertools
import struct import struct
import sys import sys
from typing import NamedTuple
from . import Image from . import Image
from ._util import is_path from ._util import is_path
@ -78,6 +79,13 @@ def _tilesort(t):
return t[2] return t[2]
class _Tile(NamedTuple):
encoder_name: str
extents: tuple[int, int, int, int]
offset: int
args: tuple | str | None
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# ImageFile base class # ImageFile base class
@ -521,7 +529,7 @@ def _save(im, fp, tile, bufsize=0):
fp.flush() 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: for encoder_name, extents, offset, args in tile:
if offset > 0: if offset > 0:
fp.seek(offset) fp.seek(offset)