Compare commits

...

12 Commits

Author SHA1 Message Date
55f0ced229 getting started instructions 2022-05-04 23:29:28 -07:00
eb7b259d53 Autogenerated command documentation from helpstrings 2022-05-04 23:03:06 -07:00
03801e2e1b New index page and remove broken page 2022-05-04 22:51:40 -07:00
3a28ec690c Merge remote-tracking branch 'origin/dependabot/github_actions/pypa/cibuildwheel-2.5.0' into dev 2022-05-02 22:42:11 -07:00
697f7243a0 Documentation nice-to-haves 2022-05-02 22:41:39 -07:00
22e1455ceb tell dependabot to target the dev branch 2022-05-02 22:26:05 -07:00
dependabot[bot]
c13f64828f
Bump pypa/cibuildwheel from 2.4.0 to 2.5.0
Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/pypa/cibuildwheel/releases)
- [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md)
- [Commits](https://github.com/pypa/cibuildwheel/compare/v2.4.0...2.5.0)

---
updated-dependencies:
- dependency-name: pypa/cibuildwheel
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 11:41:47 +00:00
9a57b096f5 Set min version for sphinx tools 2022-04-21 09:34:21 -07:00
82f079f1b6 Enable RTD building 2022-04-19 21:55:00 -07:00
2c72b7ad22 Ignore previous commit in blames 2022-04-18 19:56:41 -07:00
cb84f32eda Migrate code style to Black 2022-04-18 19:53:26 -07:00
b34fdf2316 Ignore wheels and sdists 2022-04-16 22:40:54 -07:00
25 changed files with 389 additions and 116 deletions

8
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,8 @@
# 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

View File

@ -4,6 +4,7 @@ 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"

View File

@ -76,7 +76,7 @@ jobs:
platforms: arm64 platforms: arm64
- name: Build wheels - name: Build wheels
uses: pypa/cibuildwheel@v2.4.0 uses: pypa/cibuildwheel@2.5.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
View File

@ -5,6 +5,8 @@ build/
*.egg-info *.egg-info
*.pyc *.pyc
*.pyi *.pyi
*.whl
*.tar.gz
#sphinx #sphinx
docs/_build/ docs/_build/

28
.readthedocs.yaml Normal file
View File

@ -0,0 +1,28 @@
# .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

2
docs/changelog.md Normal file
View File

@ -0,0 +1,2 @@
```{include} ../CHANGELOG.md
```

View File

@ -13,12 +13,13 @@
# 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 = '2021, Andrew Cassidy' copyright = f'{date.today().year}, Andrew Cassidy'
author = 'Andrew Cassidy' author = 'Andrew Cassidy'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
@ -28,11 +29,14 @@ 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']
@ -70,5 +74,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),
} }

View File

@ -0,0 +1,7 @@
# Command Reference
```{eval-rst}
.. click:: quicktex.__main__:main
:prog: quicktex
:nested: full
```

View File

@ -0,0 +1,84 @@
# 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
```

10
docs/handbook/index.md Normal file
View File

@ -0,0 +1,10 @@
# Handbook
```{toctree}
---
maxdepth: 3
---
commands
getting_started
```

34
docs/index.md Normal file
View File

@ -0,0 +1,34 @@
# 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`

View File

@ -1,15 +0,0 @@
Welcome to Quicktex's documentation!
========================================
.. toctree::
:maxdepth: 2
reference/index.rst
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

2
docs/license.md Normal file
View File

@ -0,0 +1,2 @@
```{include} ../LICENSE.md
```

View File

@ -1,10 +0,0 @@
.. py:currentmodule:: quicktex
Conversion
============
.. autoclass:: BlockEncoder
:members:
.. autoclass:: BlockDecoder
:members:

View File

@ -1,10 +1,9 @@
Reference API 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

View File

