1 Commits

Author SHA1 Message Date
1aa08a0cda Attempt to port to scikit-build 2022-04-05 00:15:59 -07:00
103 changed files with 611 additions and 967 deletions

View File

@ -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

View File

@ -1,10 +0,0 @@
# Set update schedule for GitHub Actions
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "dev"
schedule:
# Check for updates to GitHub Actions every weekday
interval: "daily"

View File

@ -11,14 +11,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
with:
fetch-depth: 0
# Whether to checkout submodules: `true` to checkout submodules or `recursive` to
# recursively checkout submodules.
submodules: 'true'
- name: Set up Python
uses: actions/setup-python@v4.5.0
with:
python-version: '3.x'
uses: actions/setup-python@v2
- name: Install dependencies
run: |
@ -40,67 +40,80 @@ jobs:
run: python -m twine check dist/*
- name: Upload SDist
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
with:
path: dist/*.tar.gz
build-wheels:
name: Build Wheels on ${{ matrix.os }} ${{ matrix.arch[0] }}
name: Build Wheels for ${{ matrix.os }}-${{ matrix.arch }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ macos-11, windows-latest, ubuntu-latest ]
arch: [ ['x86', 'x86_64', 'AMD64', 'x86_64' ] ] #[suffix, mac, windows, linux] arch names
os: [ macos-latest, windows-latest, ubuntu-latest ]
arch: [ x86_64 ]
include:
- os: ubuntu-latest
arch: [ 'ARM', 'arm64', 'ARM64', 'aarch64' ]
- arch: arm64
os: macos-latest
- arch: aarch64
os: ubuntu-latest
qemu: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
with:
fetch-depth: 0
# Whether to checkout submodules: `true` to checkout submodules or `recursive` to
# recursively checkout submodules.
submodules: 'true'
- name: Install libomp
if: runner.os == 'macOS'
# openMP isnt part of core apple clang for some reason?
# libomp is in homebrew, which works for end users but its not a fat binary
# so we have to install it manually
# compiled dylibs courtesy of https://mac.r-project.org/openmp/ and mirrored on my own server
run: |
wget https://pileof.rocks/openmp-13.0.0-darwin21-Release.tar.gz
sudo tar fvxz openmp-*.tar.gz -C /
run: brew install libomp
- name: Install QEMU
# install QEMU if building for arm linux
uses: docker/setup-qemu-action@v2
if: runner.os == 'linux' && matrix.arch[3] == 'aarch64'
uses: docker/setup-qemu-action@v1
if: matrix.qemu
with:
platforms: arm64
- name: Install test images
run: git clone https://git.pileof.rocks/drewcassidy/quicktex-test-images.git tests/images
- name: Build wheels
uses: pypa/cibuildwheel@v2.11.2
uses: joerick/cibuildwheel@v2.3.1
env:
MACOSX_DEPLOYMENT_TARGET: "10.15"
CIBW_ARCHS_LINUX: ${{ matrix.arch[3] }}
CIBW_BUILD: "cp{37,38,39,310}-*"
CIBW_ARCHS_MACOS: ${{ matrix.arch }}
CIBW_ARCHS_LINUX: ${{ matrix.arch }}
CIBW_ARCHS_WINDOWS: "auto64"
CIBW_MANYLINUX_X86_64_IMAGE: "manylinux2014"
CIBW_MANYLINUX_AARCH64_IMAGE: "manylinux2014"
CIBW_TEST_EXTRAS: "tests"
CIBW_TEST_COMMAND: nosetests {project}/tests -d
CIBW_TEST_SKIP: "*-macosx_arm64"
- name: Upload Wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
with:
path: ./wheelhouse/*.whl
publish:
name: Publish to PyPI and Github
needs: [ build-wheels, build-sdist ]
needs: [build-wheels, build-sdist]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3 # just need the changelog
- uses: actions/checkout@v2
with:
# Whether to checkout submodules: `true` to checkout submodules or `recursive` to
# recursively checkout submodules.
submodules: 'true'
- name: Set up Python
uses: actions/setup-python@v4.5.0
with:
python-version: '3.x'
uses: actions/setup-python@v2
- name: Install dependencies
run: |
@ -113,7 +126,7 @@ jobs:
echo "$(yaclog show -mb)" >> RELEASE.md
- name: Download Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v2
with:
name: artifact
path: dist

6
.gitignore vendored
View File

@ -5,8 +5,6 @@ build/
*.egg-info
*.pyc
*.pyi
*.whl
*.tar.gz
#sphinx
docs/_build/
@ -14,6 +12,9 @@ docs/_build/
#mypy
out
# Test images
tests/images/
# IDEs
**/.idea
@ -33,3 +34,4 @@ compile_commands.json
CTestTestfile.cmake
_deps
cmake-build-*
_skbuild

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "extern/pybind11"]
path = extern/pybind11
url = https://github.com/pybind/pybind11.git
branch = stable

View File

@ -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

View File

@ -2,26 +2,6 @@
All notable changes to this project will be documented in this file
## Unreleased
### Changed
- Updated Pybind11 to version 3.10, adding Python 3.11 support
## 0.1.3 - 2022-04-13
### Fixed
- Fixed quicktex not compiling for python 3.10 on Windows
### Changed
- Reworked CI job, adding wheels for ARM macOS, ARM Linux, and x86 musl Linux.
- Added wheels for python 3.10
- Added a more useful error message when importing quicktex on macOS when libomp.dylib isn't installed
## 0.1.2 - 2022-03-27
### Fixed

