Skip to content

Commit

Permalink
Merge pull request #10 from radarhere/improved_dds
Browse files Browse the repository at this point in the history
Moved _Tile from Image to ImageFile
  • Loading branch information
REDxEYE committed Dec 2, 2023
2 parents 49578f0 + e072a12 commit 9e5ff76
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 32 deletions.
Binary file removed Tests/images/rgb8.dds
Binary file not shown.
Binary file removed Tests/images/rgb8.png
Binary file not shown.
8 changes: 1 addition & 7 deletions Tests/test_file_dds.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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"),
[
Expand Down
31 changes: 17 additions & 14 deletions src/PIL/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Full text of the CC0 license:
https://creativecommons.org/publicdomain/zero/1.0/
"""

import io
import struct
import sys
Expand Down Expand Up @@ -183,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):
Expand Down Expand Up @@ -351,9 +352,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:
Expand All @@ -379,7 +378,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"
Expand All @@ -405,7 +404,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("<I", self.fp.read(4))
self.fp.read(16)
Expand Down Expand Up @@ -462,9 +461,11 @@ def _open(self):

extents = (0, 0) + self.size
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:
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
Expand Down Expand Up @@ -496,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)
Expand All @@ -507,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):
Expand Down
10 changes: 0 additions & 10 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#
# See the README file for information on usage and redistribution.
#
from __future__ import annotations

import atexit
import builtins
Expand All @@ -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
Expand Down Expand Up @@ -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


# --------------------------------------------------------------------
# Registries

Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import itertools
import struct
import sys
from typing import NamedTuple

from . import Image
from ._util import is_path
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 9e5ff76

Please sign in to comment.