@ -37,10 +37,16 @@ dynamic = ["version"]
[project.optional-dependencies] [project.optional-dependencies]
tests = ["parameterized"] tests = ["parameterized"]
docs = ["sphinx", "myst-parser", "sphinx-rtd-theme"] docs = [
"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"
@ -73,3 +79,8 @@ 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

View File

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

View File

@ -1,7 +1,8 @@
from PIL import Image
from typing import List
import pathlib import pathlib
from typing import List
import click import click
from PIL import Image
def get_decoded_extensions(feature: str = 'open') -> List[str]: def get_decoded_extensions(feature: str = 'open') -> List[str]:

View File

@ -1,28 +1,49 @@
import click
import os.path import os.path
import quicktex.dds as dds
import quicktex.cli.common as common import click
from PIL import Image from PIL import Image
import quicktex.cli.common as common
import quicktex.dds as dds
@click.command() @click.command()
@click.option('-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image after converting.") @click.option(
'-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('-s', '--suffix', type=str, default='', help="Suffix to append to output file(s). Ignored if output is a single file.") @click.option(
@click.option('-x', '--extension', '-s',
callback=common.validate_decoded_extension, '--suffix',
type=str, default='.png', show_default=True, type=str,
help="Extension to use for output. Ignored if output is a single file. Output filetype is deduced from this") default='',
@click.option('-o', '--output', help="Suffix to append to output file(s). Ignored if output is a single file.",
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.option(
'-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(path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else '') as bar: 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: 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.")

View File

@ -1,13 +1,14 @@
import click
import os import os
import pathlib
import click
from PIL import Image
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()
@ -16,17 +17,31 @@ def encode():
@click.command() @click.command()
@click.option('-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image before converting.") @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('-s', '--suffix', type=str, default='', help="Suffix to append to output file(s). Ignored if output is a single file.") @click.option(
@click.option('-o', '--output', '-s',
type=click.Path(writable=True), default=None, '--suffix',
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.") 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)) @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(path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else '') as bar: 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: for inpath, outpath in bar:
image = Image.open(inpath) image = Image.open(inpath)
@ -40,17 +55,44 @@ def encode_format(encoder, four_cc, flip, remove, suffix, output, filenames):
@click.command('auto') @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(
@click.option('-b/-B', '--black/--no-black', '-l',
help='[BC1 only] Enable 3-color mode for blocks containing black or very dark pixels. --3color must also be enabled for this to work.' '--level',
' (Important: engine/shader MUST ignore decoded texture alpha if this flag is enabled!)') type=click.IntRange(0, 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.') default=18,
@click.option('-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image before converting.") 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('-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(
@click.option('-o', '--output', '-s',
type=click.Path(writable=True), default=None, '--suffix',
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.") 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)) @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."""
@ -67,7 +109,9 @@ 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(path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else '') as bar: 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: for inpath, outpath in bar:
image = Image.open(inpath) image = Image.open(inpath)
@ -90,11 +134,26 @@ def encode_auto(level, black, threecolor, flip, remove, suffix, output, filename
@click.command('bc1') @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(
@click.option('-b/-B', '--black/--no-black', '-l',
help='Enable 3-color mode for blocks containing black or very dark pixels. --3color must also be enabled for this to work.' '--level',
' (Important: engine/shader MUST ignore decoded texture alpha if this flag is enabled!)') type=click.IntRange(0, 18),
@click.option('-3/-4', '--3color/--4color', 'threecolor', default=True, help='Enable 3-color mode for non-black pixels. Higher quality, but slightly slower.') 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): 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
@ -109,7 +168,13 @@ def encode_bc1(level, black, threecolor, **kwargs):
@click.command('bc3') @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(
'-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)

View File

@ -4,12 +4,14 @@ 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:
@ -165,8 +167,28 @@ class DDSFile:
file.write(DDSFile.magic) file.write(DDSFile.magic)
# WRITE HEADER # WRITE HEADER
file.write(struct.pack('<7I44x', DDSFile.header_bytes, int(self.flags), self.size[1], self.size[0], self.pitch, self.depth, self.mipmap_count)) file.write(
file.write(struct.pack('<2I4s5I', 32, int(self.pf_flags), bytes(self.four_cc, 'ascii'), self.pixel_size, *self.pixel_bitmasks)) struct.pack(
'<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'

View File

@ -1,8 +1,9 @@
"""Various utilities for working with Pillow images""" """Various utilities for working with Pillow images"""
from PIL import Image
from typing import List, Tuple, Optional
import math import math
from typing import List, Tuple, Optional
from PIL import Image
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]]:

View File

@ -22,6 +22,7 @@ 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)))
@ -45,7 +46,8 @@ 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", "CMAKE_MAKE_PROGRAM", "-U",
"CMAKE_MAKE_PROGRAM",
] ]
build_args = [] build_args = []
@ -81,9 +83,7 @@ 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 += [ cmake_args += ["-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir)]
"-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,12 +104,8 @@ 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( subprocess.check_call(["cmake", ext.sourcedir] + cmake_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)
)
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

View File

@ -58,12 +58,8 @@ class TestBC1Block(unittest.TestCase):
@parameterized_class( @parameterized_class(
("name", "w", "h", "wb", "hb"), [ ("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)]
("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)
@ -98,7 +94,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')
@ -124,11 +120,13 @@ 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"""
@ -189,11 +187,13 @@ 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), [
("3color", BC1Blocks.three_color.block, BC1Blocks.three_color.image), ("4color", BC1Blocks.greyscale.block, BC1Blocks.greyscale.image),
("3color_black", BC1Blocks.three_color_black.block, BC1Blocks.three_color_black.image) ("3color", BC1Blocks.three_color.block, BC1Blocks.three_color.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)

View File

@ -7,6 +7,7 @@ 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)
@ -68,12 +69,8 @@ class TestBC4Block(unittest.TestCase):
@parameterized_class( @parameterized_class(
("name", "w", "h", "wb", "hb"), [ ("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)]
("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)
@ -108,7 +105,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')
@ -160,10 +157,12 @@ 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), [
("6value", BC4Blocks.six_value.block, BC4Blocks.six_value.image), ("8value", BC4Blocks.eight_value.block, BC4Blocks.eight_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)