diff --git a/quicktex/dds.py b/quicktex/dds.py index 111fff0..3794f18 100644 --- a/quicktex/dds.py +++ b/quicktex/dds.py @@ -230,7 +230,8 @@ class DDSFile: self.header: DDSHeader = DDSHeader() """The DDS file's header object""" - self.textures = [] + self.textures: typing.List[bytes] = [] + """A list of bytes objects for each texture in the file""" @staticmethod def from_file(file: typing.BinaryIO) -> DDSFile: @@ -246,7 +247,18 @@ class DDSFile: dds = DDSFile() dds.header = DDSHeader.from_file(file) - # TODO: read file contents + four_cc = dds.header.pixel_format.four_cc + assert four_cc == 'DXT1' or four_cc == 'DXT5' + block_size = 8 if four_cc == 'DXT1' else 16 + mip_count = dds.header.mipmap_count if DDSHeader.Flags.MIPMAPCOUNT in dds.header.flags else 1 + + dds.textures = [] + for mip in range(mip_count): + block_dimensions = dds.image_block_dimensions() + blocks = block_dimensions[0] * block_dimensions[1] + size = blocks * block_size + + dds.textures.append(file.read(size)) return dds @@ -261,4 +273,40 @@ class DDSFile: file.write(DDSFile.magic) file.write(self.header.to_bytes()) - # TODO: write file contents + for texture in self.textures: + file.write(texture) + + def mipmap_count(self) -> int: + """ + Get the number of mipmaps in the dds file. + + :return: The number of mipmaps + """ + if DDSHeader.Flags.MIPMAPCOUNT in self.header.flags: + mips = self.header.mipmap_count + assert mips > 0 + return mips + else: + return 1 + + def image_dimensions(self, mip: int = 0) -> typing.Tuple[int, int]: + """ + Calculate the dimensions of a mip level in pixels. + + :param int mip: which mip level to calculate the dimensions of, between 0 and :py:attr:`~DDSHeader.mipmap_count` exclusive. + :return: the dimensions of the selected mipmap in pixels + """ + assert self.mipmap_count() > mip >= 0, "Invalid mip level" + return tuple([max(size // (2 ** mip), 1) for size in self.header.dimensions]) + + def image_block_dimensions(self, mip: int = 0) -> typing.Tuple[int, int]: + """ + Calculate the dimensions of a mip level in 4x4 blocks. Only relevent for compressed textures. + + :param int mip: Whick mip level to calculate the dimensions of, between 0 and :py:attr:`~DDSHeader.mipmap_count` exclusive. + :return: the dimensions of the selected mipmap in blocks + """ + dimensions = self.image_dimensions(mip) + + assert all(size > 0 for size in dimensions) + return tuple([max((size + 3) // 4, 1) for size in dimensions])