mirror of
https://github.com/drewcassidy/quicktex.git
synced 2024-09-13 06:37:34 +00:00
Compare commits
No commits in common. "55f0ced229db2feda2ada23ca7bdb82c1a761f38" and "ac4e5b2679b1ffb7a6e0bbeb1a1af58d06570a7f" have entirely different histories.
55f0ced229
...
ac4e5b2679
@ -1,8 +0,0 @@
|
|||||||
# git-blame ignored revisions
|
|
||||||
# To configure, run
|
|
||||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
|
||||||
# Requires Git > 2.23
|
|
||||||
# See https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt
|
|
||||||
|
|
||||||
# Migrate code style to Black
|
|
||||||
cb84f32edab717389d03a3855aa5bd4d0db1ae3c
|
|
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
@ -4,7 +4,6 @@ updates:
|
|||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
target-branch: "dev"
|
|
||||||
schedule:
|
schedule:
|
||||||
# Check for updates to GitHub Actions every weekday
|
# Check for updates to GitHub Actions every weekday
|
||||||
interval: "daily"
|
interval: "daily"
|
2
.github/workflows/python-package.yml
vendored
2
.github/workflows/python-package.yml
vendored
@ -76,7 +76,7 @@ jobs:
|
|||||||
platforms: arm64
|
platforms: arm64
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: pypa/cibuildwheel@2.5.0
|
uses: pypa/cibuildwheel@v2.4.0
|
||||||
env:
|
env:
|
||||||
MACOSX_DEPLOYMENT_TARGET: "10.15"
|
MACOSX_DEPLOYMENT_TARGET: "10.15"
|
||||||
CIBW_ARCHS_LINUX: ${{ matrix.arch[3] }}
|
CIBW_ARCHS_LINUX: ${{ matrix.arch[3] }}
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,8 +5,6 @@ build/
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyi
|
*.pyi
|
||||||
*.whl
|
|
||||||
*.tar.gz
|
|
||||||
|
|
||||||
#sphinx
|
#sphinx
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
# .readthedocs.yaml
|
|
||||||
# Read the Docs configuration file
|
|
||||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
||||||
|
|
||||||
# Required
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
# Set the version of Python and other tools you might need
|
|
||||||
build:
|
|
||||||
os: ubuntu-20.04
|
|
||||||
tools:
|
|
||||||
python: "3.10"
|
|
||||||
|
|
||||||
# Build documentation in the docs/ directory with Sphinx
|
|
||||||
sphinx:
|
|
||||||
configuration: docs/conf.py
|
|
||||||
|
|
||||||
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
|
||||||
formats:
|
|
||||||
- pdf
|
|
||||||
|
|
||||||
# Give python build instructions
|
|
||||||
python:
|
|
||||||
install:
|
|
||||||
- method: pip
|
|
||||||
path: .
|
|
||||||
extra_requirements:
|
|
||||||
- docs
|
|
@ -1,2 +0,0 @@
|
|||||||
```{include} ../CHANGELOG.md
|
|
||||||
```
|
|
@ -13,13 +13,12 @@
|
|||||||
# import os
|
# import os
|
||||||
# import sys
|
# import sys
|
||||||
# sys.path.insert(0, os.path.abspath('..'))
|
# sys.path.insert(0, os.path.abspath('..'))
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
project = 'Quicktex'
|
project = 'Quicktex'
|
||||||
copyright = f'{date.today().year}, Andrew Cassidy'
|
copyright = '2021, Andrew Cassidy'
|
||||||
author = 'Andrew Cassidy'
|
author = 'Andrew Cassidy'
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
@ -29,14 +28,11 @@ author = 'Andrew Cassidy'
|
|||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
'myst_parser',
|
'myst_parser',
|
||||||
'sphinx_click',
|
|
||||||
'sphinx_rtd_theme',
|
'sphinx_rtd_theme',
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
]
|
]
|
||||||
|
|
||||||
myst_heading_anchors = 2
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
|
||||||
@ -74,5 +70,5 @@ autodoc_default_options = {
|
|||||||
# should be linked to in this documentation.
|
# should be linked to in this documentation.
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
'python': ('https://docs.python.org/3', None),
|
'python': ('https://docs.python.org/3', None),
|
||||||
'PIL': ('https://pillow.readthedocs.io/en/stable/', None),
|
'PIL': ('https://pillow.readthedocs.io/en/stable/', None)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
# Command Reference
|
|
||||||
|
|
||||||
```{eval-rst}
|
|
||||||
.. click:: quicktex.__main__:main
|
|
||||||
:prog: quicktex
|
|
||||||
:nested: full
|
|
||||||
```
|
|
@ -1,84 +0,0 @@
|
|||||||
# Getting Started
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Install and update using [pip](https://pip.pypa.io/en/stable/quickstart/):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pip install -U quicktex
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are on macOS, you need to install openMP to allow multithreading, since it does not ship with the built-in Clang.
|
|
||||||
This can be done easily
|
|
||||||
using [homebrew](https://brew.sh). This is not required if building from source, but highly recommended.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
brew install libomp
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want, you can also install from source. First clone the [git repo](https://github.com/drewcassidy/quicktex) and
|
|
||||||
install it with:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pip install .
|
|
||||||
```
|
|
||||||
|
|
||||||
and setuptools will take care of any dependencies for you.
|
|
||||||
|
|
||||||
The package also makes tests, stub generation, and docs available. To install the
|
|
||||||
required dependencies for them, install with options like so:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pip install quicktex[tests,stubs,docs]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
For detailed documentation on the {command}`quicktex` command and its subcommands see the {doc}`commands`.
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
#### Encoding a file
|
|
||||||
|
|
||||||
To encode a file in place, use the {command}`encode` command
|
|
||||||
|
|
||||||
```shell
|
|
||||||
quicktex encode auto bun.png # chooses format based on alpha
|
|
||||||
quicktex encode bc3 bun.png # encodes as bc3
|
|
||||||
```
|
|
||||||
|
|
||||||
the auto subcommand automatically chooses between bc1 and bc3 for your image depending on the contents of its alpha
|
|
||||||
channel. Quicktex supports reading from all image formats supported by [pillow](https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html).
|
|
||||||
|
|
||||||
By default, Quicktex converts in place, meaning the above command will produce a `bun.dds` file alongside the png. If
|
|
||||||
you want to replace the png, use the `-r` flag to remove it after converting.
|
|
||||||
|
|
||||||
if you want to specify an output filename or directory use the `-o` flag.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
quicktex encode auto -o rabbit.dds bun.png # produces rabbit.dds
|
|
||||||
quicktex.encode auto -o textures/ bun.png # produces textures/bun.dds, if textures/ exists
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Encoding multiple files
|
|
||||||
|
|
||||||
quicktex is also able to convert multiple files at once, for example, to encode every png file in the images folder,
|
|
||||||
use:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
quicktex encode auto images/*.png # encodes in-place
|
|
||||||
quicktex encode auto -o textures/ images/*.png # encodes to the textures/ directory
|
|
||||||
```
|
|
||||||
|
|
||||||
please note that globbing is an operation performed by your shell and is not supported by the built in windows `cmd.exe`
|
|
||||||
. If you are on Windows, please use Powershell or any posix-ish shell like [fish](https://fishshell.com).
|
|
||||||
|
|
||||||
#### Decoding files
|
|
||||||
|
|
||||||
decoding is performed exactly the same as encoding, except without having to specify a format. The output image format
|
|
||||||
is set using the `-x` flag, and defaults to png. Quicktex supports writing to all image formats supported by [pillow](https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html)
|
|
||||||
|
|
||||||
```shell
|
|
||||||
quicktex decode bun.dds # produces bun.png
|
|
||||||
quicktex decode -x .tga bun2.dds # produces bun.tga
|
|
||||||
```
|
|
@ -1,10 +0,0 @@
|
|||||||
# Handbook
|
|
||||||
|
|
||||||
```{toctree}
|
|
||||||
---
|
|
||||||
maxdepth: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
commands
|
|
||||||
getting_started
|
|
||||||
```
|
|
@ -1,34 +0,0 @@
|
|||||||
# Welcome to Quicktex's Documentation
|
|
||||||
|
|
||||||
[![Documentation Status](https://readthedocs.org/projects/quicktex/badge/?version=latest)](https://quicktex.readthedocs.io/en/latest/?badge=latest)
|
|
||||||
[![Python Package](https://github.com/drewcassidy/quicktex/actions/workflows/python-package.yml/badge.svg)](https://github.com/drewcassidy/quicktex/actions/workflows/python-package.yml)
|
|
||||||
[![PyPI version](https://badge.fury.io/py/quicktex.svg)](https://badge.fury.io/py/quicktex)
|
|
||||||
|
|
||||||
Quicktex is a Python library for encoding, decoding, and manipulating compressed textures. It uses a backend written in
|
|
||||||
C++ for superior performance, as well as an extensive API for low-level access to the texture data. The compression
|
|
||||||
engine is based in [rgbcx](https://github.com/richgel999/bc7enc).
|
|
||||||
|
|
||||||
```{toctree}
|
|
||||||
---
|
|
||||||
maxdepth: 2
|
|
||||||
caption: Contents
|
|
||||||
---
|
|
||||||
|
|
||||||
handbook/index
|
|
||||||
reference/index
|
|
||||||
```
|
|
||||||
|
|
||||||
```{toctree}
|
|
||||||
---
|
|
||||||
maxdepth: 1
|
|
||||||
---
|
|
||||||
|
|
||||||
Changelog <changelog>
|
|
||||||
License <license>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Indices and tables
|
|
||||||
|
|
||||||
* {ref}`genindex`
|
|
||||||
* {ref}`modindex`
|
|
||||||
* {ref}`search`
|
|
15
docs/index.rst
Normal file
15
docs/index.rst
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Welcome to Quicktex's documentation!
|
||||||
|
========================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
reference/index.rst
|
||||||
|
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
@ -1,2 +0,0 @@
|
|||||||
```{include} ../LICENSE.md
|
|
||||||
```
|
|
10
docs/reference/conversion.rst
Normal file
10
docs/reference/conversion.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.. py:currentmodule:: quicktex
|
||||||
|
|
||||||
|
Conversion
|
||||||
|
============
|
||||||
|
|
||||||
|
.. autoclass:: BlockEncoder
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: BlockDecoder
|
||||||
|
:members:
|
@ -1,9 +1,10 @@
|
|||||||
API Reference
|
Reference
|
||||||
=============
|
=========
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
conversion.rst
|
||||||
dds.rst
|
dds.rst
|
||||||
image_utils.rst
|
image_utils.rst
|
||||||
formats/index.rst
|
formats/index.rst
|
||||||
|
@ -37,16 +37,10 @@ dynamic = ["version"]
|
|||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
tests = ["parameterized"]
|
tests = ["parameterized"]
|
||||||
docs = [
|
docs = ["sphinx", "myst-parser", "sphinx-rtd-theme"]
|
||||||
"Sphinx >= 3.5",
|
|
||||||
"sphinx-click >= 2.7",
|
|
||||||
"sphinx-rtd-theme",
|
|
||||||
"myst-parser >= 0.14",
|
|
||||||
]
|
|
||||||
stubs = ["pybind11-stubgen"]
|
stubs = ["pybind11-stubgen"]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Docs = "https://quicktex.readthedocs.io/en/"
|
|
||||||
Source = "https://github.com/drewcassidy/quicktex"
|
Source = "https://github.com/drewcassidy/quicktex"
|
||||||
Changelog = "https://github.com/drewcassidy/quicktex/blob/main/CHANGELOG.md"
|
Changelog = "https://github.com/drewcassidy/quicktex/blob/main/CHANGELOG.md"
|
||||||
|
|
||||||
@ -79,8 +73,3 @@ test-command = "cd /d {project} && python -m unittest --verbose" # windows why i
|
|||||||
skip = ["cp37-musllinux*", "*musllinux_aarch64*"] # skip targets without available Pillow wheels
|
skip = ["cp37-musllinux*", "*musllinux_aarch64*"] # skip targets without available Pillow wheels
|
||||||
manylinux-x86_64-image = "manylinux2014"
|
manylinux-x86_64-image = "manylinux2014"
|
||||||
manylinux-aarch64-image = "manylinux2014"
|
manylinux-aarch64-image = "manylinux2014"
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 120 # 80-column is stupid
|
|
||||||
target-version = ['py37', 'py38', 'py39', 'py310']
|
|
||||||
skip-string-normalization = true
|
|
@ -1,7 +1,6 @@
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
from quicktex.cli.decode import decode
|
|
||||||
from quicktex.cli.encode import encode
|
from quicktex.cli.encode import encode
|
||||||
|
from quicktex.cli.decode import decode
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import pathlib
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import click
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from typing import List
|
||||||
|
import pathlib
|
||||||
|
import click
|
||||||
|
|
||||||
|
|
||||||
def get_decoded_extensions(feature: str = 'open') -> List[str]:
|
def get_decoded_extensions(feature: str = 'open') -> List[str]:
|
||||||
|
@ -1,49 +1,28 @@
|
|||||||
import os.path
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from PIL import Image
|
import os.path
|
||||||
|
|
||||||
import quicktex.cli.common as common
|
|
||||||
import quicktex.dds as dds
|
import quicktex.dds as dds
|
||||||
|
import quicktex.cli.common as common
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option(
|
@click.option('-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image after converting.")
|
||||||
'-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image after converting."
|
|
||||||
)
|
|
||||||
@click.option('-r', '--remove', is_flag=True, help="Remove input images after converting.")
|
@click.option('-r', '--remove', is_flag=True, help="Remove input images after converting.")
|
||||||
@click.option(
|
@click.option('-s', '--suffix', type=str, default='', help="Suffix to append to output file(s). Ignored if output is a single file.")
|
||||||
'-s',
|
@click.option('-x', '--extension',
|
||||||
'--suffix',
|
callback=common.validate_decoded_extension,
|
||||||
type=str,
|
type=str, default='.png', show_default=True,
|
||||||
default='',
|
help="Extension to use for output. Ignored if output is a single file. Output filetype is deduced from this")
|
||||||
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,
|
||||||
@click.option(
|
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.")
|
||||||
'-x',
|
|
||||||
'--extension',
|
|
||||||
callback=common.validate_decoded_extension,
|
|
||||||
type=str,
|
|
||||||
default='.png',
|
|
||||||
show_default=True,
|
|
||||||
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(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))
|
@click.argument('filenames', nargs=-1, type=click.Path(exists=True, readable=True, dir_okay=False))
|
||||||
def decode(flip, remove, suffix, extension, output, filenames):
|
def decode(flip, remove, suffix, extension, output, filenames):
|
||||||
"""Decode DDS files to images."""
|
"""Decode DDS files to images."""
|
||||||
|
|
||||||
path_pairs = common.path_pairs(filenames, output, suffix, extension)
|
path_pairs = common.path_pairs(filenames, output, suffix, extension)
|
||||||
|
|
||||||
with click.progressbar(
|
with click.progressbar(path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else '') as bar:
|
||||||
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:
|
for inpath, outpath in bar:
|
||||||
if inpath.suffix != '.dds':
|
if inpath.suffix != '.dds':
|
||||||
raise click.BadArgumentUsage(f"Input file '{inpath}' is not a DDS file.")
|
raise click.BadArgumentUsage(f"Input file '{inpath}' is not a DDS file.")
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from PIL import Image
|
import os
|
||||||
|
import pathlib
|
||||||
import quicktex.cli.common as common
|
|
||||||
import quicktex.dds as dds
|
|
||||||
import quicktex.s3tc.bc1
|
import quicktex.s3tc.bc1
|
||||||
import quicktex.s3tc.bc3
|
import quicktex.s3tc.bc3
|
||||||
import quicktex.s3tc.bc4
|
import quicktex.s3tc.bc4
|
||||||
import quicktex.s3tc.bc5
|
import quicktex.s3tc.bc5
|
||||||
|
import quicktex.dds as dds
|
||||||
|
import quicktex.cli.common as common
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
@ -17,31 +16,17 @@ def encode():
|
|||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option(
|
@click.option('-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image before converting.")
|
||||||
'-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('-r', '--remove', is_flag=True, help="Remove input images after converting.")
|
||||||
@click.option(
|
@click.option('-s', '--suffix', type=str, default='', help="Suffix to append to output file(s). Ignored if output is a single file.")
|
||||||
'-s',
|
@click.option('-o', '--output',
|
||||||
'--suffix',
|
type=click.Path(writable=True), default=None,
|
||||||
type=str,
|
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.")
|
||||||
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))
|
@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):
|
def encode_format(encoder, four_cc, flip, remove, suffix, output, filenames):
|
||||||
path_pairs = common.path_pairs(filenames, output, suffix, '.dds')
|
path_pairs = common.path_pairs(filenames, output, suffix, '.dds')
|
||||||
|
|
||||||
with click.progressbar(
|
with click.progressbar(path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else '') as bar:
|
||||||
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:
|
for inpath, outpath in bar:
|
||||||
image = Image.open(inpath)
|
image = Image.open(inpath)
|
||||||
|
|
||||||
@ -55,44 +40,17 @@ def encode_format(encoder, four_cc, flip, remove, suffix, output, filenames):
|
|||||||
|
|
||||||
|
|
||||||
@click.command('auto')
|
@click.command('auto')
|
||||||
@click.option(
|
@click.option('-l', '--level', type=click.IntRange(0, 18), default=18, help='Quality level to use. Higher values = higher quality, but slower.')
|
||||||
'-l',
|
@click.option('-b/-B', '--black/--no-black',
|
||||||
'--level',
|
help='[BC1 only] Enable 3-color mode for blocks containing black or very dark pixels. --3color must also be enabled for this to work.'
|
||||||
type=click.IntRange(0, 18),
|
' (Important: engine/shader MUST ignore decoded texture alpha if this flag is enabled!)')
|
||||||
default=18,
|
@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.')
|
||||||
help='Quality level to use. Higher values = higher quality, but slower.',
|
@click.option('-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image before converting.")
|
||||||
)
|
|
||||||
@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('-r', '--remove', is_flag=True, help="Remove input images after converting.")
|
||||||
@click.option(
|
@click.option('-s', '--suffix', type=str, default='', help="Suffix to append to output file(s). Ignored if output is a single file.")
|
||||||
'-s',
|
@click.option('-o', '--output',
|
||||||
'--suffix',
|
type=click.Path(writable=True), default=None,
|
||||||
type=str,
|
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.")
|
||||||
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))
|
@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):
|
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."""
|
"""Encode images to BC1 or BC3, with the format chosen based on each image's alpha channel."""
|
||||||
@ -109,9 +67,7 @@ def encode_auto(level, black, threecolor, flip, remove, suffix, output, filename
|
|||||||
bc3_encoder = quicktex.s3tc.bc3.BC3Encoder(level)
|
bc3_encoder = quicktex.s3tc.bc3.BC3Encoder(level)
|
||||||
path_pairs = common.path_pairs(filenames, output, suffix, '.dds')
|
path_pairs = common.path_pairs(filenames, output, suffix, '.dds')
|
||||||
|
|
||||||
with click.progressbar(
|
with click.progressbar(path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else '') as bar:
|
||||||
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:
|
for inpath, outpath in bar:
|
||||||
image = Image.open(inpath)
|
image = Image.open(inpath)
|
||||||
|
|
||||||
@ -134,26 +90,11 @@ def encode_auto(level, black, threecolor, flip, remove, suffix, output, filename
|
|||||||
|
|
||||||
|
|
||||||
@click.command('bc1')
|
@click.command('bc1')
|
||||||
@click.option(
|
@click.option('-l', '--level', type=click.IntRange(0, 18), default=18, help='Quality level to use. Higher values = higher quality, but slower.')
|
||||||
'-l',
|
@click.option('-b/-B', '--black/--no-black',
|
||||||
'--level',
|
help='Enable 3-color mode for blocks containing black or very dark pixels. --3color must also be enabled for this to work.'
|
||||||
type=click.IntRange(0, 18),
|
' (Important: engine/shader MUST ignore decoded texture alpha if this flag is enabled!)')
|
||||||
default=18,
|
@click.option('-3/-4', '--3color/--4color', 'threecolor', default=True, help='Enable 3-color mode for non-black pixels. Higher quality, but slightly slower.')
|
||||||
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):
|
def encode_bc1(level, black, threecolor, **kwargs):
|
||||||
"""Encode images to BC1 (RGB, no alpha)."""
|
"""Encode images to BC1 (RGB, no alpha)."""
|
||||||
color_mode = quicktex.s3tc.bc1.BC1Encoder.ColorMode
|
color_mode = quicktex.s3tc.bc1.BC1Encoder.ColorMode
|
||||||
@ -168,13 +109,7 @@ def encode_bc1(level, black, threecolor, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@click.command('bc3')
|
@click.command('bc3')
|
||||||
@click.option(
|
@click.option('-l', '--level', type=click.IntRange(0, 18), default=18, help='Quality level to use. Higher values = higher quality, but slower.')
|
||||||
'-l',
|
|
||||||
'--level',
|
|
||||||
type=click.IntRange(0, 18),
|
|
||||||
default=18,
|
|
||||||
help='Quality level to use. Higher values = higher quality, but slower.',
|
|
||||||
)
|
|
||||||
def encode_bc3(level, **kwargs):
|
def encode_bc3(level, **kwargs):
|
||||||
"""Encode images to BC4 (RGBA, 8-bit interpolated alpha)."""
|
"""Encode images to BC4 (RGBA, 8-bit interpolated alpha)."""
|
||||||
encode_format.callback(quicktex.s3tc.bc3.BC3Encoder(level), 'DXT5', **kwargs)
|
encode_format.callback(quicktex.s3tc.bc3.BC3Encoder(level), 'DXT5', **kwargs)
|
||||||
|
@ -4,14 +4,12 @@ import enum
|
|||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
import quicktex.image_utils
|
import quicktex.image_utils
|
||||||
import quicktex.s3tc.bc1 as bc1
|
import quicktex.s3tc.bc1 as bc1
|
||||||
import quicktex.s3tc.bc3 as bc3
|
import quicktex.s3tc.bc3 as bc3
|
||||||
import quicktex.s3tc.bc4 as bc4
|
import quicktex.s3tc.bc4 as bc4
|
||||||
import quicktex.s3tc.bc5 as bc5
|
import quicktex.s3tc.bc5 as bc5
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
class DDSFormat:
|
class DDSFormat:
|
||||||
@ -167,28 +165,8 @@ class DDSFile:
|
|||||||
file.write(DDSFile.magic)
|
file.write(DDSFile.magic)
|
||||||
|
|
||||||
# WRITE HEADER
|
# WRITE HEADER
|
||||||
file.write(
|
file.write(struct.pack('<7I44x', DDSFile.header_bytes, int(self.flags), self.size[1], self.size[0], self.pitch, self.depth, self.mipmap_count))
|
||||||
struct.pack(
|
file.write(struct.pack('<2I4s5I', 32, int(self.pf_flags), bytes(self.four_cc, 'ascii'), self.pixel_size, *self.pixel_bitmasks))
|
||||||
'<7I44x',
|
|
||||||
DDSFile.header_bytes,
|
|
||||||
int(self.flags),
|
|
||||||
self.size[1],
|
|
||||||
self.size[0],
|
|
||||||
self.pitch,
|
|
||||||
self.depth,
|
|
||||||
self.mipmap_count,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
file.write(
|
|
||||||
struct.pack(
|
|
||||||
'<2I4s5I',
|
|
||||||
32,
|
|
||||||
int(self.pf_flags),
|
|
||||||
bytes(self.four_cc, 'ascii'),
|
|
||||||
self.pixel_size,
|
|
||||||
*self.pixel_bitmasks,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
file.write(struct.pack('<4I4x', *self.caps))
|
file.write(struct.pack('<4I4x', *self.caps))
|
||||||
|
|
||||||
assert file.tell() == 4 + DDSFile.header_bytes, 'error writing file: incorrect header size'
|
assert file.tell() == 4 + DDSFile.header_bytes, 'error writing file: incorrect header size'
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
"""Various utilities for working with Pillow images"""
|
"""Various utilities for working with Pillow images"""
|
||||||
|
|
||||||
import math
|
|
||||||
from typing import List, Tuple, Optional
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from typing import List, Tuple, Optional
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
def mip_sizes(dimensions: Tuple[int, int], mip_count: Optional[int] = None) -> List[Tuple[int, int]]:
|
def mip_sizes(dimensions: Tuple[int, int], mip_count: Optional[int] = None) -> List[Tuple[int, int]]:
|
||||||
|
16
setup.py
16
setup.py
@ -22,7 +22,6 @@ class CMakeExtension(Extension):
|
|||||||
class CMakeBuild(build_ext):
|
class CMakeBuild(build_ext):
|
||||||
def build_extension(self, ext):
|
def build_extension(self, ext):
|
||||||
from setuptools_scm import get_version
|
from setuptools_scm import get_version
|
||||||
|
|
||||||
version = get_version(root='.', relative_to=__file__)
|
version = get_version(root='.', relative_to=__file__)
|
||||||
|
|
||||||
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
|
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
|
||||||
@ -46,8 +45,7 @@ class CMakeBuild(build_ext):
|
|||||||
"-DQUICKTEX_VERSION_INFO={}".format(version),
|
"-DQUICKTEX_VERSION_INFO={}".format(version),
|
||||||
"-DCMAKE_BUILD_TYPE={}".format(cfg), # not used on MSVC, but no harm
|
"-DCMAKE_BUILD_TYPE={}".format(cfg), # not used on MSVC, but no harm
|
||||||
# clear cached make program binary, see https://github.com/pypa/setuptools/issues/2912
|
# clear cached make program binary, see https://github.com/pypa/setuptools/issues/2912
|
||||||
"-U",
|
"-U", "CMAKE_MAKE_PROGRAM",
|
||||||
"CMAKE_MAKE_PROGRAM",
|
|
||||||
]
|
]
|
||||||
build_args = []
|
build_args = []
|
||||||
|
|
||||||
@ -83,7 +81,9 @@ class CMakeBuild(build_ext):
|
|||||||
|
|
||||||
# Multi-config generators have a different way to specify configs
|
# Multi-config generators have a different way to specify configs
|
||||||
if not single_config:
|
if not single_config:
|
||||||
cmake_args += ["-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir)]
|
cmake_args += [
|
||||||
|
"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir)
|
||||||
|
]
|
||||||
build_args += ["--config", cfg]
|
build_args += ["--config", cfg]
|
||||||
|
|
||||||
if sys.platform.startswith("darwin"):
|
if sys.platform.startswith("darwin"):
|
||||||
@ -104,8 +104,12 @@ class CMakeBuild(build_ext):
|
|||||||
if not os.path.exists(self.build_temp):
|
if not os.path.exists(self.build_temp):
|
||||||
os.makedirs(self.build_temp)
|
os.makedirs(self.build_temp)
|
||||||
|
|
||||||
subprocess.check_call(["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp)
|
subprocess.check_call(
|
||||||
subprocess.check_call(["cmake", "--build", ".", "--target", ext.name] + build_args, cwd=self.build_temp)
|
["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp
|
||||||
|
)
|
||||||
|
subprocess.check_call(
|
||||||
|
["cmake", "--build", ".", "--target", ext.name] + build_args, cwd=self.build_temp
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# The information here can also be placed in setup.cfg - better separation of
|
# The information here can also be placed in setup.cfg - better separation of
|
||||||
|
@ -58,8 +58,12 @@ class TestBC1Block(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
@parameterized_class(
|
@parameterized_class(
|
||||||
("name", "w", "h", "wb", "hb"), [("8x8", 8, 8, 2, 2), ("9x9", 9, 9, 3, 3), ("7x7", 7, 7, 2, 2), ("7x9", 7, 9, 2, 3)]
|
("name", "w", "h", "wb", "hb"), [
|
||||||
)
|
("8x8", 8, 8, 2, 2),
|
||||||
|
("9x9", 9, 9, 3, 3),
|
||||||
|
("7x7", 7, 7, 2, 2),
|
||||||
|
("7x9", 7, 9, 2, 3)
|
||||||
|
])
|
||||||
class TestBC1Texture(unittest.TestCase):
|
class TestBC1Texture(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.tex = BC1Texture(self.w, self.h)
|
self.tex = BC1Texture(self.w, self.h)
|
||||||
@ -94,7 +98,7 @@ class TestBC1Texture(unittest.TestCase):
|
|||||||
for y in range(self.hb):
|
for y in range(self.hb):
|
||||||
index = (x + (y * self.wb)) * BC1Block.nbytes
|
index = (x + (y * self.wb)) * BC1Block.nbytes
|
||||||
tb = self.tex[x, y]
|
tb = self.tex[x, y]
|
||||||
fb = BC1Block.frombytes(b[index : index + BC1Block.nbytes])
|
fb = BC1Block.frombytes(b[index:index + BC1Block.nbytes])
|
||||||
self.assertEqual(tb, blocks[y][x], 'incorrect block read from texture')
|
self.assertEqual(tb, blocks[y][x], 'incorrect block read from texture')
|
||||||
self.assertEqual(fb, blocks[y][x], 'incorrect block read from texture bytes')
|
self.assertEqual(fb, blocks[y][x], 'incorrect block read from texture bytes')
|
||||||
|
|
||||||
@ -120,13 +124,11 @@ class TestBC1Texture(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
@parameterized_class(
|
@parameterized_class(
|
||||||
("name", "color_mode"),
|
("name", "color_mode"), [
|
||||||
[
|
|
||||||
("4Color", BC1Encoder.ColorMode.FourColor),
|
("4Color", BC1Encoder.ColorMode.FourColor),
|
||||||
("3Color", BC1Encoder.ColorMode.ThreeColor),
|
("3Color", BC1Encoder.ColorMode.ThreeColor),
|
||||||
("3Color_Black", BC1Encoder.ColorMode.ThreeColorBlack),
|
("3Color_Black", BC1Encoder.ColorMode.ThreeColorBlack),
|
||||||
],
|
])
|
||||||
)
|
|
||||||
class TestBC1Encoder(unittest.TestCase):
|
class TestBC1Encoder(unittest.TestCase):
|
||||||
"""Test BC1Encoder"""
|
"""Test BC1Encoder"""
|
||||||
|
|
||||||
@ -187,13 +189,11 @@ class TestBC1Decoder(unittest.TestCase):
|
|||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.bc1_decoder = BC1Decoder()
|
cls.bc1_decoder = BC1Decoder()
|
||||||
|
|
||||||
@parameterized.expand(
|
@parameterized.expand([
|
||||||
[
|
("4color", BC1Blocks.greyscale.block, BC1Blocks.greyscale.image),
|
||||||
("4color", BC1Blocks.greyscale.block, BC1Blocks.greyscale.image),
|
("3color", BC1Blocks.three_color.block, BC1Blocks.three_color.image),
|
||||||
("3color", BC1Blocks.three_color.block, BC1Blocks.three_color.image),
|
("3color_black", BC1Blocks.three_color_black.block, BC1Blocks.three_color_black.image)
|
||||||
("3color_black", BC1Blocks.three_color_black.block, BC1Blocks.three_color_black.image),
|
])
|
||||||
]
|
|
||||||
)
|
|
||||||
def test_block(self, _, block, image):
|
def test_block(self, _, block, image):
|
||||||
"""Test decoder output for a single block"""
|
"""Test decoder output for a single block"""
|
||||||
in_tex = BC1Texture(4, 4)
|
in_tex = BC1Texture(4, 4)
|
||||||
|
@ -7,7 +7,6 @@ from PIL import Image, ImageChops
|
|||||||
|
|
||||||
class TestBC4Block(unittest.TestCase):
|
class TestBC4Block(unittest.TestCase):
|
||||||
"""Tests for the BC1Block class"""
|
"""Tests for the BC1Block class"""
|
||||||
|
|
||||||
block_bytes = b'\xF0\x10\x88\x86\x68\xAC\xCF\xFA'
|
block_bytes = b'\xF0\x10\x88\x86\x68\xAC\xCF\xFA'
|
||||||
selectors = [[0, 1, 2, 3]] * 2 + [[4, 5, 6, 7]] * 2
|
selectors = [[0, 1, 2, 3]] * 2 + [[4, 5, 6, 7]] * 2
|
||||||
endpoints = (240, 16)
|
endpoints = (240, 16)
|
||||||
@ -69,8 +68,12 @@ class TestBC4Block(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
@parameterized_class(
|
@parameterized_class(
|
||||||
("name", "w", "h", "wb", "hb"), [("8x8", 8, 8, 2, 2), ("9x9", 9, 9, 3, 3), ("7x7", 7, 7, 2, 2), ("7x9", 7, 9, 2, 3)]
|
("name", "w", "h", "wb", "hb"), [
|
||||||
)
|
("8x8", 8, 8, 2, 2),
|
||||||
|
("9x9", 9, 9, 3, 3),
|
||||||
|
("7x7", 7, 7, 2, 2),
|
||||||
|
("7x9", 7, 9, 2, 3)
|
||||||
|
])
|
||||||
class TestBC4Texture(unittest.TestCase):
|
class TestBC4Texture(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.tex = BC4Texture(self.w, self.h)
|
self.tex = BC4Texture(self.w, self.h)
|
||||||
@ -105,7 +108,7 @@ class TestBC4Texture(unittest.TestCase):
|
|||||||
for y in range(self.hb):
|
for y in range(self.hb):
|
||||||
index = (x + (y * self.wb)) * BC4Block.nbytes
|
index = (x + (y * self.wb)) * BC4Block.nbytes
|
||||||
tb = self.tex[x, y]
|
tb = self.tex[x, y]
|
||||||
fb = BC4Block.frombytes(b[index : index + BC4Block.nbytes])
|
fb = BC4Block.frombytes(b[index:index + BC4Block.nbytes])
|
||||||
self.assertEqual(tb, blocks[y][x], 'incorrect block read from texture')
|
self.assertEqual(tb, blocks[y][x], 'incorrect block read from texture')
|
||||||
self.assertEqual(fb, blocks[y][x], 'incorrect block read from texture bytes')
|
self.assertEqual(fb, blocks[y][x], 'incorrect block read from texture bytes')
|
||||||
|
|
||||||
@ -157,12 +160,10 @@ class TestBC4Decoder(unittest.TestCase):
|
|||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.bc4_decoder = BC4Decoder(0)
|
cls.bc4_decoder = BC4Decoder(0)
|
||||||
|
|
||||||
@parameterized.expand(
|
@parameterized.expand([
|
||||||
[
|
("8value", BC4Blocks.eight_value.block, BC4Blocks.eight_value.image),
|
||||||
("8value", BC4Blocks.eight_value.block, BC4Blocks.eight_value.image),
|
("6value", BC4Blocks.six_value.block, BC4Blocks.six_value.image),
|
||||||
("6value", BC4Blocks.six_value.block, BC4Blocks.six_value.image),
|
])
|
||||||
]
|
|
||||||
)
|
|
||||||
def test_block(self, _, block, image):
|
def test_block(self, _, block, image):
|
||||||
"""Test decoder output for a single block"""
|
"""Test decoder output for a single block"""
|
||||||
in_tex = BC4Texture(4, 4)
|
in_tex = BC4Texture(4, 4)
|
||||||
|
Loading…
Reference in New Issue
Block a user