Add decode command

This commit is contained in:
Andrew Cassidy 2021-04-09 01:33:30 -07:00
parent 8b6ea69300
commit 156880d430
4 changed files with 53 additions and 25 deletions

View File

@ -1,6 +1,6 @@
import click import click
from encode import encode from quicktex.cli.encode import encode
from decode import decode from quicktex.cli.decode import decode
@click.group() @click.group()

View File

@ -1,16 +1,46 @@
import click import click
import io import io
import os import os.path
import quicktex.dds as dds
from PIL import Image from PIL import Image
from pathlib import Path
@click.command() @click.command()
@click.option('-f', '--flip', type=bool, default=True, show_default=True, help="vertically flip image after converting") @click.option('-f', '--flip', help="vertically flip image after converting")
@click.option('-r', '--remove', help="remove input images after converting") @click.option('-r', '--remove', help="remove input images after converting")
@click.option('-o', '--output', help="output file name. Must only specify one input image.")
@click.option('-s', '--suffix', type=str, default='', help="suffix to append to output file(s).") @click.option('-s', '--suffix', type=str, default='', help="suffix to append to output file(s).")
@click.argument('filenames', nargs=-1, type=click.Path(exists=True)) @click.option('-x', '--extension',
def decode(flip, remove, output, suffix, filenames): type=str, default='png',
help="extension to use for output. ignored if output is a single file. output filetype is deduced from this")
@click.option('-o', '--output',
type=click.Path(exists=True, readable=True), default='.',
help="output file name or directory. If outputting to a file, input filenames must be only a single item.")
@click.argument('filenames',
nargs=-1,
type=click.Path(exists=True, readable=True))
def decode(flip, remove, suffix, extension, output, filenames):
"""Decode an input texture file to an image""" """Decode an input texture file to an image"""
assert len(filenames) != 0, 'No input files provided.'
outpath = Path(output)
if outpath.is_file():
assert len(filenames) == 1, 'Provided an output file with multiple inputs.'
def make_outpath(p):
return outpath
else:
def make_outpath(p):
return outpath / (p.stem + suffix + '.' + extension)
for filename in filenames: for filename in filenames:
assert filename.endswith(".dds"), "Incorrect file extension" filepath = Path(filename)
outpath = make_outpath(filepath)
assert filepath.is_file(), f"{filename} is not a file!"
assert filepath.suffix == '.dds', f"{filename} is not a DDS file!"
ddsfile = dds.read(filepath)
image = ddsfile.decode()
image.save(outpath)

View File

@ -5,10 +5,10 @@ import os
import struct import struct
import typing import typing
import quicktex.image_utils import quicktex.image_utils
import quicktex.s3tc.bc1 import quicktex.s3tc.bc1 as bc1
import quicktex.s3tc.bc3 import quicktex.s3tc.bc3 as bc3
import quicktex.s3tc.bc4 import quicktex.s3tc.bc4 as bc4
import quicktex.s3tc.bc5 import quicktex.s3tc.bc5 as bc5
from PIL import Image from PIL import Image
@ -22,10 +22,10 @@ class DDSFormat:
dds_formats = [ dds_formats = [
DDSFormat('BC1', quicktex.s3tc.bc1.BC1Texture, quicktex.s3tc.bc1.BC1Encoder, quicktex.s3tc.bc1.BC1Decoder, 'DXT1'), DDSFormat('BC1', bc1.BC1Texture, bc1.BC1Encoder, bc1.BC1Decoder, 'DXT1'),
DDSFormat('BC3', quicktex.s3tc.bc3.BC3Texture, quicktex.s3tc.bc3.BC3Encoder, quicktex.s3tc.bc3.BC3Decoder, 'DXT5'), DDSFormat('BC3', bc3.BC3Texture, bc3.BC3Encoder, bc3.BC3Decoder, 'DXT5'),
DDSFormat('BC4', quicktex.s3tc.bc4.BC4Texture, quicktex.s3tc.bc4.BC4Encoder, quicktex.s3tc.bc4.BC4Decoder, 'ATI1'), DDSFormat('BC4', bc4.BC4Texture, bc4.BC4Encoder, bc4.BC4Decoder, 'ATI1'),
DDSFormat('BC5', quicktex.s3tc.bc5.BC5Texture, quicktex.s3tc.bc5.BC5Encoder, quicktex.s3tc.bc5.BC5Decoder, 'ATI2'), DDSFormat('BC5', bc5.BC5Texture, bc5.BC5Encoder, bc5.BC5Decoder, 'ATI2'),
] ]
@ -194,16 +194,17 @@ def read(path: os.PathLike) -> DDSFile:
dds = DDSFile() dds = DDSFile()
# READ HEADER # READ HEADER
assert struct.unpack('<I', file.read(4)) == DDSFile.header_bytes, "Incorrect DDS header size." header_bytes = struct.unpack('<I', file.read(4))[0]
assert header_bytes == DDSFile.header_bytes, "Incorrect DDS header size."
dds.flags = DDSFlags(struct.unpack('<I', file.read(4))) # read flags enum dds.flags = DDSFlags(struct.unpack('<I', file.read(4))[0]) # read flags enum
dds.size = struct.unpack('<2I', file.read(8))[::-1] # read dimensions dds.size = struct.unpack('<2I', file.read(8))[::-1] # read dimensions
dds.pitch, dds.depth, dds.mipmap_count = struct.unpack('<3I', file.read(12)) dds.pitch, dds.depth, dds.mipmap_count = struct.unpack('<3I', file.read(12))
file.read(44) # skip 44 unused bytes of data file.read(44) # skip 44 unused bytes of data
assert struct.unpack('<I', file.read(4)) == 32, "Incorrect pixel format size." assert struct.unpack('<I', file.read(4))[0] == 32, "Incorrect pixel format size."
dds.pf_flags = PFFlags(struct.unpack('<I', file.read(4))) dds.pf_flags = PFFlags(struct.unpack('<I', file.read(4))[0])
dds.four_cc = file.read(4).decode() dds.four_cc = file.read(4).decode()
dds.pixel_size, *pixel_bitmasks = struct.unpack('<5I', file.read(20)) dds.pixel_size, *pixel_bitmasks = struct.unpack('<5I', file.read(20))
@ -235,7 +236,7 @@ def read(path: os.PathLike) -> DDSFile:
texture = dds.format.texture(*size) # make a new blocktexture of the current mip size texture = dds.format.texture(*size) # make a new blocktexture of the current mip size
nbytes = file.readinto(texture) nbytes = file.readinto(texture)
assert nbytes == texture.size, 'Unexpected end of file' assert nbytes == texture.nbytes, 'Unexpected end of file'
dds.textures.append(texture) dds.textures.append(texture)

View File

@ -27,11 +27,8 @@ def mip_sizes(dimensions: typing.Tuple[int, int], mip_count: typing.Optional[int
for mip in range(mip_count): for mip in range(mip_count):
chain.append(dimensions) chain.append(dimensions)
dimensions = tuple([max(dim // 2, 1) for dim in dimensions])
if all([dim == 1 for dim in dimensions]): if all([dim == 1 for dim in dimensions]):
break # we've reached a 1x1 mip and can get no smaller break # we've reached a 1x1 mip and can get no smaller
dimensions = tuple([max(dim // 2, 1) for dim in dimensions])
return chain return chain