You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
7.0 KiB
Python
221 lines
7.0 KiB
Python
import os
|
|
|
|
import click
|
|
from PIL import Image
|
|
|
|
import quicktex.cli.common as common
|
|
import quicktex.dds as dds
|
|
import quicktex.s3tc.bc1
|
|
import quicktex.s3tc.bc3
|
|
import quicktex.s3tc.bc4
|
|
import quicktex.s3tc.bc5
|
|
|
|
|
|
@click.group()
|
|
def encode():
|
|
"""Encode images to DDS files of the given format."""
|
|
|
|
|
|
@click.command()
|
|
@click.option(
|
|
'-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image before converting."
|
|
)
|
|
@click.option('-r', '--remove', is_flag=True, help="Remove input images after converting.")
|
|
@click.option(
|
|
'-s',
|
|
'--suffix',
|
|
type=str,
|
|
default='',
|
|
help="Suffix to append to output file(s). Ignored if output is a single file.",
|
|
)
|
|
@click.option(
|
|
'-o',
|
|
'--output',
|
|
type=click.Path(writable=True),
|
|
default=None,
|
|
help="Output file or directory. If outputting to a file, input filenames must be only a single item. By default, files are decoded in place.",
|
|
)
|
|
@click.argument('filenames', nargs=-1, type=click.Path(exists=True, readable=True, dir_okay=False))
|
|
def encode_format(encoder, four_cc, flip, remove, suffix, output, filenames, swizzle=False):
|
|
filenames = [f for f in filenames if not f.endswith('.dds')]
|
|
path_pairs = common.path_pairs(filenames, output, suffix, '.dds')
|
|
|
|
with click.progressbar(
|
|
path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else ''
|
|
) as bar:
|
|
for inpath, outpath in bar:
|
|
image = Image.open(inpath)
|
|
|
|
if flip:
|
|
image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
|
|
|
if swizzle:
|
|
bands = image.split()
|
|
one = Image.new('L', image.size, 0xFF)
|
|
image = Image.merge('RGBA', (one, bands[1], bands[1], bands[0]))
|
|
|
|
dds.encode(image, encoder, four_cc).save(outpath)
|
|
|
|
if remove:
|
|
os.remove(inpath)
|
|
|
|
|
|
@click.command('auto')
|
|
@click.option(
|
|
'-l',
|
|
'--level',
|
|
type=click.IntRange(0, 18),
|
|
default=18,
|
|
help='Quality level to use. Higher values = higher quality, but slower.',
|
|
)
|
|
@click.option(
|
|
'-b/-B',
|
|
'--black/--no-black',
|
|
help='[BC1 only] Enable 3-color mode for blocks containing black or very dark pixels. --3color must also be enabled for this to work.'
|
|
' (Important: engine/shader MUST ignore decoded texture alpha if this flag is enabled!)',
|
|
)
|
|
@click.option(
|
|
'-3/-4',
|
|
'--3color/--4color',
|
|
'threecolor',
|
|
default=True,
|
|
help='[BC1 only] Enable 3-color mode for non-black pixels. Higher quality, but slightly slower.',
|
|
)
|
|
@click.option(
|
|
'-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image before converting."
|
|
)
|
|
@click.option('-r', '--remove', is_flag=True, help="Remove input images after converting.")
|
|
@click.option(
|
|
'-s',
|
|
'--suffix',
|
|
type=str,
|
|
default='',
|
|
help="Suffix to append to output file(s). Ignored if output is a single file.",
|
|
)
|
|
@click.option(
|
|
'-o',
|
|
'--output',
|
|
type=click.Path(writable=True),
|
|
default=None,
|
|
help="Output file or directory. If outputting to a file, input filenames must be only a single item. By default, files are decoded in place.",
|
|
)
|
|
@click.argument('filenames', nargs=-1, type=click.Path(exists=True, readable=True, dir_okay=False))
|
|
def encode_auto(level, black, threecolor, flip, remove, suffix, output, filenames):
|
|
"""Encode images to BC1 or BC3, with the format chosen based on each image's alpha channel."""
|
|
|
|
color_mode = quicktex.s3tc.bc1.BC1Encoder.ColorMode
|
|
if not threecolor:
|
|
mode = color_mode.FourColor
|
|
elif not black:
|
|
mode = color_mode.ThreeColor
|
|
else:
|
|
mode = color_mode.ThreeColorBlack
|
|
|
|
bc1_encoder = quicktex.s3tc.bc1.BC1Encoder(level, mode)
|
|
bc3_encoder = quicktex.s3tc.bc3.BC3Encoder(level)
|
|
filenames = [f for f in filenames if not f.endswith('.dds')]
|
|
path_pairs = common.path_pairs(filenames, output, suffix, '.dds')
|
|
|
|
assert len(filenames) > 0
|
|
|
|
with click.progressbar(
|
|
path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else ''
|
|
) as bar:
|
|
for inpath, outpath in bar:
|
|
image = Image.open(inpath)
|
|
|
|
if flip:
|
|
image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
|
|
|
if 'A' not in image.mode:
|
|
has_alpha = False
|
|
else:
|
|
alpha_hist = image.getchannel('A').histogram()
|
|
has_alpha = any([a > 0 for a in alpha_hist[:-1]])
|
|
|
|
if has_alpha:
|
|
dds.encode(image, bc3_encoder, 'DXT5').save(outpath)
|
|
else:
|
|
dds.encode(image, bc1_encoder, 'DXT1').save(outpath)
|
|
|
|
if remove:
|
|
os.remove(inpath)
|
|
|
|
|
|
@click.command('bc1')
|
|
@click.option(
|
|
'-l',
|
|
'--level',
|
|
type=click.IntRange(0, 18),
|
|
default=18,
|
|
help='Quality level to use. Higher values = higher quality, but slower.',
|
|
)
|
|
@click.option(
|
|
'-b/-B',
|
|
'--black/--no-black',
|
|
help='Enable 3-color mode for blocks containing black or very dark pixels. --3color must also be enabled for this to work.'
|
|
' (Important: engine/shader MUST ignore decoded texture alpha if this flag is enabled!)',
|
|
)
|
|
@click.option(
|
|
'-3/-4',
|
|
'--3color/--4color',
|
|
'threecolor',
|
|
default=True,
|
|
help='Enable 3-color mode for non-black pixels. Higher quality, but slightly slower.',
|
|
)
|
|
def encode_bc1(level, black, threecolor, **kwargs):
|
|
"""Encode images to BC1 (RGB, no alpha)."""
|
|
color_mode = quicktex.s3tc.bc1.BC1Encoder.ColorMode
|
|
if not threecolor:
|
|
mode = color_mode.FourColor
|
|
elif not black:
|
|
mode = color_mode.ThreeColor
|
|
else:
|
|
mode = color_mode.ThreeColorBlack
|
|
|
|
encode_format.callback(encoder=quicktex.s3tc.bc1.BC1Encoder(level, mode), four_cc='DXT1', **kwargs)
|
|
|
|
|
|
@click.command('bc3')
|
|
@click.option(
|
|
'-l',
|
|
'--level',
|
|
type=click.IntRange(0, 18),
|
|
default=18,
|
|
help='Quality level to use. Higher values = higher quality, but slower.',
|
|
)
|
|
@click.option(
|
|
'-n/-N',
|
|
'--normal/--no-normal',
|
|
type=bool,
|
|
default=False,
|
|
help='Perform a BC3nm swizzle, copying the red channel into the alpha [default: no-normal]',
|
|
)
|
|
def encode_bc3(level, normal, **kwargs):
|
|
"""Encode images to BC4 (RGBA, 8-bit interpolated alpha)."""
|
|
encode_format.callback(quicktex.s3tc.bc3.BC3Encoder(level), 'DXT5', swizzle=normal, **kwargs)
|
|
|
|
|
|
@click.command('bc4')
|
|
def encode_bc4(**kwargs):
|
|
"""Encode images to BC4 (Single channel, 8-bit interpolated red channel)."""
|
|
encode_format.callback(quicktex.s3tc.bc4.BC4Encoder(), 'ATI1', **kwargs)
|
|
|
|
|
|
@click.command('bc5')
|
|
def encode_bc5(**kwargs):
|
|
"""Encode images to BC5 (2-channel, 8-bit interpolated red and green channels)."""
|
|
encode_format.callback(quicktex.s3tc.bc5.BC5Encoder(), 'ATI2', **kwargs)
|
|
|
|
|
|
encode_bc1.params += encode_format.params
|
|
encode_bc3.params += encode_format.params
|
|
encode_bc4.params += encode_format.params
|
|
encode_bc5.params += encode_format.params
|
|
|
|
encode.add_command(encode_bc1)
|
|
encode.add_command(encode_bc3)
|
|
encode.add_command(encode_bc4)
|
|
encode.add_command(encode_bc5)
|
|
encode.add_command(encode_auto)
|