View File

@ -1,11 +1,23 @@
cmake_minimum_required(VERSION 3.18)
cmake_minimum_required(VERSION 3.17)
include(CheckIPOSupported)
include(tools/CompilerWarnings.cmake)
set(CMAKE_VERBOSE_MAKEFILE ON)
project(quicktex)
if (SKBUILD)
# Scikit-Build does not add your site-packages to the search path
# automatically, so we need to add it _or_ the pybind11 specific directory
# here.
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" -c
"import pybind11; print(pybind11.get_cmake_dir())"
OUTPUT_VARIABLE _tmp_dir
OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ECHO STDOUT)
list(APPEND CMAKE_PREFIX_PATH "${_tmp_dir}")
endif ()
# Find dependencies
find_package(Python COMPONENTS Interpreter Development.Module)
find_package(Python COMPONENTS Interpreter Development)
find_package(pybind11 CONFIG REQUIRED)
find_package(OpenMP)
@ -30,6 +42,8 @@ file(GLOB HEADER_FILES
"quicktex/s3tc/interpolator/*.h"
)
file(GLOB TEST_FILES "tests/*.cpp")
file(GLOB_RECURSE PYTHON_FILES "src/**/*.py")
# Organize source files together for some IDEs
@ -40,24 +54,36 @@ pybind11_add_module(_quicktex
${SOURCE_FILES}
${HEADER_FILES})
# Set Quicktex version info
add_executable(test_quicktex
${SOURCE_FILES}
${HEADER_FILES}
${TEST_FILES})
target_link_libraries(test_quicktex PRIVATE pybind11::embed)
target_compile_definitions(test_quicktex PRIVATE -DCUSTOM_SYS_PATH="${CMAKE_HOME_DIRECTORY}/env/lib/python3.9/site-packages")
target_compile_definitions(_quicktex PRIVATE VERSION_INFO=${QUICKTEX_VERSION_INFO})
# enable openMP if available
if (OpenMP_CXX_FOUND)
target_link_libraries(_quicktex PUBLIC OpenMP::OpenMP_CXX)
target_link_libraries(test_quicktex PUBLIC OpenMP::OpenMP_CXX)
endif ()
# Set module features, like C/C++ standards
target_compile_features(_quicktex PUBLIC cxx_std_17 c_std_11)
target_compile_features(test_quicktex PUBLIC cxx_std_17 c_std_11)
# Set compiler warnings
set_project_warnings(_quicktex)
set_project_warnings(test_quicktex)
set(CMAKE_VERBOSE_MAKEFILE ON)
# Clang-specific
if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -stdlib=libc++ -fsanitize=undefined")
set(PROJECT_WARNINGS ${CLANG_WARNINGS})
endif ()
install(TARGETS _quicktex LIBRARY DESTINATION quicktex)

View File

@ -1,25 +0,0 @@
# Development
This document outlines how to set up a development environment for Quicktex. Documentation on writing Python extension modules is sparse, so I hope this document is useful for other projects as well. The [Coding Patterns for Python Extensions](https://pythonextensionpatterns.readthedocs.io/en/latest/) site has some useful information and will be linked to often in this document.
## Setting up Debug Python
Many development tools require debug symbols to function, and since the front-end for accessing an extension module is Python, that usually means adding debug symbols to Python. [This Page](https://pythonextensionpatterns.readthedocs.io/en/latest/debugging/debug_python.html) has some instructions on building python with debug symbols.
If you plan to use DTrace, enable the `--with-dtrace` flag when running `configure`.
It's useful for this debug python to have SSL enabled so that packages can be installed using pip. Enable SSL with the `--with-openssl` flag when running `configure`. If you are on macOS and installed OpenSSL through Homebrew, you may need to use `--with-openssl=$(brew --prefix openssl)` to help the compiler find it.
### Installing Debug Python
You can keep the resulting binary in your local copy of the cpython repo and symlink to it, but I like to install it somewhere like `/opt/python-debug/`. The install location is set in the `configure` tool using the `--prefix` flag, and installation is done by running `make install`
### Mixing Debug and Release Python
The debug build of python is slow (It may be possible to build with debug symbols but full optimization, I have not looked into it). If you already have a venv setup for your project, you can just symlink the debug python binary into `env/bin` with a full name like `python3.9d`. Make sure that the debug build has the same minor version (e.g '3.9') as the version you made the virtual environment with to maintain ABI compatibility.
## Profiling with Dtrace
DTrace is the default program profiler on macOS and other Unix systems, but it's also available for use on Windows and Linux. Using DTrace requires building Python with DTrace hooks as seen above.
Your extension module does not need a full debug build to profile, but it does need frame pointers to see the stack trace at each sample, as well as debug symbols to give functions names. The cmake build type `RelWithDebInfo` handles this automatically.

1
MANIFEST.in Normal file
View File

@ -0,0 +1 @@
graft extern

View File

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

View File

@ -13,13 +13,12 @@
# import os
# import sys
# sys.path.insert(0, os.path.abspath('..'))
from datetime import date
# -- Project information -----------------------------------------------------
project = 'Quicktex'
copyright = f'{date.today().year}, Andrew Cassidy'
copyright = '2021, Andrew Cassidy'
author = 'Andrew Cassidy'
# -- General configuration ---------------------------------------------------
@ -29,14 +28,11 @@ author = 'Andrew Cassidy'
# ones.
extensions = [
'myst_parser',
'sphinx_click',
'sphinx_rtd_theme',
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
]
myst_heading_anchors = 2
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@ -74,5 +70,5 @@ autodoc_default_options = {
# should be linked to in this documentation.
intersphinx_mapping = {
'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

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

View File

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

View File

@ -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
```

View File

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

View File

@ -1,35 +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
---
development
Changelog <changelog>
License <license>
```
## Indices and tables
* {ref}`genindex`
* {ref}`modindex`
* {ref}`search`

15
docs/index.rst Normal file
View 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`

View File

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

View File

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

View File

@ -1,9 +1,10 @@
API Reference
=============
Reference
=========
.. toctree::
:maxdepth: 2
conversion.rst
dds.rst
image_utils.rst
formats/index.rst

View File

@ -3,8 +3,9 @@ requires = [
"setuptools>=61",
"setuptools_scm>=6.2",
"wheel",
"cmake>=3.18",
"pybind11~=2.10",
"pybind11>=2.9.0",
"cmake>=3.22",
"scikit-build>0.13",
"ninja; sys_platform != 'win32'",
]
build-backend = "setuptools.build_meta"
@ -13,6 +14,7 @@ build-backend = "setuptools.build_meta"
name = "quicktex"
description = "A fast block compression library for python"
readme = "README.md"
license = { file = "LICENSE.md" }
authors = [{ name = "Andrew Cassidy", email = "drewcassidy@me.com" }]
classifiers = [
@ -36,19 +38,13 @@ dependencies = ["Pillow", "click"]
dynamic = ["version"]
[project.optional-dependencies]
tests = ["parameterized", "pytest"]
docs = [
"Sphinx >= 3.5",
"sphinx-click >= 2.7",
"sphinx-rtd-theme",
"myst-parser >= 0.14",
]
tests = ["nose", "parameterized"]
docs = ["sphinx", "myst-parser", "sphinx-rtd-theme"]
stubs = ["pybind11-stubgen"]
[project.urls]
Docs = "https://quicktex.readthedocs.io/en/"
Source = "https://github.com/drewcassidy/quicktex"
Changelog = "https://github.com/drewcassidy/quicktex/blob/main/CHANGELOG.md"
repository = "https://github.com/drewcassidy/quicktex"
changelog = "https://github.com/drewcassidy/quicktex/blob/main/CHANGELOG.md"
[project.scripts]
quicktex = "quicktex.__main__:main"
@ -60,26 +56,3 @@ package-data = { '*' = ['py.typed', '*.pyi'] } # include stubs
package-dir = { '' = '.' } # without this line, C++ source files get included in the bdist
[tool.setuptools_scm]
[tool.cibuildwheel]
build = "cp*" # only build wheels for cpython.
build-frontend = "build"
test-command = "pytest {project}/tests --verbose"
test-extras = ["tests"]
[tool.cibuildwheel.macos]
archs = ["x86_64", "universal2"] # build fat binaries, or x86-64 for python 3.7
skip = ["cp{38,39,31*}-macosx_x86_64"] # skip x86-only builds where fat binaries are supported
[tool.cibuildwheel.windows]
archs = ["auto64"] # arm64 windows builds not yet supported
[tool.cibuildwheel.linux]
skip = ["cp37-musllinux*", "*musllinux_aarch64*"] # skip targets without available Pillow wheels
manylinux-x86_64-image = "manylinux2014"
manylinux-aarch64-image = "manylinux2014"
[tool.black]
line-length = 120 # 80-column is stupid
target-version = ['py37', 'py38', 'py39', 'py310', 'py310']
skip-string-normalization = true

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich 2020 <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich 2020 <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,8 +1,2 @@
try:
from _quicktex import *
from _quicktex import __version__
except ImportError as e:
if 'libomp.dylib' in e.msg:
print('\033[41m\033[01mERROR: LIBOMP NOT FOUND! PLEASE INSTALL IT WITH \033[04m`brew install libomp`\033[0m')
print('original error message:')
raise e
from _quicktex import *
from _quicktex import __version__

View File

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

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain
@ -96,9 +96,9 @@ template <typename T> T BufferToTexture(py::buffer buf, int width, int height) {
auto dst_size = output.NBytes();
if (info.format != py::format_descriptor<uint8_t>::format()) throw std::runtime_error("Incompatible format in python buffer: expected a byte array.");
if (info.size < (Py_ssize_t)dst_size) std::runtime_error("Incompatible format in python buffer: Input data is smaller than texture size.");
if (info.size < (ssize_t)dst_size) std::runtime_error("Incompatible format in python buffer: Input data is smaller than texture size.");
if (info.ndim == 1) {
if (info.shape[0] < (Py_ssize_t)dst_size) throw std::runtime_error("Incompatible format in python buffer: 1-D buffer has incorrect length.");
if (info.shape[0] < (ssize_t)dst_size) throw std::runtime_error("Incompatible format in python buffer: 1-D buffer has incorrect length.");
if (info.strides[0] != 1) throw std::runtime_error("Incompatible format in python buffer: 1-D buffer is not contiguous.");
} else {
throw std::runtime_error("Incompatible format in python buffer: Incorrect number of dimensions.");
@ -115,9 +115,9 @@ template <typename T> T BufferToPOD(py::buffer buf) {
auto info = buf.request(false);
if (info.format != py::format_descriptor<uint8_t>::format()) throw std::runtime_error("Incompatible format in python buffer: expected a byte array.");
if (info.size < (Py_ssize_t)sizeof(T)) std::runtime_error("Incompatible format in python buffer: Input data is smaller than texture size.");
if (info.size < (ssize_t)sizeof(T)) std::runtime_error("Incompatible format in python buffer: Input data is smaller than texture size.");
if (info.ndim == 1) {
if (info.shape[0] < (Py_ssize_t)sizeof(T)) throw std::runtime_error("Incompatible format in python buffer: 1-D buffer has incorrect length.");
if (info.shape[0] < (ssize_t)sizeof(T)) throw std::runtime_error("Incompatible format in python buffer: 1-D buffer has incorrect length.");
if (info.strides[0] != 1) throw std::runtime_error("Incompatible format in python buffer: 1-D buffer is not contiguous.");
} else {
throw std::runtime_error("Incompatible format in python buffer: Incorrect number of dimensions.");

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich 2020 <richgel99@gmail.com>
and licenced under the public domain

View File

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

View File

@ -1,49 +1,28 @@
import os.path
import click
from PIL import Image
import quicktex.cli.common as common
import os.path
import quicktex.dds as dds
import quicktex.cli.common as common
from PIL import Image
@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(
'-s',
'--suffix',
type=str,
default='',
help="Suffix to append to output file(s). Ignored if output is a single file.",
)
@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.option('-s', '--suffix', type=str, default='', help="Suffix to append to output file(s). Ignored if output is a single file.")
@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))
def decode(flip, remove, suffix, extension, output, filenames):
"""Decode DDS files to images."""
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:
if inpath.suffix != '.dds':
raise click.BadArgumentUsage(f"Input file '{inpath}' is not a DDS file.")

View File

@ -1,14 +1,13 @@
import os
import click
from PIL import Image
import quicktex.cli.common as common
import quicktex.dds as dds
import os
import pathlib
import quicktex.s3tc.bc1
import quicktex.s3tc.bc3
import quicktex.s3tc.bc4
import quicktex.s3tc.bc5
import quicktex.dds as dds
import quicktex.cli.common as common
from PIL import Image
@click.group()
@ -17,31 +16,17 @@ def encode():
@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(
'-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.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):
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:
image = Image.open(inpath)
@ -55,44 +40,17 @@ def encode_format(encoder, four_cc, flip, remove, suffix, output, filenames):
@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('-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.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."""
@ -109,9 +67,7 @@ def encode_auto(level, black, threecolor, flip, remove, suffix, output, filename
bc3_encoder = quicktex.s3tc.bc3.BC3Encoder(level)
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:
image = Image.open(inpath)
@ -134,26 +90,11 @@ def encode_auto(level, black, threecolor, flip, remove, suffix, output, filename
@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.',
)
@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
@ -168,13 +109,7 @@ def encode_bc1(level, black, threecolor, **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('-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):
"""Encode images to BC4 (RGBA, 8-bit interpolated alpha)."""
encode_format.callback(quicktex.s3tc.bc3.BC3Encoder(level), 'DXT5', **kwargs)

View File

@ -4,14 +4,12 @@ import enum
import os
import struct
import typing
from PIL import Image
import quicktex.image_utils
import quicktex.s3tc.bc1 as bc1
import quicktex.s3tc.bc3 as bc3
import quicktex.s3tc.bc4 as bc4
import quicktex.s3tc.bc5 as bc5
from PIL import Image
class DDSFormat:
@ -167,28 +165,8 @@ class DDSFile:
file.write(DDSFile.magic)
# 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(
struct.pack(
'<2I4s5I',
32,
int(self.pf_flags),
bytes(self.four_cc, 'ascii'),
self.pixel_size,
*self.pixel_bitmasks,
)
)
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(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))
assert file.tell() == 4 + DDSFile.header_bytes, 'error writing file: incorrect header size'

View File

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

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain
@ -58,7 +58,7 @@ template <size_t N> class OrderTable {
static bool Generate() {
static_assert(N == 4 || N == 3);
std::scoped_lock{table_mutex};
table_mutex.lock();
if (!generated) {
hashes = new std::array<Hash, HashCount>();
factors = new std::array<Vector4, OrderCount>();
@ -85,6 +85,8 @@ template <size_t N> class OrderTable {
generated = true;
}
table_mutex.unlock();
assert(generated);
return true;
}

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com> and licenced under the public domain
This program is free software: you can redistribute it and/or modify

View File

@ -1,5 +1,5 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain

120
setup.py
View File

@ -1,111 +1,15 @@
import os
import re
import subprocess
import sys
import pybind11
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
try:
from skbuild import setup
except ImportError:
print(
"Please update pip, you need pip 10 or greater,\n"
" or you need to install the PEP 518 requirements in pyproject.toml yourself",
file=sys.stderr,
)
raise
project_path = os.path.dirname(os.path.realpath(__file__))
# A CMakeExtension needs a sourcedir instead of a file list.
# The name must be the _single_ output extension from the CMake build.
# If you need multiple extensions, see scikit-build.
class CMakeExtension(Extension):
def __init__(self, name, sourcedir=""):
Extension.__init__(self, name, sources=[])
self.sourcedir = os.path.abspath(sourcedir)
class CMakeBuild(build_ext):
def build_extension(self, ext):
from setuptools_scm import get_version
version = get_version(root='.', relative_to=__file__)
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
# required for auto-detection of auxiliary "native" libs
if not extdir.endswith(os.path.sep):
extdir += os.path.sep
cfg = "Debug" if self.debug else "RelWithDebInfo"
if 'QUICKTEX_DEBUG' in os.environ:
cfg = "Debug"
# CMake lets you override the generator - we need to check this.
# Can be set with Conda-Build, for example.
cmake_generator = os.environ.get("CMAKE_GENERATOR", "")
# Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON
cmake_args = [
"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}".format(extdir),
"-Dpybind11_DIR={}".format(pybind11.get_cmake_dir()),
"-DPython_EXECUTABLE={}".format(sys.executable),
"-DPython_ROOT_DIR={}".format(os.path.dirname(sys.executable)),
"-DQUICKTEX_VERSION_INFO={}".format(version), # include version info in module
"-DQUICKTEX_MODULE_ONLY=TRUE", # only build the module, not the wrapper
"-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
"-U",
"CMAKE_MAKE_PROGRAM",
]
build_args = []
if self.compiler.compiler_type != "msvc":
# Using Ninja-build since it a) is available as a wheel and b)
# multithreads automatically. MSVC would require all variables be
# exported for Ninja to pick it up, which is a little tricky to do.
# Users can override the generator with CMAKE_GENERATOR in CMake
# 3.15+.
if not cmake_generator:
cmake_args += ["-GNinja"]
else:
# Single config generators are handled "normally"
single_config = any(x in cmake_generator for x in {"NMake", "Ninja"})
# CMake allows an arch-in-generator style for backward compatibility
contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"})
# Convert distutils Windows platform specifiers to CMake -A arguments
plat_to_cmake = {"win32": "Win32", "win-amd64": "x64", "win-arm32": "ARM", "win-arm64": "ARM64"}
# Specify the arch if using MSVC generator, but only if it doesn't
# contain a backward-compatibility arch spec already in the
# generator name.
if not single_config and not contains_arch:
cmake_args += ["-A", plat_to_cmake[self.plat_name]]
# Multi-config generators have a different way to specify configs
if not single_config:
cmake_args += ["-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir)]
build_args += ["--config", cfg]
if sys.platform.startswith("darwin"):
# Cross-compile support for macOS - respect ARCHFLAGS if set
archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", ""))
if archs:
cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))]
# Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level
# across all generators.
if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ:
# self.parallel is a Python 3 only way to set parallel jobs by hand
# using -j in the build_ext call, not supported by pip or PyPA-build.
if hasattr(self, "parallel") and self.parallel:
# CMake 3.12+ only.
build_args += ["-j{}".format(self.parallel)]
if not os.path.exists(self.build_temp):
os.makedirs(self.build_temp)
subprocess.check_call(["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
# logic and declaration, and simpler if you include description/version in a file.
setup(use_scm_version=True, ext_modules=[CMakeExtension("_quicktex")], cmdclass={"build_ext": CMakeBuild})
setup(
cmake_install_dir='.'
)

View File

@ -1,3 +1,9 @@
import os.path
# Some checks to run before tests can begin
images_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'images')
assert os.path.isdir(images_path), 'test images repo not present'
assert os.path.isfile(os.path.join(images_path, '__init__.py')), 'images __init__.py not present, is the test image repo present?'
bp_size = os.path.getsize(os.path.join(images_path, 'Boilerplate.png'))
assert bp_size == 955989, 'Boilerplate.png is the wrong size, is the test image repo checked out with LFS enabled?'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 934 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 KiB

View File

@ -1,32 +0,0 @@
from PIL import Image
from quicktex.s3tc.bc1 import BC1Block
from quicktex.s3tc.bc4 import BC4Block
from quicktex import RawTexture
import os.path
image_path = os.path.dirname(os.path.realpath(__file__))
class BC1Blocks:
class Entry:
def __init__(self, filename, block):
path = os.path.join(image_path, 'bc1', filename)
self.image = Image.open(path).convert('RGBA')
self.texture = RawTexture.frombytes(self.image.tobytes('raw', 'RGBA'), *self.image.size)
self.block = block
greyscale = Entry('greyscale_unpacked.png', BC1Block.frombytes(b'\xFF\xFF\x49\x4A\x78\x78\x78\x78'))
three_color = Entry('3color_unpacked.png', BC1Block.frombytes(b'\xE0\x07\x00\xF8\x29\x29\x29\x29'))
three_color_black = Entry('3color_black_unpacked.png', BC1Block.frombytes(b'\xE0\x07\x00\xF8\x27\x27\x27\x27'))
class BC4Blocks:
class Entry:
def __init__(self, filename, block):
path = os.path.join(image_path, 'bc4', filename)
self.image = Image.open(path).convert('RGBA')
self.texture = RawTexture.frombytes(self.image.tobytes('raw', 'RGBA'), *self.image.size)
self.block = block
six_value = Entry('6value.png', BC4Block(8, 248, [[0, 1, 2, 3]] * 2 + [[4, 5, 6, 7]] * 2))
eight_value = Entry('8value.png', BC4Block(240, 16, [[0, 1, 2, 3]] * 2 + [[4, 5, 6, 7]] * 2))

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

44
tests/run_tests.cpp Normal file
View File

@ -0,0 +1,44 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file allows for easy debugging in CLion or other IDEs that dont natively support cross-debugging between Python and C++
#include <pybind11/embed.h>
#include <array>
#include <string>
namespace py = pybind11;
using namespace pybind11::literals;
#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)
int main() {
py::scoped_interpreter guard{};
py::module_ site = py::module_::import("site");
site.attr("addsitedir")(CUSTOM_SYS_PATH);
py::module_ nose = py::module_::import("nose");
py::module_ tests = py::module_::import("tests");
py::list argv(1);
nose.attr("runmodule")("name"_a = "tests.test_bc1", "exit"_a = false);
}

View File

@ -1,206 +1,213 @@
import math
import pytest
import unittest
import nose
import os.path
from parameterized import parameterized, parameterized_class
import quicktex
from quicktex.s3tc.bc1 import BC1Block, BC1Texture, BC1Encoder, BC1Decoder
from tests.images import BC1Blocks, image_path
from PIL import Image, ImageChops
from quicktex.s3tc.bc1 import BC1Block, BC1Texture, BC1Encoder, BC1Decoder
from .images import BC1Blocks
in_endpoints = ((253, 254, 255), (65, 70, 67)) # has some small changes that should encode the same in 5:6:5
in_endpoints = ((253, 254, 255), (65, 70, 67)) # has some small changes that should encode the same
out_endpoints = ((255, 255, 255, 255), (66, 69, 66, 255))
selectors = [[0, 2, 3, 1]] * 4
block_bytes = b'\xff\xff\x28\x42\x78\x78\x78\x78'
class TestBC1Block:
class TestBC1Block(unittest.TestCase):
"""Tests for the BC1Block class"""
def test_size(self):
"""Test the size and dimensions of BC1Block"""
assert BC1Block.nbytes == 8
assert BC1Block.width == 4
assert BC1Block.height == 4
assert BC1Block.size == (4, 4)
self.assertEqual(BC1Block.nbytes, 8, 'incorrect block size')
self.assertEqual(BC1Block.width, 4, 'incorrect block width')
self.assertEqual(BC1Block.height, 4, 'incorrect block width')
self.assertEqual(BC1Block.size, (4, 4), 'incorrect block dimensions')
def test_buffer(self):
"""Test the buffer protocol of BC1Block"""
block = BC1Block()
mv = memoryview(block)
mv[:] = block_bytes
assert not mv.readonly
assert mv.c_contiguous
assert mv.ndim == 1
assert mv.nbytes == 8
assert mv.format == 'B'
assert mv.tobytes() == block_bytes
assert mv.tobytes() == block.tobytes()
self.assertFalse(mv.readonly, 'buffer is readonly')
self.assertTrue(mv.c_contiguous, 'buffer is not contiguous')
self.assertEqual(mv.ndim, 1, 'buffer is multidimensional')
self.assertEqual(mv.nbytes, BC1Block.nbytes, 'buffer is the wrong size')
self.assertEqual(mv.format, 'B', 'buffer has the wrong format')
mv[:] = block_bytes
self.assertEqual(mv.tobytes(), block_bytes, 'incorrect buffer data')
def test_constructor(self):
"""Test constructing a block out of endpoints and selectors"""
block = BC1Block(*in_endpoints, selectors)
assert block.tobytes() == block_bytes
assert block.selectors == selectors
assert block.endpoints == out_endpoints
assert not block.is_3color
self.assertEqual(block.tobytes(), block_bytes, 'incorrect block bytes')
self.assertEqual(block.selectors, selectors, 'incorrect selectors')
self.assertEqual(block.endpoints, out_endpoints, 'incorrect endpoints')
self.assertFalse(block.is_3color, 'incorrect color mode')
def test_frombytes(self):
"""Test constructing a block out of raw data"""
block = BC1Block.frombytes(block_bytes)
assert block.tobytes() == block_bytes
assert block.selectors == selectors
assert block.endpoints == out_endpoints
assert not block.is_3color
self.assertEqual(block.tobytes(), block_bytes, 'incorrect block bytes')
self.assertEqual(block.selectors, selectors, 'incorrect selectors')
self.assertEqual(block.endpoints, out_endpoints, 'incorrect endpoints')
self.assertFalse(block.is_3color, 'incorrect color mode')
def test_eq(self):
"""Test equality between two identical blocks"""
block1 = BC1Block.frombytes(block_bytes)
block2 = BC1Block.frombytes(block_bytes)
assert block1 == block2
self.assertEqual(block1, block2, 'identical blocks not equal')
# noinspection PyMethodMayBeStatic
@pytest.mark.parametrize('w', [7, 8, 9])
@pytest.mark.parametrize('h', [7, 8, 9])
class TestBC1Texture:
def test_dimensions(self, w, h):
"""Test dimensions of BC1Texture in pixels, blocks, and bytes"""
tex = BC1Texture(w, h)
wb = math.ceil(w / 4)
hb = math.ceil(h / 4)
@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)
])
class TestBC1Texture(unittest.TestCase):
def setUp(self):
self.tex = BC1Texture(self.w, self.h)
self.nbytes = self.wb * self.hb * BC1Block.nbytes
assert tex.nbytes == BC1Block.nbytes * wb * hb # block width x block height
assert len(tex.tobytes()) == tex.nbytes
def test_size(self):
"""Test size of BC1Texture in bytes"""
self.assertEqual(self.tex.nbytes, self.nbytes, 'incorrect texture size')
self.assertEqual(len(self.tex.tobytes()), self.nbytes, 'incorrect texture size from tobytes')
assert tex.width == w
assert tex.height == h
assert tex.size == (w, h)
def test_dimensions(self):
"""Test dimensions of BC1Texture in pixels"""
self.assertEqual(self.tex.width, self.w, 'incorrect texture width')
self.assertEqual(self.tex.height, self.h, 'incorrect texture height')
self.assertEqual(self.tex.size, (self.w, self.h), 'incorrect texture dimensions')
assert tex.width_blocks == wb
assert tex.height_blocks == hb
assert tex.size_blocks == (wb, hb)
def test_dimensions_blocks(self):
"""Test dimensions of BC1Texture in blocks"""
self.assertEqual(self.tex.width_blocks, self.wb, 'incorrect texture width_blocks')
self.assertEqual(self.tex.height_blocks, self.hb, 'incorrect texture width_blocks')
self.assertEqual(self.tex.size_blocks, (self.wb, self.hb), 'incorrect texture dimensions_blocks')
def test_blocks(self, w, h):
def test_blocks(self):
"""Test getting and setting blocks to BC1Texture"""
tex = BC1Texture(w, h)
blocks = [[BC1Block.frombytes(bytes([x, y] + [0] * 6)) for x in range(self.wb)] for y in range(self.hb)]
for x in range(self.wb):
for y in range(self.hb):
self.tex[x, y] = blocks[y][x]
# generate garbage blocks with the x and y index in the first 2 bytes
blocks = [
[BC1Block.frombytes(bytes([x, y] + [0] * 6)) for x in range(tex.width_blocks)]
for y in range(tex.height_blocks)
]
# assign those blocks to the texture
for x in range(tex.width_blocks):
for y in range(tex.height_blocks):
tex[x, y] = blocks[y][x]
b = self.tex.tobytes()
for x in range(self.wb):
for y in range(self.hb):
index = (x + (y * self.wb)) * BC1Block.nbytes
tb = self.tex[x, y]
fb = BC1Block.frombytes(b[index:index + BC1Block.nbytes])
self.assertEqual(tb, blocks[y][x], 'incorrect block read from texture')
self.assertEqual(fb, blocks[y][x], 'incorrect block read from texture bytes')
# get the blocks and analyze
b = tex.tobytes()
for x in range(tex.width_blocks):
for y in range(tex.height_blocks):
index = (x + (y * tex.width_blocks)) * BC1Block.nbytes
tb = tex[x, y]
fb = BC1Block.frombytes(b[index : index + BC1Block.nbytes])
assert tb == blocks[y][x]
assert fb == blocks[y][x]
self.assertEqual(self.tex[-1, -1], self.tex[self.wb - 1, self.hb - 1], 'incorrect negative subscripting')
def text_subscript(self, w, h):
"""Test BC1Texture subscripting for blocks"""
tex = BC1Texture(w, h)
with self.assertRaises(IndexError):
thing = self.tex[self.wb, self.hb]
with self.assertRaises(IndexError):
thing = self.tex[-1 - self.wb, -1 - self.hb]
# ensure negative wraparound works
assert tex[-1, -1] == tex[tex.width_blocks - 1, tex.height_blocks - 1]
with pytest.raises(IndexError):
_ = tex[tex.width_blocks, tex.height_blocks]
with pytest.raises(IndexError):
_ = tex[-1 - tex.width_blocks, -1 - tex.height_blocks]
def test_buffer(self, w, h):
def test_buffer(self):
"""Test the buffer protocol of BC1Texture"""
tex = BC1Texture(w, h)
mv = memoryview(tex)
mv = memoryview(self.tex)
data = block_bytes * tex.width_blocks * tex.height_blocks
self.assertFalse(mv.readonly, 'buffer is readonly')
self.assertTrue(mv.c_contiguous, 'buffer is not contiguous')
self.assertEqual(mv.nbytes, self.nbytes, 'buffer is the wrong size')
self.assertEqual(mv.format, 'B', 'buffer has the wrong format')
data = block_bytes * self.wb * self.hb
mv[:] = data
assert not mv.readonly
assert mv.c_contiguous
assert mv.nbytes == tex.nbytes
assert mv.format == 'B'
assert mv.tobytes() == data
self.assertEqual(mv.tobytes(), data, 'incorrect buffer data')
@pytest.mark.parametrize(
'color_mode',
[BC1Encoder.ColorMode.FourColor, BC1Encoder.ColorMode.ThreeColor, BC1Encoder.ColorMode.ThreeColorBlack],
)
class TestBC1Encoder:
@parameterized_class(
("name", "color_mode"), [
("4Color", BC1Encoder.ColorMode.FourColor),
("3Color", BC1Encoder.ColorMode.ThreeColor),
("3Color_Black", BC1Encoder.ColorMode.ThreeColorBlack),
])
class TestBC1Encoder(unittest.TestCase):
"""Test BC1Encoder"""
def test_block_4color(self, color_mode):
@classmethod
def setUpClass(cls):
cls.bc1_encoder = BC1Encoder(5, cls.color_mode)
def test_block_4color(self):
"""Test encoder output with 4 color greyscale test block"""
encoder = BC1Encoder(color_mode=color_mode)
out_tex = encoder.encode(BC1Blocks.greyscale.texture)
out_tex = self.bc1_encoder.encode(BC1Blocks.greyscale.texture)
self.assertEqual(out_tex.size_blocks, (1, 1), 'encoded texture has multiple blocks')
out_block = out_tex[0, 0]
assert out_tex.size_blocks == (1, 1)
self.assertFalse(out_block.is_3color, 'returned 3color mode for greyscale test block')
self.assertEqual(out_block, BC1Blocks.greyscale.block, 'encoded block is incorrect')
assert not out_block.is_3color
assert out_block == BC1Blocks.greyscale.block
def test_block_3color(self, color_mode):
def test_block_3color(self):
"""Test encoder output with 3 color test block"""
encoder = BC1Encoder(color_mode=color_mode)
out_tex = encoder.encode(BC1Blocks.three_color.texture)
out_tex = self.bc1_encoder.encode(BC1Blocks.three_color.texture)
self.assertEqual(out_tex.size_blocks, (1, 1), 'encoded texture has multiple blocks')
out_block = out_tex[0, 0]
assert out_tex.size_blocks == (1, 1)
if encoder.color_mode != BC1Encoder.ColorMode.FourColor:
# we only care about the selectors if we are in 3 color mode
assert out_block.is_3color
assert out_block == BC1Blocks.three_color.block
if self.color_mode != BC1Encoder.ColorMode.FourColor: # we only care about the selectors if we are in 3 color mode
self.assertTrue(out_block.is_3color, 'returned 4-color block for 3 color test block')
self.assertEqual(out_block, BC1Blocks.three_color.block, 'encoded block is incorrect')
else:
assert not out_block.is_3color
self.assertFalse(out_block.is_3color, 'returned 3-color block in 4-color mode')
def test_block_3color_black(self, color_mode):
def test_block_3color_black(self):
"""Test encoder output with 3 color test block with black pixels"""
encoder = BC1Encoder(color_mode=color_mode)
out_tex = encoder.encode(BC1Blocks.three_color_black.texture)
out_tex = self.bc1_encoder.encode(BC1Blocks.three_color_black.texture)
self.assertEqual(out_tex.size_blocks, (1, 1), 'encoded texture has multiple blocks')
out_block = out_tex[0, 0]
assert out_tex.size_blocks == (1, 1)
has_black = 3 in [j for row in out_block.selectors for j in row]
if color_mode == BC1Encoder.ColorMode.ThreeColorBlack:
# we only care about the selectors if we are in 3 color black mode
assert out_block.is_3color
assert has_black
assert out_block == BC1Blocks.three_color_black.block
elif color_mode == BC1Encoder.ColorMode.ThreeColor:
assert not (has_black and out_block.is_3color)
if self.color_mode == BC1Encoder.ColorMode.ThreeColorBlack: # we only care about the selectors if we are in 3 color black mode
self.assertTrue(out_block.is_3color, 'returned 4-color block for 3 color test block with black')
self.assertTrue(has_black, 'block does not have black pixels as expected')
self.assertEqual(out_block, BC1Blocks.three_color_black.block, "encoded block is incorrect")
elif self.color_mode == BC1Encoder.ColorMode.ThreeColor:
self.assertFalse(has_black and out_block.is_3color, 'returned 3color block with black pixels')
else:
assert not out_block.is_3color
self.assertFalse(out_block.is_3color, 'returned 3-color block in 4-color mode')
@pytest.mark.parametrize('texture', [BC1Blocks.greyscale, BC1Blocks.three_color, BC1Blocks.three_color_black])
class TestBC1Decoder:
class TestBC1Decoder(unittest.TestCase):
"""Test BC1Decoder"""
def test_block(self, texture):
@classmethod
def setUpClass(cls):
cls.bc1_decoder = BC1Decoder()
@parameterized.expand([
("4color", BC1Blocks.greyscale.block, BC1Blocks.greyscale.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):
"""Test decoder output for a single block"""
block = texture.block
image = texture.image
decoder = BC1Decoder()
in_tex = BC1Texture(4, 4)
in_tex[0, 0] = block
out_tex = decoder.decode(in_tex)
out_tex = self.bc1_decoder.decode(in_tex)
assert out_tex.size == (4, 4)
self.assertEqual(out_tex.size, (4, 4), 'decoded texture has incorrect dimensions')
out_img = Image.frombytes('RGBA', (4, 4), out_tex.tobytes())
img_diff = ImageChops.difference(out_img, image).convert('L')
img_hist = img_diff.histogram()
assert img_hist[0] == 16
self.assertEqual(16, img_hist[0], 'decoded block is incorrect')
if __name__ == '__main__':
nose.main()

Some files were not shown because too many files have changed in this diff Show More