mirror of
https://github.com/drewcassidy/quicktex.git
synced 2024-09-13 06:37:34 +00:00
Compare commits
294 Commits
64757f34c8
...
0.2.1
Author | SHA1 | Date | |
---|---|---|---|
a05c1e352e | |||
1f7aad7218 | |||
23133eb802 | |||
0448dbe6e1 | |||
ec7953dcff | |||
3280fc74be | |||
cbec93ed55 | |||
8509384bff | |||
1c86b09ca0 | |||
d4eada16f9 | |||
aed575edc6 | |||
4cdcb65f3a | |||
0a66fcca20 | |||
37f0673e95 | |||
b81df96990 | |||
38beffef05 | |||
0dccd1cd07 | |||
7ea104f712 | |||
9cb60f0ce2 | |||
15e0c68df6 | |||
9f54349556 | |||
71c069d30c | |||
661536e6f6 | |||
920059bea1 | |||
daae86cf50 | |||
5c87c82702 | |||
ddbeff43cb | |||
5c94782876 | |||
9eaaf901f3 | |||
c79ffc8794 | |||
55f0ced229 | |||
eb7b259d53 | |||
03801e2e1b | |||
3a28ec690c | |||
697f7243a0 | |||
22e1455ceb | |||
c13f64828f | |||
9a57b096f5 | |||
82f079f1b6 | |||
2c72b7ad22 | |||
cb84f32eda | |||
b34fdf2316 | |||
ac4e5b2679 | |||
25e74b9b08 | |||
a881a0a36b | |||
3fdfc3ecaa | |||
b440543de3 | |||
23ed54c7a2 | |||
e7e8657100 | |||
2a07db8c8f | |||
b8a80235f8 | |||
4cac24798e | |||
9b6097373e | |||
b954ac6ccc | |||
593a0c3f46 | |||
3b73bc8bce | |||
abeb08fc81 | |||
77637f6abd | |||
df6d5b1848 | |||
b5aea803d5 | |||
b80a6d2229 | |||
dac7f07db4 | |||
7dfefa3007 | |||
eaca455a08 | |||
e5ccdbb4f4 | |||
94d88c7e00 | |||
9421a6d372 | |||
cab0eeebae | |||
3d98b37a37 | |||
654b6d628a | |||
1502c5318c | |||
421876ab0f | |||
29590e0323 | |||
8b4e3c5746 | |||
8e7b95609c | |||
71379b7ae1 | |||
d1346ca11d | |||
e488dbcbff | |||
1f7b45aa57 | |||
70b7251eae | |||
e8e0f4e29b | |||
6d7f56476f | |||
b4d2388615 | |||
f7d57aa859 | |||
24b064e6b4 | |||
28930d89d5 | |||
4f25e0f750 | |||
433e728424 | |||
4939171d5d | |||
f0f132328c | |||
7a11901aa6 | |||
cb8b251baf | |||
463956b63b | |||
22cc5f6148 | |||
0478532cb9 | |||
f2873f3a38 | |||
2618faadfc | |||
38d3805add | |||
f87226ad50 | |||
8967bc2f1a | |||
b3d6585799 | |||
cde6ae006e | |||
2b36086bf5 | |||
faa7e4df08 | |||
f353bf6e92 | |||
7954ec1de6 | |||
890d60cd82 | |||
3ebf07ae74 | |||
4a8e416f1f | |||
a07fd55014 | |||
803dccae2d | |||
148fb36273 | |||
b3f6350353 | |||
d4c91e21b8 | |||
de2dc0c0a8 | |||
96dc62a1b0 | |||
c18cebb519 | |||
784f2393f1 | |||
83b4914bb0 | |||
72aeb54c18 | |||
60a944500b | |||
f34824b45b | |||
68e37ca836 | |||
46a62b0dab | |||
9156ac199b | |||
69fdd6a4c8 | |||
d5a3f09a58 | |||
0152e99c44 | |||
560acb20ea | |||
362c1c0715 | |||
4c6b683616 | |||
b8aba4d382 | |||
1f0a577d7f | |||
76da50d003 | |||
8a85f5c920 | |||
156880d430 | |||
8b6ea69300 | |||
09e05b7cde | |||
7149864173 | |||
2fa4f59104 | |||
ac16b14a92 | |||
7154857323 | |||
6e0c855470 | |||
253b9c76ba | |||
a4a19505d7 | |||
f5bacd5940 | |||
d77665a8a9 | |||
48e118042d | |||
42c9110a40 | |||
d100f10931 | |||
822633ca7e | |||
88f555d076 | |||
b62d64d540 | |||
497203e76a | |||
b7b5c5f843 | |||
d6b8a18e6f | |||
824292c9bd | |||
ca98bbb1f7 | |||
d5c2dfb96b | |||
aacceb6cff | |||
0348407acb | |||
6578729e1c | |||
93baaacf5f | |||
2bb2b143f2 | |||
3c538f0843 | |||
b35c7d5ae9 | |||
fea4d6c2b1 | |||
f06fc809b2 | |||
50d411a349 | |||
901dcc45df | |||
8ab324a661 | |||
9fdbaf2909 | |||
25cd3bba24 | |||
190bb58367 | |||
6e30ba871c | |||
b800511fcb | |||
5d89684a6e | |||
4fdf1085e0 | |||
424d635292 | |||
d25d8c3c56 | |||
6831c3f6c9 | |||
639ce6ad14 | |||
831e86fda6 | |||
3d91ac2bed | |||
e1739b99d3 | |||
53605c983d | |||
8cd870ed26 | |||
0caa4fbc4c | |||
fa3fab4ca0 | |||
3852da6249 | |||
28b541d49a | |||
bd454d9d20 | |||
22bbdeb7b8 | |||
0965f3958d | |||
0b3e3b5cb0 | |||
c70907ced2 | |||
67751e87c7 | |||
caef02e57f | |||
539980f75f | |||
eea15dcc4e | |||
8e758d5739 | |||
c1bde8560e | |||
fb56c6af04 | |||
c843871ac1 | |||
e5f60ec030 | |||
2244b2117d | |||
18e3089eb0 | |||
98061239f9 | |||
96497cac20 | |||
5f13b841ad | |||
dae82c0662 | |||
8676a6c10f | |||
c86eca293c | |||
023be6d770 | |||
3ed1e7dfa4 | |||
f7821c3a70 | |||
7c91139775 | |||
920bc8d9fe | |||
882c970e27 | |||
63978703cd | |||
2a9dbe6a92 | |||
d39679adaf | |||
862500cec3 | |||
6a85b43542 | |||
9d4e4c6c2b | |||
4ec588c8e3 | |||
7a81bbb58d | |||
5a272c954f | |||
285e891bef | |||
71119b9279 | |||
70ebb43017 | |||
64919ab55a | |||
d39e0c06f7 | |||
8f1e76bb43 | |||
f72b794d20 | |||
ab65c0a7c8 | |||
c930d10cc4 | |||
543601ce57 | |||
d686ec3c9f | |||
46d64139bd | |||
ab0d4b30af | |||
b77a5acfb6 | |||
18544645a2 | |||
83d547dd8e | |||
04d11112d4 | |||
52d1185dac | |||
60ed47ab10 | |||
d182cbae65 | |||
beb3d76a4b | |||
4d82dee240 | |||
d42eadcf86 | |||
a4c1f00014 | |||
637e7b44bb | |||
36652b212e | |||
b63c26a45a | |||
e58871167e | |||
5056e07f37 | |||
289a4fa001 | |||
19028db209 | |||
af7860c06a | |||
be5a439b08 | |||
9fbe5a2b22 | |||
277b38a548 | |||
70c8169711 | |||
06c9bacab0 | |||
0924e43dc2 | |||
4eb8c397f9 | |||
f7f5a10b66 | |||
ac62e1c262 | |||
e75ae9c570 | |||
5e2f790ee6 | |||
05af708e84 | |||
d0e66b31f7 | |||
f65008ccc3 | |||
645549978d | |||
63cf1745f9 | |||
28c9f857ea | |||
b4f180f5de | |||
193ac22eb5 | |||
9435003553 | |||
ed89203510 | |||
ab752e51b6 | |||
b57a3d2561 | |||
5b492fd4b5 | |||
68896aca1a | |||
2e5cf991b0 | |||
8acaf1ed96 | |||
4d3a0c0b61 | |||
265a41bc2e | |||
90ea798568 | |||
534c4f7cfc | |||
6427edb050 | |||
d0f8272fa5 | |||
d21798d4a4 |
8
.git-blame-ignore-revs
Normal file
8
.git-blame-ignore-revs
Normal 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
|
16
.github/dependabot.yml
vendored
Normal file
16
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
# 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"
|
||||
|
||||
# Maintain dependencies for pip
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
71
.github/workflows/cmake.yml
vendored
71
.github/workflows/cmake.yml
vendored
@ -1,71 +0,0 @@
|
||||
name: CMake
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Debug
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os }}-${{ matrix.cc }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
cc: "clang"
|
||||
cxx: "clang++"
|
||||
- os: Ubuntu-20.04
|
||||
cc: "clang-10"
|
||||
cxx: "clang++-10"
|
||||
- os: Ubuntu-20.04
|
||||
cc: "gcc-10"
|
||||
cxx: "g++-10"
|
||||
- os: windows-latest
|
||||
cc: "cl"
|
||||
cxx: "cl"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
# Whether to checkout submodules: `true` to checkout submodules or `recursive` to
|
||||
# recursively checkout submodules.
|
||||
submodules: 'true'
|
||||
|
||||
- name: Set Windows ENV
|
||||
if: runner.os == 'Windows'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Create Build Environment
|
||||
# Some projects don't allow in-source building, so create a separate build directory
|
||||
# We'll use this as our working directory for all subsequent commands
|
||||
run: cmake -E make_directory ${{github.workspace}}/build
|
||||
|
||||
- name: Configure CMake
|
||||
# Use a bash shell so we can use the same syntax for environment variable
|
||||
# access regardless of the host operating system
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build
|
||||
# Note the current convention is to use the -S and -B options here to specify source
|
||||
# and build directories, but this is only available with CMake 3.13 and higher.
|
||||
# The CMake binaries on the Github Actions machines are (as of this writing) 3.12
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }}
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{github.workspace}}/build
|
||||
shell: bash
|
||||
|
||||
# Execute the build. You can specify a specific target with "--target <NAME>"
|
||||
run: cmake --build . --config $BUILD_TYPE --verbose
|
||||
|
||||
# - name: Test
|
||||
# working-directory: ${{github.workspace}}/build
|
||||
# shell: bash
|
||||
# # Execute tests defined by the CMake configuration.
|
||||
# # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||
# run: ctest -C $BUILD_TYPE
|
142
.github/workflows/python-package.yml
vendored
Normal file
142
.github/workflows/python-package.yml
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: Python Package
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
build-sdist:
|
||||
name: Build SDist
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4.3.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install flake8
|
||||
python -m pip install setuptools twine build
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
|
||||
- name: Build SDist
|
||||
run: python -m build --sdist
|
||||
|
||||
- name: Check metadata
|
||||
run: python -m twine check dist/*
|
||||
|
||||
- name: Upload SDist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: dist/*.tar.gz
|
||||
|
||||
build-wheels:
|
||||
name: Build Wheels on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
# macos-13 is an intel runner, macos-14 is apple silicon
|
||||
os: [ubuntu-latest, windows-latest, macos-13, macos-14]
|
||||
linux_arch: [ 'x86_64' ] #[suffix, mac, windows, linux] arch names
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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 /
|
||||
|
||||
- name: Install QEMU
|
||||
# install QEMU if building for linux
|
||||
uses: docker/setup-qemu-action@v2
|
||||
if: runner.os == 'linux'
|
||||
with:
|
||||
platforms: arm64
|
||||
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v2.18.1
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "12"
|
||||
CIBW_ARCHS_LINUX: 'x86_64 aarch64'
|
||||
CIBW_ARCHS_MACOS: 'native'
|
||||
CIBW_SKIP: 'cp37*'
|
||||
|
||||
- name: Upload Wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
publish:
|
||||
name: Publish to PyPI and Github
|
||||
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
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4.3.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install yaclog
|
||||
|
||||
- name: Get version name and body
|
||||
run: |
|
||||
echo "VERSION_TILE=Version $(yaclog show -n)" >> $GITHUB_ENV
|
||||
echo "$(yaclog show -mb)" >> RELEASE.md
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: artifact
|
||||
path: dist
|
||||
|
||||
- name: List artifacts
|
||||
run: ls -l dist
|
||||
|
||||
- name: Publish to Test PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
repository_url: https://test.pypi.org/legacy/
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
- name: Publish to Github
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: dist/*
|
||||
name: ${{ env.VERSION_TITLE }}
|
||||
body_path: RELEASE.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
17
.gitignore
vendored
17
.gitignore
vendored
@ -1,11 +1,26 @@
|
||||
# Python
|
||||
env/
|
||||
dist/
|
||||
build/
|
||||
*.egg-info
|
||||
*.pyc
|
||||
*.pyi
|
||||
*.whl
|
||||
*.tar.gz
|
||||
|
||||
#sphinx
|
||||
docs/_build/
|
||||
|
||||
#mypy
|
||||
out
|
||||
|
||||
# IDEs
|
||||
**/.idea
|
||||
|
||||
# cmake
|
||||
# binaries
|
||||
*.so
|
||||
|
||||
# cmake
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
|
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,4 +0,0 @@
|
||||
[submodule "extern/pybind11"]
|
||||
path = extern/pybind11
|
||||
url = https://github.com/pybind/pybind11.git
|
||||
branch = stable
|
28
.readthedocs.yaml
Normal file
28
.readthedocs.yaml
Normal 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
|
70
CHANGELOG.md
Normal file
70
CHANGELOG.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file
|
||||
|
||||
## 0.2.1 - 2024-06-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed broken transparency on palettized PNG files
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed which wheels are built by the CI. There are no changes to OS or Python version compatibility if you compile from source.
|
||||
- Stopped building Python 3.7 wheels
|
||||
- Stopped building macOS universal wheels
|
||||
- Wheels for macOS now require macOS 12 or later
|
||||
- Included macOS ARM wheels
|
||||
- Included Python 3.12 wheels
|
||||
|
||||
|
||||
## 0.2.0 - 2023-06-21
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated Pybind11 to version 3.10, adding Python 3.11 support
|
||||
- Updated install instructions in readme to reflect availability on PyPI
|
||||
- Encode now skips .dds files in its input to prevent needless re-encoding
|
||||
|
||||
### Added
|
||||
|
||||
- Added the `-n` option for bc3 encoding to perform a BC3nm swizzle
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
- Fixed sdist not including pybind
|
||||
|
||||
|
||||
## 0.1.1 - 2021-09-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed alpha premultiplication when generating mipmaps
|
||||
|
||||
|
||||
## 0.1.0 - 2021-05-10
|
||||
|
||||
### Added
|
||||
|
||||
- Began publishing to PyPI
|
||||
|
||||
### Changed
|
||||
|
||||
- Rewrote CI workflow to include ManyLinux2014 builds
|
||||
- Reverted project to C++17 for better compiler compatibility
|
@ -1,44 +1,63 @@
|
||||
cmake_minimum_required(VERSION 3.17)
|
||||
include(CheckIPOSupported)
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
include(tools/CompilerWarnings.cmake)
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
|
||||
project(python_rgbcx)
|
||||
project(quicktex)
|
||||
|
||||
# Link to Pybind
|
||||
add_subdirectory(extern/pybind11)
|
||||
# Find dependencies
|
||||
find_package(Python COMPONENTS Interpreter Development.Module)
|
||||
find_package(pybind11 CONFIG REQUIRED)
|
||||
find_package(OpenMP)
|
||||
|
||||
# Collect source files
|
||||
file(GLOB SOURCE_FILES "src/*.cpp" "src/BC*/*.cpp")
|
||||
file(GLOB HEADER_FILES "src/*.h" "src/BC*/*.h")
|
||||
file(GLOB PYTHON_FILES "python/*.cpp" "python/*.h")
|
||||
file(GLOB TEST_FILES "src/test/*.c" "src/test/*.cpp" "src/test/*.h")
|
||||
file(GLOB SOURCE_FILES
|
||||
"quicktex/*.cpp"
|
||||
"quicktex/s3tc/*.cpp"
|
||||
"quicktex/s3tc/bc1/*.cpp"
|
||||
"quicktex/s3tc/bc3/*.cpp"
|
||||
"quicktex/s3tc/bc4/*.cpp"
|
||||
"quicktex/s3tc/bc5/*.cpp"
|
||||
"quicktex/s3tc/interpolator/*.cpp"
|
||||
)
|
||||
|
||||
file(GLOB HEADER_FILES
|
||||
"quicktex/*.h"
|
||||
"quicktex/s3tc/*.h"
|
||||
"quicktex/s3tc/bc1/*.h"
|
||||
"quicktex/s3tc/bc3/*.h"
|
||||
"quicktex/s3tc/bc4/*.h"
|
||||
"quicktex/s3tc/bc5/*.h"
|
||||
"quicktex/s3tc/interpolator/*.h"
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE PYTHON_FILES "src/**/*.py")
|
||||
|
||||
# Organize source files together for some IDEs
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_FILES} ${HEADER_FILES} ${PYTHON_FILES})
|
||||
|
||||
# Add python module
|
||||
pybind11_add_module(python_rgbcx
|
||||
pybind11_add_module(_quicktex
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
${PYTHON_FILES})
|
||||
${HEADER_FILES})
|
||||
|
||||
add_executable(test_rgbcx
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
${TEST_FILES})
|
||||
# Set Quicktex version info
|
||||
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)
|
||||
endif ()
|
||||
|
||||
# Set module features, like C/C++ standards
|
||||
target_compile_features(python_rgbcx PUBLIC cxx_std_20 c_std_11)
|
||||
target_compile_features(test_rgbcx PUBLIC cxx_std_20 c_std_11)
|
||||
target_compile_features(_quicktex PUBLIC cxx_std_17 c_std_11)
|
||||
|
||||
set_project_warnings(python_rgbcx)
|
||||
set_project_warnings(test_rgbcx)
|
||||
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
# Set compiler warnings
|
||||
set_project_warnings(_quicktex)
|
||||
|
||||
# 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++")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -stdlib=libc++ -fsanitize=undefined")
|
||||
set(PROJECT_WARNINGS ${CLANG_WARNINGS})
|
||||
endif ()
|
||||
|
25
DEVELOPMENT.md
Normal file
25
DEVELOPMENT.md
Normal file
@ -0,0 +1,25 @@
|
||||
# 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.
|
84
README.md
84
README.md
@ -1,3 +1,83 @@
|
||||
# python-rgbcx
|
||||
A python library for using the rgbcx texture compressor
|
||||
# Quicktex
|
||||
A python library for using DDS files
|
||||
|
||||
Quicktex is a python library and command line tool for encoding and decoding DDS files.
|
||||
It is based on the [RGBCX encoder](https://github.com/richgel999/bc7enc), which is currently
|
||||
one of the [highest quality S3TC encoders available](https://aras-p.info/blog/2020/12/08/Texture-Compression-in-2020/).
|
||||
Quicktex has a python front end, but the encoding and decoding is all done in C++ for speed
|
||||
comparable to the original library.
|
||||
|
||||
## Installation
|
||||
|
||||
### From Wheel (Easiest)
|
||||
|
||||
To install, run
|
||||
|
||||
```shell
|
||||
pip install quicktex
|
||||
```
|
||||
|
||||
If you are on macOS, You need to install openMP from homebrew:
|
||||
|
||||
```shell
|
||||
brew install libomp
|
||||
```
|
||||
|
||||
### From Source
|
||||
|
||||
To build from source, first clone this repo and cd into it, then run:
|
||||
|
||||
```shell
|
||||
git submodule update --init
|
||||
pip install .
|
||||
```
|
||||
|
||||
and setuptools will take care of any dependencies for you.
|
||||
|
||||
If you are on macOS, it is recommended to first install openMP from homebrew to enable
|
||||
multithreading, since it is not included in the default Apple Clang install:
|
||||
|
||||
```shell
|
||||
brew install libomp
|
||||
```
|
||||
|
||||
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 .[tests,stubs,docs]
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Usage: quicktex [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Encode and Decode various image formats
|
||||
|
||||
Options:
|
||||
--version Show the version and exit.
|
||||
--help Show this message and exit.
|
||||
|
||||
Commands:
|
||||
decode Decode DDS files to images.
|
||||
encode Encode images to DDS files of the given format.
|
||||
```
|
||||
|
||||
To decode DDS files to images, use the `decode` subdommand, along with a glob or a
|
||||
list of files to decode.
|
||||
|
||||
To encode images to DDS files, use the `encode` subcommand, plus an additional
|
||||
subcommand for the format. For example, `quicktex encode bc1 bun.png` will encode
|
||||
bun.png in the current directory to a bc1/DXT1 dds file next to it.
|
||||
|
||||
`encode` and `decode` both accept several common parameters:
|
||||
|
||||
- `-f, --flip / -F, --no-flip`: Vertically flip image before/after converting.
|
||||
[default: True]
|
||||
- `-r, --remove`: Remove input images after converting.
|
||||
- `-s, --suffix TEXT`: Suffix to append to output filename(s).
|
||||
Ignored if `output` is a single file.
|
||||
- `-o, --output`: Output file or directory. If outputting to a file, input filenames
|
||||
must be only a single item. By default, files are converted in place.
|
||||
|
||||
|
2
docs/changelog.md
Normal file
2
docs/changelog.md
Normal file
@ -0,0 +1,2 @@
|
||||
```{include} ../CHANGELOG.md
|
||||
```
|
78
docs/conf.py
Normal file
78
docs/conf.py
Normal file
@ -0,0 +1,78 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# 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'
|
||||
author = 'Andrew Cassidy'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# 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']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# -- Options for Autodoc -----------------------------------------------------
|
||||
|
||||
add_module_names = False
|
||||
autodoc_docstring_signature = True
|
||||
|
||||
autodoc_default_options = {
|
||||
'member-order': 'bysource',
|
||||
'exclude-members': '__weakref__',
|
||||
'imported-members': True,
|
||||
}
|
||||
|
||||
# -- Options for Intersphinx ------------------------------------------------
|
||||
|
||||
# This config value contains the locations and names of other projects that
|
||||
# should be linked to in this documentation.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/3', None),
|
||||
'PIL': ('https://pillow.readthedocs.io/en/stable/', None),
|
||||
}
|
2
docs/development.md
Normal file
2
docs/development.md
Normal file
@ -0,0 +1,2 @@
|
||||
```{include} ../DEVELOPMENT.md
|
||||
```
|
7
docs/handbook/commands.md
Normal file
7
docs/handbook/commands.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Command Reference
|
||||
|
||||
```{eval-rst}
|
||||
.. click:: quicktex.__main__:main
|
||||
:prog: quicktex
|
||||
:nested: full
|
||||
```
|
84
docs/handbook/getting_started.md
Normal file
84
docs/handbook/getting_started.md
Normal 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
10
docs/handbook/index.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Handbook
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 3
|
||||
---
|
||||
|
||||
commands
|
||||
getting_started
|
||||
```
|
35
docs/index.md
Normal file
35
docs/index.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Welcome to Quicktex's Documentation
|
||||
|
||||
[](https://quicktex.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://github.com/drewcassidy/quicktex/actions/workflows/python-package.yml)
|
||||
[](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`
|
2
docs/license.md
Normal file
2
docs/license.md
Normal file
@ -0,0 +1,2 @@
|
||||
```{include} ../LICENSE.md
|
||||
```
|
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
6
docs/reference/dds.rst
Normal file
6
docs/reference/dds.rst
Normal file
@ -0,0 +1,6 @@
|
||||
dds module
|
||||
==========
|
||||
|
||||
.. automodule:: quicktex.dds
|
||||
:members:
|
||||
|
7
docs/reference/formats/index.rst
Normal file
7
docs/reference/formats/index.rst
Normal file
@ -0,0 +1,7 @@
|
||||
Formats
|
||||
=======
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
s3tc.rst
|
97
docs/reference/formats/s3tc.rst
Normal file
97
docs/reference/formats/s3tc.rst
Normal file
@ -0,0 +1,97 @@
|
||||
s3tc module
|
||||
===========
|
||||
|
||||
.. automodule:: quicktex.s3tc
|
||||
|
||||
bc1 module
|
||||
----------
|
||||
.. automodule:: quicktex.s3tc.bc1
|
||||
|
||||
.. autoclass:: BC1Encoder
|
||||
|
||||
.. automethod:: __init__
|
||||
.. automethod:: set_level
|
||||
|
||||
.. autoproperty:: color_mode(self) -> ColorMode
|
||||
.. autoproperty:: interpolator(self) -> quicktex.s3tc.interpolator.Interpolator
|
||||
|
||||
.. autoclass:: quicktex.s3tc.bc1::BC1Encoder.ColorMode
|
||||
|
||||
**Advanced API**
|
||||
|
||||
Additional properties are provided for finer-grained control over quality and performance
|
||||
|
||||
.. autoproperty:: error_mode(self) -> ErrorMode
|
||||
.. autoproperty:: endpoint_mode(self) -> EndpointMode
|
||||
|
||||
.. autoproperty:: two_ls_passes(self) -> bool
|
||||
.. autoproperty:: two_ep_passes(self) -> bool
|
||||
.. autoproperty:: two_cf_passes(self) -> bool
|
||||
|
||||
.. autoproperty:: exhaustive(self) -> bool
|
||||
.. autoproperty:: search_rounds(self) -> int
|
||||
.. autoproperty:: orderings(self) -> tuple[int, int]
|
||||
.. autoproperty:: power_iterations(self) -> int
|
||||
.. autoattribute:: max_power_iterations
|
||||
.. autoattribute:: min_power_iterations
|
||||
|
||||
.. autoclass:: quicktex.s3tc.bc1::BC1Encoder.EndpointMode
|
||||
.. autoclass:: quicktex.s3tc.bc1::BC1Encoder.ErrorMode
|
||||
|
||||
.. autoclass:: BC1Decoder
|
||||
|
||||
.. automethod:: __init__
|
||||
.. autoproperty:: interpolator(self) -> quicktex.s3tc.interpolator.Interpolator
|
||||
.. autoproperty:: write_alpha(self) -> bool
|
||||
|
||||
bc3 module
|
||||
----------
|
||||
.. automodule:: quicktex.s3tc.bc3
|
||||
|
||||
.. autoclass:: BC3Encoder
|
||||
|
||||
.. automethod:: __init__
|
||||
.. autoproperty:: bc1_encoder(self) -> quicktex.s3tc.bc1.BC1Encoder
|
||||
.. autoproperty:: bc4_encoder(self) -> quicktex.s3tc.bc4.BC4Encoder
|
||||
|
||||
.. autoclass:: BC3Decoder
|
||||
|
||||
.. automethod:: __init__
|
||||
.. autoproperty:: bc1_decoder(self) -> quicktex.s3tc.bc1.BC1Decoder
|
||||
.. autoproperty:: bc4_decoder(self) -> quicktex.s3tc.bc4.BC4Decoder
|
||||
|
||||
bc4 module
|
||||
----------
|
||||
.. automodule:: quicktex.s3tc.bc4
|
||||
|
||||
.. autoclass:: BC4Encoder
|
||||
|
||||
.. automethod:: __init__
|
||||
.. autoproperty:: channel(self) -> int
|
||||
|
||||
.. autoclass:: BC4Decoder
|
||||
|
||||
.. automethod:: __init__
|
||||
.. autoproperty:: channel(self) -> int
|
||||
|
||||
bc5 module
|
||||
----------
|
||||
.. automodule:: quicktex.s3tc.bc5
|
||||
|
||||
.. autoclass:: BC5Encoder
|
||||
|
||||
.. automethod:: __init__
|
||||
.. autoproperty:: bc4_encoders(self) -> tuple[quicktex.s3tc.bc4.BC4Encoder]
|
||||
.. autoproperty:: channels(self) -> tuple[int, int]
|
||||
|
||||
.. autoclass:: BC5Decoder
|
||||
|
||||
.. automethod:: __init__
|
||||
.. autoproperty:: bc4_decoders(self) -> tuple[quicktex.s3tc.bc4.BC4Decoder]
|
||||
.. autoproperty:: channels(self) -> tuple[int, int]
|
||||
|
||||
interpolator module
|
||||
-------------------
|
||||
|
||||
.. automodule:: quicktex.s3tc.interpolator
|
||||
:members:
|
5
docs/reference/image_utils.rst
Normal file
5
docs/reference/image_utils.rst
Normal file
@ -0,0 +1,5 @@
|
||||
image_utils module
|
||||
==================
|
||||
|
||||
.. automodule:: quicktex.image_utils
|
||||
:members:
|
9
docs/reference/index.rst
Normal file
9
docs/reference/index.rst
Normal file
@ -0,0 +1,9 @@
|
||||
API Reference
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
dds.rst
|
||||
image_utils.rst
|
||||
formats/index.rst
|
1
extern/pybind11
vendored
1
extern/pybind11
vendored
Submodule extern/pybind11 deleted from 8de7772cc7
85
pyproject.toml
Normal file
85
pyproject.toml
Normal file
@ -0,0 +1,85 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=61",
|
||||
"setuptools_scm>=6.2",
|
||||
"wheel",
|
||||
"cmake>=3.18",
|
||||
"pybind11~=2.10",
|
||||
"ninja; sys_platform != 'win32'",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "quicktex"
|
||||
description = "A fast block compression library for python"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "Andrew Cassidy", email = "drewcassidy@me.com" }]
|
||||
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: C++",
|
||||
]
|
||||
|
||||
requires-python = ">=3.7"
|
||||
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",
|
||||
]
|
||||
stubs = ["pybind11-stubgen"]
|
||||
|
||||
[project.urls]
|
||||
Docs = "https://quicktex.readthedocs.io/en/latest/"
|
||||
Source = "https://github.com/drewcassidy/quicktex"
|
||||
Changelog = "https://github.com/drewcassidy/quicktex/blob/main/CHANGELOG.md"
|
||||
|
||||
[project.scripts]
|
||||
quicktex = "quicktex.__main__:main"
|
||||
|
||||
[tool.setuptools]
|
||||
zip-safe = false
|
||||
packages = { find = { include = ["quicktex*"] } } # only include quicktex and not tests
|
||||
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
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -18,16 +18,26 @@
|
||||
*/
|
||||
#include "Color.h"
|
||||
|
||||
#include <algorithm> // for max, Min
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Vector4.h"
|
||||
#include "Vector4Int.h"
|
||||
#include "util.h" // for scale5To8, scale8To5, assert5bit, scale6To8
|
||||
|
||||
namespace rgbcx {
|
||||
namespace quicktex {
|
||||
|
||||
Color::Color() { SetRGBA(0, 0, 0, 0xFF); }
|
||||
Color::Color(Vector4Int v) {
|
||||
if (v.MaxAbs() > 0xFF) throw std::invalid_argument("Vector members out of range");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (v[i] < 0) throw std::range_error("Color members cannot be negative");
|
||||
}
|
||||
|
||||
Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { SetRGBA(r, g, b, a); }
|
||||
r = static_cast<uint8_t>(v[0]);
|
||||
g = static_cast<uint8_t>(v[1]);
|
||||
b = static_cast<uint8_t>(v[2]);
|
||||
a = static_cast<uint8_t>(v[3]);
|
||||
}
|
||||
|
||||
uint16_t Color::Pack565Unscaled(uint8_t r, uint8_t g, uint8_t b) {
|
||||
assert5bit(r);
|
||||
@ -76,13 +86,6 @@ Color Color::PreciseRound565(Vector4 &v) {
|
||||
return Color(r, g, b);
|
||||
}
|
||||
|
||||
void Color::SetRGBA(uint8_t vr, uint8_t vg, uint8_t vb, uint8_t va = 0xFF) {
|
||||
r = vr;
|
||||
g = vg;
|
||||
b = vb;
|
||||
a = va;
|
||||
}
|
||||
|
||||
void Color::SetRGB(uint8_t vr, uint8_t vg, uint8_t vb) {
|
||||
r = vr;
|
||||
g = vg;
|
||||
@ -90,14 +93,14 @@ void Color::SetRGB(uint8_t vr, uint8_t vg, uint8_t vb) {
|
||||
}
|
||||
|
||||
size_t Color::MinChannelRGB() {
|
||||
if (r < g && r < b) return 0;
|
||||
if (g < b && g < r) return 1;
|
||||
if (r <= g && r <= b) return 0;
|
||||
if (g <= b && g <= r) return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
size_t Color::MaxChannelRGB() {
|
||||
if (r > g && r > b) return 0;
|
||||
if (g > b && g > r) return 1;
|
||||
if (r >= g && r >= b) return 0;
|
||||
if (g >= b && g >= r) return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
@ -105,10 +108,21 @@ Color Color::Min(const Color &A, const Color &B) { return Color(std::min(A[0], B
|
||||
|
||||
Color Color::Max(const Color &a, const Color &b) { return Color(std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); }
|
||||
|
||||
uint16_t Color::pack565() { return Pack565(r, g, b); }
|
||||
uint16_t Color::pack565Unscaled() { return Pack565Unscaled(r, g, b); }
|
||||
Color::operator Vector4() const { return Vector4(r, g, b, a); }
|
||||
Color::operator Vector4Int() const { return Vector4Int(r, g, b, a); }
|
||||
Vector4Int operator-(const Color &lhs, const Color &rhs) {
|
||||
Vector4Int result;
|
||||
for (unsigned i = 0; i < 4; i++) { result[i] = (int)lhs[i] - rhs[i]; }
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t Color::Pack565() const { return Pack565(r, g, b); }
|
||||
uint16_t Color::Pack565Unscaled() const { return Pack565Unscaled(r, g, b); }
|
||||
|
||||
Color Color::ScaleTo565() const { return Color(scale8To5(r), scale8To6(g), scale8To5(b)); }
|
||||
Color Color::ScaleFrom565() const { return Color(scale5To8(r), scale6To8(g), scale5To8(b)); }
|
||||
|
||||
} // namespace rgbcx
|
||||
bool Color::operator==(const Color &Rhs) const { return r == Rhs.r && g == Rhs.g && b == Rhs.b && a == Rhs.a; }
|
||||
bool Color::operator!=(const Color &Rhs) const { return !(Rhs == *this); }
|
||||
|
||||
} // namespace quicktex
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -18,16 +18,15 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <assert.h> // for assert
|
||||
#include <stddef.h> // for size_t
|
||||
|
||||
#include <cassert> // for assert
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint8_t, uint16_t
|
||||
|
||||
namespace rgbcx {
|
||||
namespace quicktex {
|
||||
class Vector4;
|
||||
class Vector4Int;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
class Color {
|
||||
public:
|
||||
uint8_t r;
|
||||
@ -35,9 +34,11 @@ class Color {
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
|
||||
Color();
|
||||
constexpr Color() : Color(0, 0, 0, 0xFF) {}
|
||||
|
||||
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0xFF);
|
||||
constexpr Color(uint8_t vr, uint8_t vg, uint8_t vb, uint8_t va = 0xFF) : r(vr), g(vg), b(vb), a(va) {}
|
||||
|
||||
Color(Vector4Int v);
|
||||
|
||||
static uint16_t Pack565Unscaled(uint8_t r, uint8_t g, uint8_t b);
|
||||
static uint16_t Pack565(uint8_t r, uint8_t g, uint8_t b);
|
||||
@ -50,7 +51,8 @@ class Color {
|
||||
static Color Min(const Color &A, const Color &B);
|
||||
static Color Max(const Color &A, const Color &B);
|
||||
|
||||
bool operator==(const Color &Rhs) const { return r == Rhs.r && g == Rhs.g && b == Rhs.b && a == Rhs.a; }
|
||||
bool operator==(const Color &Rhs) const;
|
||||
bool operator!=(const Color &Rhs) const;
|
||||
|
||||
uint8_t operator[](size_t index) const {
|
||||
assert(index < 4);
|
||||
@ -61,14 +63,15 @@ class Color {
|
||||
return reinterpret_cast<uint8_t *>(this)[index];
|
||||
}
|
||||
|
||||
void SetRGBA(uint8_t vr, uint8_t vg, uint8_t vb, uint8_t va);
|
||||
void SetRGBA(const Color &other) { SetRGBA(other.r, other.g, other.b, other.a); }
|
||||
operator Vector4() const;
|
||||
operator Vector4Int() const;
|
||||
friend Vector4Int operator-(const Color &lhs, const Color &rhs);
|
||||
|
||||
void SetRGB(uint8_t vr, uint8_t vg, uint8_t vb);
|
||||
void SetRGB(const Color &other) { SetRGB(other.r, other.g, other.b); }
|
||||
|
||||
uint16_t pack565();
|
||||
uint16_t pack565Unscaled();
|
||||
uint16_t Pack565() const;
|
||||
uint16_t Pack565Unscaled() const;
|
||||
|
||||
Color ScaleTo565() const;
|
||||
Color ScaleFrom565() const;
|
||||
@ -77,6 +80,7 @@ class Color {
|
||||
size_t MaxChannelRGB();
|
||||
|
||||
bool IsGrayscale() const { return ((r == g) && (r == b)); }
|
||||
bool IsBlack() const { return (r | g | b) < 4; }
|
||||
|
||||
int GetLuma() const { return (13938U * r + 46869U * g + 4729U * b + 32768U) >> 16U; } // REC709 weightings
|
||||
|
||||
@ -92,4 +96,4 @@ class Color {
|
||||
.882353f, .898039f, .913725f, .929412f, .945098f, .960784f, .976471f, .992157f, 1e+37f};
|
||||
};
|
||||
#pragma pack(pop)
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex
|
124
quicktex/ColorBlock.h
Normal file
124
quicktex/ColorBlock.h
Normal file
@ -0,0 +1,124 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Color.h"
|
||||
#include "Vector4Int.h"
|
||||
|
||||
namespace quicktex {
|
||||
using Coords = std::tuple<int, int>;
|
||||
|
||||
template <int N, int M> class ColorBlock {
|
||||
public:
|
||||
struct Metrics {
|
||||
Color min;
|
||||
Color max;
|
||||
Color avg;
|
||||
bool is_greyscale;
|
||||
bool has_black;
|
||||
Vector4Int sums;
|
||||
};
|
||||
|
||||
static constexpr int Width = N;
|
||||
static constexpr int Height = M;
|
||||
|
||||
constexpr Color Get(int x, int y) const {
|
||||
if (x >= Width || x < 0) throw std::invalid_argument("x value out of range");
|
||||
if (y >= Height || y < 0) throw std::invalid_argument("y value out of range");
|
||||
|
||||
return _pixels[x + (N * y)];
|
||||
}
|
||||
|
||||
constexpr Color Get(int i) const {
|
||||
if (i >= N * M || i < 0) throw std::invalid_argument("i value out of range");
|
||||
return _pixels[i];
|
||||
}
|
||||
|
||||
void Set(int x, int y, const Color &value) {
|
||||
if (x >= Width || x < 0) throw std::invalid_argument("x value out of range");
|
||||
if (y >= Height || y < 0) throw std::invalid_argument("y value out of range");
|
||||
_pixels[x + (N * y)] = value;
|
||||
}
|
||||
|
||||
void Set(int i, const Color &value) {
|
||||
if (i >= N * M || i < 0) throw std::invalid_argument("i value out of range");
|
||||
_pixels[i] = value;
|
||||
}
|
||||
|
||||
void GetRow(int y, Color *dst) const {
|
||||
if (y >= Height || y < 0) throw std::invalid_argument("y value out of range");
|
||||
std::memcpy(dst, &_pixels[N * y], N * sizeof(Color));
|
||||
}
|
||||
|
||||
void SetRow(int y, const Color *src) {
|
||||
if (y >= Height || y < 0) throw std::invalid_argument("y value out of range");
|
||||
std::memcpy(&_pixels[N * y], src, N * sizeof(Color));
|
||||
}
|
||||
|
||||
bool IsSingleColor() const {
|
||||
auto first = Get(0, 0);
|
||||
for (unsigned j = 1; j < M * N; j++) {
|
||||
if (Get(j) != first) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Metrics GetMetrics(bool ignore_black = false) const {
|
||||
Metrics metrics;
|
||||
metrics.min = Color(UINT8_MAX, UINT8_MAX, UINT8_MAX);
|
||||
metrics.max = Color(0, 0, 0);
|
||||
metrics.has_black = false;
|
||||
metrics.is_greyscale = true;
|
||||
metrics.sums = {0, 0, 0};
|
||||
|
||||
unsigned total = 0;
|
||||
|
||||
for (unsigned i = 0; i < M * N; i++) {
|
||||
Color val = Get(i);
|
||||
bool is_black = val.IsBlack();
|
||||
|
||||
metrics.has_black |= is_black;
|
||||
|
||||
if (ignore_black && is_black) { continue; }
|
||||
|
||||
metrics.is_greyscale &= val.IsGrayscale();
|
||||
for (unsigned c = 0; c < 3; c++) {
|
||||
metrics.min[c] = std::min(metrics.min[c], val[c]);
|
||||
metrics.max[c] = std::max(metrics.max[c], val[c]);
|
||||
metrics.sums[c] += val[c];
|
||||
}
|
||||
total++;
|
||||
}
|
||||
|
||||
if (total > 0) metrics.avg = (metrics.sums + Vector4Int(total / 2)) / (int)total; // half-total added for better rounding
|
||||
return metrics;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<Color, N * M> _pixels;
|
||||
};
|
||||
|
||||
} // namespace quicktex
|
68
quicktex/Decoder.h
Normal file
68
quicktex/Decoder.h
Normal file
@ -0,0 +1,68 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich 2020 <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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ColorBlock.h"
|
||||
#include "Texture.h"
|
||||
|
||||
namespace quicktex {
|
||||
|
||||
template <class T> class Decoder {
|
||||
public:
|
||||
using Texture = T;
|
||||
|
||||
virtual ~Decoder() = default;
|
||||
virtual RawTexture Decode(const T &encoded) const = 0;
|
||||
};
|
||||
|
||||
template <class T> class BlockDecoder : public Decoder<T> {
|
||||
public:
|
||||
inline static constexpr int BlockWidth = T::BlockType::Width;
|
||||
inline static constexpr int BlockHeight = T::BlockType::Height;
|
||||
|
||||
using Texture = T;
|
||||
using EncodedBlock = typename T::BlockType;
|
||||
using DecodedBlock = ColorBlock<BlockWidth, BlockHeight>;
|
||||
|
||||
virtual DecodedBlock DecodeBlock(const EncodedBlock &block) const = 0;
|
||||
|
||||
virtual RawTexture Decode(const T &encoded) const override {
|
||||
auto decoded = RawTexture(encoded.Width(), encoded.Height());
|
||||
|
||||
int blocks_x = encoded.BlocksX();
|
||||
int blocks_y = encoded.BlocksY();
|
||||
|
||||
// from experimentation, multithreading this using OpenMP actually makes decoding slower
|
||||
// due to thread creation/teardown taking longer than the decoding process itself.
|
||||
// As a result, this is left as a serial operation despite being embarassingly parallelizable
|
||||
for (int y = 0; y < blocks_y; y++) {
|
||||
for (int x = 0; x < blocks_x; x++) {
|
||||
auto block = encoded.GetBlock(x, y);
|
||||
auto pixels = DecodeBlock(block);
|
||||
decoded.SetBlock<BlockWidth, BlockHeight>(x, y, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
};
|
||||
} // namespace quicktex
|
72
quicktex/Encoder.h
Normal file
72
quicktex/Encoder.h
Normal file
@ -0,0 +1,72 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich 2020 <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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ColorBlock.h"
|
||||
#include "Texture.h"
|
||||
|
||||
namespace quicktex {
|
||||
|
||||
template <typename T> class Encoder {
|
||||
public:
|
||||
using Texture = T;
|
||||
|
||||
virtual ~Encoder() = default;
|
||||
virtual T Encode(const RawTexture &decoded) const = 0;
|
||||
};
|
||||
|
||||
template <typename T> class BlockEncoder : public Encoder<T> {
|
||||
public:
|
||||
inline static constexpr int BlockWidth = T::BlockType::Width;
|
||||
inline static constexpr int BlockHeight = T::BlockType::Height;
|
||||
|
||||
using Texture = T;
|
||||
using EncodedBlock = typename T::BlockType;
|
||||
using DecodedBlock = ColorBlock<BlockWidth, BlockHeight>;
|
||||
|
||||
virtual EncodedBlock EncodeBlock(const DecodedBlock &block) const = 0;
|
||||
|
||||
virtual T Encode(const RawTexture &decoded) const override {
|
||||
auto encoded = T(decoded.Width(), decoded.Height());
|
||||
|
||||
int blocks_x = encoded.BlocksX();
|
||||
int blocks_y = encoded.BlocksY();
|
||||
|
||||
// from experimentation, multithreading this using OpenMP sometimes actually makes encoding slower
|
||||
// due to thread creation/teardown taking longer than the encoding process itself.
|
||||
// As a result, this is sometimes left as a serial operation despite being embarassingly parallelizable
|
||||
// threshold for number of blocks before multithreading is set by overriding MTThreshold()
|
||||
#pragma omp parallel for if (blocks_x * blocks_y >= MTThreshold())
|
||||
for (int y = 0; y < blocks_y; y++) {
|
||||
for (int x = 0; x < blocks_x; x++) {
|
||||
auto pixels = decoded.GetBlock<BlockWidth, BlockHeight>(x, y);
|
||||
auto block = EncodeBlock(pixels);
|
||||
encoded.SetBlock(x, y, block);
|
||||
}
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
virtual size_t MTThreshold() const { return SIZE_MAX; };
|
||||
};
|
||||
} // namespace quicktex
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -19,9 +19,9 @@
|
||||
|
||||
#include "Matrix4x4.h"
|
||||
|
||||
namespace rgbcx {
|
||||
namespace quicktex {
|
||||
|
||||
Matrix4x4 operator*(Matrix4x4& lhs, Matrix4x4& rhs) {
|
||||
Matrix4x4 operator*(const Matrix4x4& lhs, const Matrix4x4& rhs) {
|
||||
Matrix4x4 trans_rhs = rhs.Transpose(); // 🏳️⚧️
|
||||
Matrix4x4 result;
|
||||
for (unsigned r = 0; r < 4; r++) {
|
||||
@ -31,7 +31,7 @@ Matrix4x4 operator*(Matrix4x4& lhs, Matrix4x4& rhs) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector4 operator*(Matrix4x4& lhs, Vector4& rhs) {
|
||||
Vector4 operator*(const Matrix4x4& lhs, const Vector4& rhs) {
|
||||
Vector4 result;
|
||||
|
||||
for (unsigned r = 0; r < 4; r++) { result[r] = rhs.Dot(lhs[r]); }
|
||||
@ -47,4 +47,4 @@ void Matrix4x4::Mirror() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -20,11 +20,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
|
||||
#include "Vector4.h"
|
||||
|
||||
namespace rgbcx {
|
||||
namespace quicktex {
|
||||
|
||||
class Matrix4x4 {
|
||||
public:
|
||||
@ -34,7 +36,7 @@ class Matrix4x4 {
|
||||
return result;
|
||||
}
|
||||
|
||||
static Matrix4x4 Transpose(Matrix4x4 &val) {
|
||||
static Matrix4x4 Transpose(const Matrix4x4 &val) {
|
||||
Matrix4x4 result;
|
||||
for (unsigned r = 0; r < 3; r++) {
|
||||
for (unsigned c = 0; c < 3; c++) { result[r][c] = val[c][r]; }
|
||||
@ -71,7 +73,7 @@ class Matrix4x4 {
|
||||
friend Matrix4x4 &operator*=(Matrix4x4 &lhs, const float &rhs) { return lhs = lhs * rhs; }
|
||||
friend Matrix4x4 &operator/=(Matrix4x4 &lhs, const float &rhs) { return lhs = lhs / rhs; }
|
||||
|
||||
Matrix4x4 Transpose() { return Transpose(*this); }
|
||||
Matrix4x4 Transpose() const { return Transpose(*this); }
|
||||
|
||||
void Mirror();
|
||||
|
||||
@ -90,4 +92,4 @@ class Matrix4x4 {
|
||||
|
||||
std::array<Vector4, 4> _r;
|
||||
};
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex
|
187
quicktex/Texture.h
Normal file
187
quicktex/Texture.h
Normal file
@ -0,0 +1,187 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "Color.h"
|
||||
#include "ColorBlock.h"
|
||||
|
||||
namespace quicktex {
|
||||
|
||||
class Texture {
|
||||
public:
|
||||
virtual ~Texture() = default;
|
||||
|
||||
virtual int Width() const { return _width; }
|
||||
virtual int Height() const { return _height; }
|
||||
virtual std::tuple<int, int> Size() const { return std::tuple<int, int>(_width, _height); }
|
||||
|
||||
/**
|
||||
* The texture's total size
|
||||
* @return The size of the texture in bytes.
|
||||
*/
|
||||
virtual size_t NBytes() const noexcept = 0;
|
||||
|
||||
virtual const uint8_t *Data() const noexcept = 0;
|
||||
virtual uint8_t *Data() noexcept = 0;
|
||||
|
||||
protected:
|
||||
Texture(int width, int height) : _width(width), _height(height) {
|
||||
if (width <= 0) throw std::invalid_argument("Texture width must be greater than 0");
|
||||
if (height <= 0) throw std::invalid_argument("Texture height must be greater than 0");
|
||||
}
|
||||
|
||||
int _width;
|
||||
int _height;
|
||||
};
|
||||
|
||||
class RawTexture : public Texture {
|
||||
using Base = Texture;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create a new RawTexture
|
||||
* @param width width of the texture in pixels
|
||||
* @param height height of the texture in pixels
|
||||
*/
|
||||
RawTexture(int width, int height) : Base(width, height), _pixels(_width * _height) {}
|
||||
|
||||
Color GetPixel(int x, int y) const {
|
||||
if (x < 0 || x >= _width) throw std::invalid_argument("x value out of range.");
|
||||
if (y < 0 || y >= _height) throw std::invalid_argument("y value out of range.");
|
||||
return _pixels.at(x + (y * _width));
|
||||
}
|
||||
|
||||
void SetPixel(int x, int y, Color val) {
|
||||
if (x < 0 || x >= _width) throw std::invalid_argument("x value out of range.");
|
||||
if (y < 0 || y >= _height) throw std::invalid_argument("y value out of range.");
|
||||
_pixels.at(x + (y * _width)) = val;
|
||||
}
|
||||
|
||||
size_t NBytes() const noexcept override { return static_cast<unsigned long>(Width() * Height()) * sizeof(Color); }
|
||||
|
||||
template <int N, int M> ColorBlock<N, M> GetBlock(int block_x, int block_y) const {
|
||||
if (block_x < 0) throw std::out_of_range("x value out of range.");
|
||||
if (block_y < 0) throw std::out_of_range("y value out of range.");
|
||||
|
||||
// coordinates in the image of the top-left pixel of the selected block
|
||||
ColorBlock<N, M> block;
|
||||
int pixel_x = block_x * N;
|
||||
int pixel_y = block_y * M;
|
||||
|
||||
if (pixel_x + N < _width && pixel_y + M < _height) {
|
||||
// fast memcpy if the block is entirely inside the bounds of the texture
|
||||
for (int y = 0; y < M; y++) {
|
||||
// copy each row into the ColorBlock
|
||||
block.SetRow(y, &_pixels[pixel_x + (_width * (pixel_y + y))]);
|
||||
}
|
||||
} else {
|
||||
// slower pixel-wise copy if the block goes over the edges
|
||||
for (int x = 0; x < N; x++) {
|
||||
for (int y = 0; y < M; y++) { block.Set(x, y, GetPixel((pixel_x + x) % _width, (pixel_y + y) % _height)); }
|
||||
}
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
template <int N, int M> void SetBlock(int block_x, int block_y, const ColorBlock<N, M> &block) {
|
||||
if (block_x < 0) throw std::out_of_range("x value out of range.");
|
||||
if (block_y < 0) throw std::out_of_range("y value out of range.");
|
||||
|
||||
// coordinates in the image of the top-left pixel of the selected block
|
||||
int pixel_x = block_x * N;
|
||||
int pixel_y = block_y * M;
|
||||
|
||||
if (pixel_x + N < _width && pixel_y + M < _height) {
|
||||
// fast row-wise memcpy if the block is entirely inside the bounds of the texture
|
||||
for (int y = 0; y < M; y++) {
|
||||
// copy each row out of the ColorBlock
|
||||
block.GetRow(y, &_pixels[pixel_x + (_width * (pixel_y + y))]);
|
||||
}
|
||||
} else {
|
||||
// slower pixel-wise copy if the block goes over the edges
|
||||
for (int x = 0; x < N; x++) {
|
||||
for (int y = 0; y < M; y++) { SetPixel((pixel_x + x) % _width, (pixel_y + y) % _height, block.Get(x, y)); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual const uint8_t *Data() const noexcept override { return reinterpret_cast<const uint8_t *>(_pixels.data()); }
|
||||
virtual uint8_t *Data() noexcept override { return reinterpret_cast<uint8_t *>(_pixels.data()); }
|
||||
|
||||
protected:
|
||||
std::vector<Color> _pixels;
|
||||
};
|
||||
|
||||
template <typename B> class BlockTexture final : public Texture {
|
||||
private:
|
||||
std::vector<B> _blocks;
|
||||
int _width_b;
|
||||
int _height_b;
|
||||
|
||||
public:
|
||||
using BlockType = B;
|
||||
using Base = Texture;
|
||||
|
||||
/**
|
||||
* Create a new BlockTexture
|
||||
* @param width width of the texture in pixels. must be divisible by B::Width
|
||||
* @param height height of the texture in pixels. must be divisible by B::Height
|
||||
*/
|
||||
BlockTexture(int width, int height) : Base(width, height) {
|
||||
_width_b = (_width + B::Width - 1) / B::Width;
|
||||
_height_b = (_height + B::Height - 1) / B::Height;
|
||||
_blocks = std::vector<B>(_width_b * _height_b);
|
||||
}
|
||||
|
||||
constexpr int BlocksX() const { return _width_b; }
|
||||
constexpr int BlocksY() const { return _height_b; }
|
||||
constexpr std::tuple<int, int> BlocksXY() const { return std::tuple<int, int>(_width_b, _height_b); }
|
||||
|
||||
B GetBlock(int x, int y) const {
|
||||
if (x < 0 || x >= _width_b) throw std::out_of_range("x value out of range.");
|
||||
if (y < 0 || y >= _height_b) throw std::out_of_range("y value out of range.");
|
||||
return _blocks.at(x + (y * _width_b));
|
||||
}
|
||||
|
||||
void SetBlock(int x, int y, const B &val) {
|
||||
if (x < 0 || x >= _width_b) throw std::out_of_range("x value out of range.");
|
||||
if (y < 0 || y >= _height_b) throw std::out_of_range("y value out of range.");
|
||||
_blocks.at(x + (y * _width_b)) = val;
|
||||
}
|
||||
|
||||
size_t NBytes() const noexcept override { return _blocks.size() * sizeof(B); }
|
||||
|
||||
const uint8_t *Data() const noexcept override { return reinterpret_cast<const uint8_t *>(_blocks.data()); }
|
||||
uint8_t *Data() noexcept override { return reinterpret_cast<uint8_t *>(_blocks.data()); }
|
||||
};
|
||||
|
||||
} // namespace quicktex
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -20,15 +20,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
|
||||
#include "Color.h"
|
||||
|
||||
namespace rgbcx {
|
||||
namespace quicktex {
|
||||
|
||||
class Vector4 {
|
||||
public:
|
||||
Vector4(float x = 0, float y = 0, float z = 0, float w = 0) {
|
||||
Vector4() : Vector4(0) {}
|
||||
|
||||
Vector4(float x, float y, float z = 0, float w = 0) {
|
||||
_c[0] = x;
|
||||
_c[1] = y;
|
||||
_c[2] = z;
|
||||
@ -48,7 +51,7 @@ class Vector4 {
|
||||
|
||||
static Vector4 FromColorRGB(const Color &c) { return Vector4(c.r, c.g, c.b); }
|
||||
|
||||
static float Dot(Vector4 &lhs, Vector4 &rhs) {
|
||||
static float Dot(const Vector4 &lhs, const Vector4 &rhs) {
|
||||
float sum = 0;
|
||||
for (unsigned i = 0; i < 4; i++) { sum += lhs[i] * rhs[i]; }
|
||||
return sum;
|
||||
@ -83,8 +86,8 @@ class Vector4 {
|
||||
friend Vector4 &operator*=(Vector4 &lhs, const float &rhs) { return lhs = lhs * rhs; }
|
||||
friend Vector4 &operator/=(Vector4 &lhs, const float &rhs) { return lhs = lhs / rhs; }
|
||||
|
||||
float Dot(Vector4 other) { return Dot(*this, other); }
|
||||
float MaxAbs(unsigned channels = 4) {
|
||||
float Dot(Vector4 other) const { return Dot(*this, other); }
|
||||
float MaxAbs(unsigned channels = 4) const {
|
||||
assert(channels < 5);
|
||||
assert(channels > 0);
|
||||
float max = 0;
|
||||
@ -95,14 +98,21 @@ class Vector4 {
|
||||
return max;
|
||||
}
|
||||
|
||||
float SqrMag() { return Dot(*this, *this); }
|
||||
|
||||
float Determinant2x2() {
|
||||
//z00 * z11 - z01 * z10;
|
||||
return (_c[0] * _c[3]) - (_c[1] * _c[2]);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Op> friend Vector4 DoOp(const Vector4 &lhs, const Vector4 &rhs, Op f) {
|
||||
template <typename Op> static inline Vector4 DoOp(const Vector4 &lhs, const Vector4 &rhs, Op f) {
|
||||
Vector4 r;
|
||||
for (unsigned i = 0; i < 4; i++) { r[i] = f(lhs[i], rhs[i]); }
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename Op> friend Vector4 DoOp(const Vector4 &lhs, const float &rhs, Op f) {
|
||||
template <typename Op> static inline Vector4 DoOp(const Vector4 &lhs, const float &rhs, Op f) {
|
||||
Vector4 r;
|
||||
for (unsigned i = 0; i < 4; i++) { r[i] = f(lhs[i], rhs); }
|
||||
return r;
|
||||
@ -111,4 +121,4 @@ class Vector4 {
|
||||
std::array<float, 4> _c;
|
||||
};
|
||||
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex
|
120
quicktex/Vector4Int.h
Normal file
120
quicktex/Vector4Int.h
Normal file
@ -0,0 +1,120 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
#include "Color.h"
|
||||
#include "Vector4.h"
|
||||
|
||||
namespace quicktex {
|
||||
|
||||
class Vector4Int {
|
||||
public:
|
||||
Vector4Int() : Vector4Int(0) {}
|
||||
|
||||
Vector4Int(int x, int y, int z = 0, int w = 0) {
|
||||
_c[0] = x;
|
||||
_c[1] = y;
|
||||
_c[2] = z;
|
||||
_c[3] = w;
|
||||
}
|
||||
|
||||
Vector4Int(int scalar) {
|
||||
_c[0] = scalar;
|
||||
_c[1] = scalar;
|
||||
_c[2] = scalar;
|
||||
_c[3] = scalar;
|
||||
}
|
||||
|
||||
Vector4Int(const Color &c) : Vector4Int(c.r, c.g, c.b, c.a) {}
|
||||
|
||||
static Vector4Int FromColor(const Color &c) { return Vector4Int(c); }
|
||||
|
||||
static Vector4Int FromColorRGB(const Color &c) { return Vector4Int(c.r, c.g, c.b); }
|
||||
|
||||
static int Dot(const Vector4Int &lhs, const Vector4Int &rhs) {
|
||||
int sum = 0;
|
||||
for (unsigned i = 0; i < 4; i++) { sum += lhs[i] * rhs[i]; }
|
||||
return sum;
|
||||
}
|
||||
|
||||
int operator[](size_t index) const {
|
||||
assert(index < 4);
|
||||
return _c[index];
|
||||
}
|
||||
int &operator[](size_t index) {
|
||||
assert(index < 4);
|
||||
return _c[index];
|
||||
}
|
||||
|
||||
operator Vector4() const { return Vector4((float)_c[0], (float)_c[1], (float)_c[2], (float)_c[3]); }
|
||||
|
||||
friend Vector4Int operator+(const Vector4Int &lhs, const Vector4Int &rhs) { return DoOp(lhs, rhs, std::plus()); }
|
||||
friend Vector4Int operator-(const Vector4Int &lhs, const Vector4Int &rhs) { return DoOp(lhs, rhs, std::minus()); }
|
||||
friend Vector4Int operator*(const Vector4Int &lhs, const Vector4Int &rhs) { return DoOp(lhs, rhs, std::multiplies()); }
|
||||
friend Vector4Int operator/(const Vector4Int &lhs, const Vector4Int &rhs) { return DoOp(lhs, rhs, std::divides()); }
|
||||
|
||||
friend Vector4Int operator+(const Vector4Int &lhs, const int &rhs) { return DoOp(lhs, rhs, std::plus()); }
|
||||
friend Vector4Int operator-(const Vector4Int &lhs, const int &rhs) { return DoOp(lhs, rhs, std::minus()); }
|
||||
friend Vector4Int operator*(const Vector4Int &lhs, const int &rhs) { return DoOp(lhs, rhs, std::multiplies()); }
|
||||
friend Vector4Int operator/(const Vector4Int &lhs, const int &rhs) { return DoOp(lhs, rhs, std::divides()); }
|
||||
|
||||
friend Vector4Int &operator+=(Vector4Int &lhs, const Vector4Int &rhs) { return lhs = lhs + rhs; }
|
||||
friend Vector4Int &operator-=(Vector4Int &lhs, const Vector4Int &rhs) { return lhs = lhs - rhs; }
|
||||
friend Vector4Int &operator*=(Vector4Int &lhs, const Vector4Int &rhs) { return lhs = lhs * rhs; }
|
||||
friend Vector4Int &operator/=(Vector4Int &lhs, const Vector4Int &rhs) { return lhs = lhs / rhs; }
|
||||
|
||||
friend Vector4Int &operator+=(Vector4Int &lhs, const int &rhs) { return lhs = lhs + rhs; }
|
||||
friend Vector4Int &operator-=(Vector4Int &lhs, const int &rhs) { return lhs = lhs - rhs; }
|
||||
friend Vector4Int &operator*=(Vector4Int &lhs, const int &rhs) { return lhs = lhs * rhs; }
|
||||
friend Vector4Int &operator/=(Vector4Int &lhs, const int &rhs) { return lhs = lhs / rhs; }
|
||||
|
||||
int Dot(const Vector4Int &other) const { return Dot(*this, other); }
|
||||
int MaxAbs(unsigned channels = 4) {
|
||||
assert(channels < 5);
|
||||
assert(channels > 0);
|
||||
int max = 0;
|
||||
for (unsigned i = 0; i < channels; i++) {
|
||||
int a = abs((*this)[i]);
|
||||
if (a > max) max = a;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
unsigned int SqrMag() { return (unsigned)Dot(*this, *this); }
|
||||
|
||||
private:
|
||||
template <typename Op> friend Vector4Int DoOp(const Vector4Int &lhs, const Vector4Int &rhs, Op f) {
|
||||
Vector4Int r;
|
||||
for (unsigned i = 0; i < 4; i++) { r[i] = f(lhs[i], rhs[i]); }
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename Op> friend Vector4Int DoOp(const Vector4Int &lhs, const int &rhs, Op f) {
|
||||
Vector4Int r;
|
||||
for (unsigned i = 0; i < 4; i++) { r[i] = f(lhs[i], rhs); }
|
||||
return r;
|
||||
}
|
||||
|
||||
std::array<int, 4> _c;
|
||||
};
|
||||
|
||||
} // namespace quicktex
|
8
quicktex/__init__.py
Normal file
8
quicktex/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
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
|
17
quicktex/__main__.py
Normal file
17
quicktex/__main__.py
Normal file
@ -0,0 +1,17 @@
|
||||
import click
|
||||
|
||||
from quicktex.cli.decode import decode
|
||||
from quicktex.cli.encode import encode
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option()
|
||||
def main():
|
||||
"""Encode and Decode various image formats"""
|
||||
|
||||
|
||||
main.add_command(encode)
|
||||
main.add_command(decode)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
74
quicktex/_bindings.cpp
Normal file
74
quicktex/_bindings.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include "_bindings.h"
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include "Color.h"
|
||||
#include "Decoder.h"
|
||||
#include "Encoder.h"
|
||||
#include "Texture.h"
|
||||
#include "_bindings.h"
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
#define MACRO_STRINGIFY(x) STRINGIFY(x)
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace quicktex::bindings {
|
||||
|
||||
void InitS3TC(py::module_ &m);
|
||||
|
||||
PYBIND11_MODULE(_quicktex, m) {
|
||||
m.doc() = "More Stuff";
|
||||
|
||||
#ifdef VERSION_INFO
|
||||
m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
|
||||
#else
|
||||
m.attr("__version__") = "dev";
|
||||
#endif
|
||||
|
||||
py::options options;
|
||||
|
||||
// Texture
|
||||
|
||||
py::class_<Texture> texture(m, "Texture", py::buffer_protocol());
|
||||
|
||||
texture.def_property_readonly("nbytes", &Texture::NBytes);
|
||||
texture.def_property_readonly("size", &Texture::Size);
|
||||
texture.def_property_readonly("width", &Texture::Width);
|
||||
texture.def_property_readonly("height", &Texture::Height);
|
||||
|
||||
texture.def_buffer([](Texture &t) { return py::buffer_info(t.Data(), t.NBytes()); });
|
||||
texture.def("tobytes", [](const Texture &t) { return py::bytes(reinterpret_cast<const char *>(t.Data()), t.NBytes()); });
|
||||
|
||||
// RawTexture
|
||||
|
||||
py::class_<RawTexture, Texture> raw_texture(m, "RawTexture");
|
||||
|
||||
raw_texture.def(py::init<int, int>(), "width"_a, "height"_a);
|
||||
raw_texture.def_static("frombytes", &BufferToTexture<RawTexture>, "data"_a, "width"_a, "height"_a);
|
||||
|
||||
DefSubscript2D(raw_texture, &RawTexture::GetPixel, &RawTexture::SetPixel, &RawTexture::Size);
|
||||
|
||||
InitS3TC(m);
|
||||
}
|
||||
|
||||
} // namespace quicktex::bindings
|
236
quicktex/_bindings.h
Normal file
236
quicktex/_bindings.h
Normal file
@ -0,0 +1,236 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/operators.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Color.h"
|
||||
#include "ColorBlock.h"
|
||||
#include "Texture.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace pybind11::detail {
|
||||
using namespace quicktex;
|
||||
/// Type caster for color class to allow it to be converted to and from a python tuple
|
||||
template <> struct type_caster<Color> {
|
||||
public:
|
||||
PYBIND11_TYPE_CASTER(Color, _("Color"));
|
||||
|
||||
bool load(handle src, bool) {
|
||||
PyObject* source = src.ptr();
|
||||
|
||||
PyObject* tmp = PySequence_Tuple(source);
|
||||
|
||||
// if the object is not a tuple, return false
|
||||
if (!tmp) { return false; } // incorrect type
|
||||
|
||||
// check the size
|
||||
Py_ssize_t size = PyTuple_Size(tmp);
|
||||
if (size < 3 || size > 4) { return false; } // incorrect size
|
||||
|
||||
value.a = 0xFF;
|
||||
// now we get the contents
|
||||
for (int i = 0; i < size; i++) {
|
||||
PyObject* src_chan = PyTuple_GetItem(tmp, i);
|
||||
PyObject* tmp_chan = PyNumber_Long(src_chan);
|
||||
|
||||
if (!tmp_chan) return false; // incorrect channel type
|
||||
|
||||
auto chan = PyLong_AsLong(tmp_chan);
|
||||
if (chan > 0xFF || chan < 0) return false; // item out of range
|
||||
value[static_cast<unsigned>(i)] = static_cast<uint8_t>(chan);
|
||||
Py_DECREF(tmp_chan);
|
||||
}
|
||||
Py_DECREF(tmp);
|
||||
|
||||
return !PyErr_Occurred();
|
||||
}
|
||||
|
||||
static handle cast(Color src, return_value_policy, handle) {
|
||||
PyObject* val = PyTuple_New(4);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
PyObject* chan = PyLong_FromLong(src[static_cast<unsigned>(i)]);
|
||||
PyTuple_SetItem(val, i, chan);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
};
|
||||
} // namespace pybind11::detail
|
||||
|
||||
namespace py = pybind11;
|
||||
namespace quicktex::bindings {
|
||||
using namespace pybind11::literals;
|
||||
|
||||
template <typename T> T BufferToTexture(py::buffer buf, int width, int height) {
|
||||
static_assert(std::is_base_of<Texture, T>::value);
|
||||
static_assert(std::is_constructible<T, int, int>::value);
|
||||
|
||||
auto info = buf.request(false);
|
||||
auto output = T(width, 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.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.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.");
|
||||
}
|
||||
|
||||
std::memcpy(output.Data(), info.ptr, dst_size);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
template <typename T> T BufferToPOD(py::buffer buf) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
|
||||
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.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.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.");
|
||||
}
|
||||
|
||||
const T* ptr = reinterpret_cast<const T*>(info.ptr);
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
inline int PyIndex(int val, int size, std::string name = "index") {
|
||||
if (val < -size || val >= size) throw std::out_of_range(name + " value out of range");
|
||||
if (val < 0) return size + val;
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename T, typename Getter, typename Setter, typename Extent> void DefSubscript(py::class_<T> t, Getter&& get, Setter&& set, Extent&& ext) {
|
||||
using V = typename std::invoke_result<Getter, T*, int>::type;
|
||||
t.def(
|
||||
"__getitem__", [get, ext](T& self, int index) { return (self.*get)(PyIndex(index, (self.*ext)())); }, "key"_a);
|
||||
t.def(
|
||||
"__setitem__", [set, ext](T& self, int index, V val) { (self.*set)(PyIndex(index, (self.*ext)()), val); }, "key"_a, "value"_a);
|
||||
}
|
||||
|
||||
template <typename Tpy, typename Getter, typename Setter, typename Extent> void DefSubscript2D(Tpy t, Getter&& get, Setter&& set, Extent&& ext) {
|
||||
using T = typename Tpy::type;
|
||||
using V = typename std::invoke_result<Getter, T*, int, int>::type;
|
||||
using Coords = std::tuple<int, int>;
|
||||
t.def(
|
||||
"__getitem__",
|
||||
[get, ext](T& self, Coords pnt) {
|
||||
Coords s = (self.*ext)();
|
||||
int x = PyIndex(std::get<0>(pnt), std::get<0>(s), "x");
|
||||
int y = PyIndex(std::get<1>(pnt), std::get<1>(s), "y");
|
||||
return (self.*get)(x, y);
|
||||
},
|
||||
"key"_a);
|
||||
t.def(
|
||||
"__setitem__",
|
||||
[set, ext](T& self, Coords pnt, const V& val) {
|
||||
Coords s = (self.*ext)();
|
||||
int x = PyIndex(std::get<0>(pnt), std::get<0>(s), "x");
|
||||
int y = PyIndex(std::get<1>(pnt), std::get<1>(s), "y");
|
||||
(self.*set)(x, y, val);
|
||||
},
|
||||
"key"_a, "value"_a);
|
||||
}
|
||||
|
||||
template <typename B> py::class_<B> BindBlock(py::module_& m, const char* name) {
|
||||
const char* frombytes_doc = R"doc(
|
||||
Create a new {0} by copying a bytes-like object.
|
||||
|
||||
:param b: A bytes-like object at least the size of the block.
|
||||
)doc";
|
||||
|
||||
const char* tobytes_doc = R"doc(
|
||||
Pack the {0} into a bytestring.
|
||||
|
||||
:returns: A bytes object of length {1}.
|
||||
)doc";
|
||||
|
||||
py::class_<B> block(m, name, py::buffer_protocol());
|
||||
block.def_static("frombytes", &BufferToPOD<B>, "data"_a, Format(frombytes_doc, name).c_str());
|
||||
|
||||
block.def_readonly_static("width", &B::Width, "The width of the block in pixels.");
|
||||
block.def_readonly_static("height", &B::Height, "The height of the block in pixels.");
|
||||
block.def_property_readonly_static(
|
||||
"size", [](py::object) { return std::make_tuple(B::Width, B::Height); }, "The dimensions of the block in pixels.");
|
||||
block.def_property_readonly_static(
|
||||
"nbytes", [](py::object) { return sizeof(B); }, "The size of the block in bytes.");
|
||||
|
||||
block.def(py::self == py::self);
|
||||
|
||||
block.def_buffer([](B& b) { return py::buffer_info(reinterpret_cast<uint8_t*>(&b), sizeof(B)); });
|
||||
block.def(
|
||||
"tobytes", [](const B& b) { return py::bytes(reinterpret_cast<const char*>(&b), sizeof(B)); },
|
||||
Format(tobytes_doc, name, std::to_string(sizeof(B))).c_str());
|
||||
|
||||
return std::move(block);
|
||||
}
|
||||
|
||||
template <typename B> py::class_<BlockTexture<B>> BindBlockTexture(py::module_& m, const char* name) {
|
||||
const auto* const constructor_str = R"doc(
|
||||
Create a new blank {0} with the given dimensions.
|
||||
If the dimenions are not multiples of the block dimensions, enough blocks will be allocated
|
||||
to cover the entire texture, and it will be implicitly cropped during decoding.
|
||||
|
||||
:param int width: The width of the texture in pixels. Must be > 0.
|
||||
:param int height: The height of the texture in pixels. must be > 0
|
||||
)doc";
|
||||
|
||||
const auto* const from_bytes_str = R"doc(
|
||||
Create a new {0} with the given dimensions, and copy a bytes-like object into it.
|
||||
If the dimenions are not multiples of the block dimensions, enough blocks will be allocated
|
||||
to cover the entire texture, and it will be implicitly cropped during decoding.
|
||||
|
||||
:param b: A bytes-like object at least the size of the resulting texture.
|
||||
:param int width: The width of the texture in pixels. Must be > 0.
|
||||
:param int height: The height of the texture in pixels. must be > 0
|
||||
)doc";
|
||||
|
||||
using BTex = BlockTexture<B>;
|
||||
|
||||
py::class_<BTex, Texture> block_texture(m, name);
|
||||
|
||||
block_texture.def(py::init<int, int>(), "width"_a, "height"_a, Format(constructor_str, name).c_str());
|
||||
block_texture.def_static("from_bytes", &BufferToTexture<BTex>, "data"_a, "width"_a, "height"_a, Format(from_bytes_str, name).c_str());
|
||||
|
||||
block_texture.def_property_readonly("width_blocks", &BTex::BlocksX, "The width of the texture in blocks.");
|
||||
block_texture.def_property_readonly("height_blocks", &BTex::BlocksY, "The height of the texture in blocks.");
|
||||
block_texture.def_property_readonly("size_blocks", &BTex::BlocksXY, "The dimensions of the texture in blocks.");
|
||||
|
||||
DefSubscript2D(block_texture, &BTex::GetBlock, &BTex::SetBlock, &BTex::BlocksXY);
|
||||
|
||||
return std::move(block_texture);
|
||||
}
|
||||
} // namespace quicktex::bindings
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich 2020 <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
0
quicktex/cli/__init__.py
Normal file
0
quicktex/cli/__init__.py
Normal file
63
quicktex/cli/common.py
Normal file
63
quicktex/cli/common.py
Normal file
@ -0,0 +1,63 @@
|
||||
import pathlib
|
||||
from typing import List
|
||||
|
||||
import click
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def get_decoded_extensions(feature: str = 'open') -> List[str]:
|
||||
"""Gets a list of extensions for Pillow formats supporting a supplied feature"""
|
||||
extensions = Image.registered_extensions() # using registered_extensions() triggers lazy loading of format data
|
||||
formats = getattr(Image, feature.upper()).keys()
|
||||
|
||||
return [ext for ext, fmt in extensions.items() if fmt in formats]
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def validate_decoded_extension(ctx, param, value) -> str:
|
||||
"""Check if an extension for a decoded image is valid"""
|
||||
if value[0] != '.':
|
||||
value = '.' + value
|
||||
|
||||
if value not in decoded_extensions:
|
||||
raise click.BadParameter(f'Invalid extension for decoded file. Valid extensions are:\n{decoded_extensions}')
|
||||
|
||||
return value
|
||||
|
||||
|
||||
decoded_extensions = get_decoded_extensions()
|
||||
encoded_extensions = '.dds'
|
||||
|
||||
|
||||
def path_pairs(inputs, output, suffix, extension):
|
||||
"""
|
||||
Generates pairs of (inpath, outpath) for the given parameters
|
||||
:param inputs: A list of input paths to read from
|
||||
:param output: output to write to. can be a file, directory, or None to convert in place
|
||||
:param suffix: suffix to add to destinations
|
||||
:param extension: extension to use for destinations
|
||||
:return: A list of pairs of (inpath, outpath)
|
||||
"""
|
||||
|
||||
if len(inputs) < 1:
|
||||
raise click.BadArgumentUsage('No valid input files were provided.')
|
||||
|
||||
inpaths = [pathlib.Path(i) for i in inputs]
|
||||
|
||||
if not output:
|
||||
# decode in place
|
||||
return [(inpath, inpath.with_name(inpath.stem + suffix + extension)) for inpath in inpaths]
|
||||
|
||||
else:
|
||||
outpath = pathlib.Path(output)
|
||||
if outpath.is_file():
|
||||
# decode to a file
|
||||
if len(inputs) > 1:
|
||||
raise click.BadOptionUsage('output', 'Output is a single file, but multiple input files were provided.')
|
||||
# if outpath.suffix not in decoded_extensions:
|
||||
# raise click.BadOptionUsage('output', f'File has incorrect extension for decoded file. Valid extensions are:\n{decoded_extensions}')
|
||||
|
||||
return [(inpath, outpath) for inpath in inpaths]
|
||||
else:
|
||||
# decode to directory
|
||||
return [(inpath, outpath / (inpath.stem + suffix + extension)) for inpath in inpaths]
|
63
quicktex/cli/decode.py
Normal file
63
quicktex/cli/decode.py
Normal file
@ -0,0 +1,63 @@
|
||||
import os.path
|
||||
|
||||
import click
|
||||
from PIL import Image
|
||||
|
||||
import quicktex.cli.common as common
|
||||
import quicktex.dds as dds
|
||||
|
||||
|
||||
@click.command()
|
||||
@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.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:
|
||||
for inpath, outpath in bar:
|
||||
if inpath.suffix != '.dds':
|
||||
raise click.BadArgumentUsage(f"Input file '{inpath}' is not a DDS file.")
|
||||
|
||||
image = dds.read(inpath).decode()
|
||||
|
||||
if flip:
|
||||
image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
|
||||
image.save(outpath)
|
||||
|
||||
if remove:
|
||||
os.remove(inpath)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
decode()
|
220
quicktex/cli/encode.py
Normal file
220
quicktex/cli/encode.py
Normal file
@ -0,0 +1,220 @@
|
||||
import os
|
||||
|
||||
import click
|
||||
from PIL import Image
|
||||
|
||||
import quicktex.cli.common as common
|
||||
import quicktex.dds as dds
|
||||
import quicktex.s3tc.bc1
|
||||
import quicktex.s3tc.bc3
|
||||
import quicktex.s3tc.bc4
|
||||
import quicktex.s3tc.bc5
|
||||
|
||||
|
||||
@click.group()
|
||||
def encode():
|
||||
"""Encode images to DDS files of the given format."""
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
'-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image before converting."
|
||||
)
|
||||
@click.option('-r', '--remove', is_flag=True, help="Remove input images after converting.")
|
||||
@click.option(
|
||||
'-s',
|
||||
'--suffix',
|
||||
type=str,
|
||||
default='',
|
||||
help="Suffix to append to output file(s). Ignored if output is a single file.",
|
||||
)
|
||||
@click.option(
|
||||
'-o',
|
||||
'--output',
|
||||
type=click.Path(writable=True),
|
||||
default=None,
|
||||
help="Output file or directory. If outputting to a file, input filenames must be only a single item. By default, files are decoded in place.",
|
||||
)
|
||||
@click.argument('filenames', nargs=-1, type=click.Path(exists=True, readable=True, dir_okay=False))
|
||||
def encode_format(encoder, four_cc, flip, remove, suffix, output, filenames, swizzle=False):
|
||||
filenames = [f for f in filenames if not f.endswith('.dds')]
|
||||
path_pairs = common.path_pairs(filenames, output, suffix, '.dds')
|
||||
|
||||
with click.progressbar(
|
||||
path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else ''
|
||||
) as bar:
|
||||
for inpath, outpath in bar:
|
||||
image = Image.open(inpath)
|
||||
|
||||
if flip:
|
||||
image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
|
||||
if swizzle:
|
||||
bands = image.split()
|
||||
one = Image.new('L', image.size, 0xFF)
|
||||
image = Image.merge('RGBA', (one, bands[1], bands[1], bands[0]))
|
||||
|
||||
dds.encode(image, encoder, four_cc).save(outpath)
|
||||
|
||||
if remove:
|
||||
os.remove(inpath)
|
||||
|
||||
|
||||
@click.command('auto')
|
||||
@click.option(
|
||||
'-l',
|
||||
'--level',
|
||||
type=click.IntRange(0, 18),
|
||||
default=18,
|
||||
help='Quality level to use. Higher values = higher quality, but slower.',
|
||||
)
|
||||
@click.option(
|
||||
'-b/-B',
|
||||
'--black/--no-black',
|
||||
help='[BC1 only] Enable 3-color mode for blocks containing black or very dark pixels. --3color must also be enabled for this to work.'
|
||||
' (Important: engine/shader MUST ignore decoded texture alpha if this flag is enabled!)',
|
||||
)
|
||||
@click.option(
|
||||
'-3/-4',
|
||||
'--3color/--4color',
|
||||
'threecolor',
|
||||
default=True,
|
||||
help='[BC1 only] Enable 3-color mode for non-black pixels. Higher quality, but slightly slower.',
|
||||
)
|
||||
@click.option(
|
||||
'-f/-F', '--flip/--no-flip', default=True, show_default=True, help="Vertically flip image before converting."
|
||||
)
|
||||
@click.option('-r', '--remove', is_flag=True, help="Remove input images after converting.")
|
||||
@click.option(
|
||||
'-s',
|
||||
'--suffix',
|
||||
type=str,
|
||||
default='',
|
||||
help="Suffix to append to output file(s). Ignored if output is a single file.",
|
||||
)
|
||||
@click.option(
|
||||
'-o',
|
||||
'--output',
|
||||
type=click.Path(writable=True),
|
||||
default=None,
|
||||
help="Output file or directory. If outputting to a file, input filenames must be only a single item. By default, files are decoded in place.",
|
||||
)
|
||||
@click.argument('filenames', nargs=-1, type=click.Path(exists=True, readable=True, dir_okay=False))
|
||||
def encode_auto(level, black, threecolor, flip, remove, suffix, output, filenames):
|
||||
"""Encode images to BC1 or BC3, with the format chosen based on each image's alpha channel."""
|
||||
|
||||
color_mode = quicktex.s3tc.bc1.BC1Encoder.ColorMode
|
||||
if not threecolor:
|
||||
mode = color_mode.FourColor
|
||||
elif not black:
|
||||
mode = color_mode.ThreeColor
|
||||
else:
|
||||
mode = color_mode.ThreeColorBlack
|
||||
|
||||
bc1_encoder = quicktex.s3tc.bc1.BC1Encoder(level, mode)
|
||||
bc3_encoder = quicktex.s3tc.bc3.BC3Encoder(level)
|
||||
filenames = [f for f in filenames if not f.endswith('.dds')]
|
||||
path_pairs = common.path_pairs(filenames, output, suffix, '.dds')
|
||||
|
||||
assert len(filenames) > 0
|
||||
|
||||
with click.progressbar(
|
||||
path_pairs, show_eta=False, show_pos=True, item_show_func=lambda x: str(x[0]) if x else ''
|
||||
) as bar:
|
||||
for inpath, outpath in bar:
|
||||
image = Image.open(inpath)
|
||||
|
||||
if flip:
|
||||
image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
|
||||
if 'A' not in image.mode:
|
||||
has_alpha = False
|
||||
else:
|
||||
alpha_hist = image.getchannel('A').histogram()
|
||||
has_alpha = any([a > 0 for a in alpha_hist[:-1]])
|
||||
|
||||
if has_alpha:
|
||||
dds.encode(image, bc3_encoder, 'DXT5').save(outpath)
|
||||
else:
|
||||
dds.encode(image, bc1_encoder, 'DXT1').save(outpath)
|
||||
|
||||
if remove:
|
||||
os.remove(inpath)
|
||||
|
||||
|
||||
@click.command('bc1')
|
||||
@click.option(
|
||||
'-l',
|
||||
'--level',
|
||||
type=click.IntRange(0, 18),
|
||||
default=18,
|
||||
help='Quality level to use. Higher values = higher quality, but slower.',
|
||||
)
|
||||
@click.option(
|
||||
'-b/-B',
|
||||
'--black/--no-black',
|
||||
help='Enable 3-color mode for blocks containing black or very dark pixels. --3color must also be enabled for this to work.'
|
||||
' (Important: engine/shader MUST ignore decoded texture alpha if this flag is enabled!)',
|
||||
)
|
||||
@click.option(
|
||||
'-3/-4',
|
||||
'--3color/--4color',
|
||||
'threecolor',
|
||||
default=True,
|
||||
help='Enable 3-color mode for non-black pixels. Higher quality, but slightly slower.',
|
||||
)
|
||||
def encode_bc1(level, black, threecolor, **kwargs):
|
||||
"""Encode images to BC1 (RGB, no alpha)."""
|
||||
color_mode = quicktex.s3tc.bc1.BC1Encoder.ColorMode
|
||||
if not threecolor:
|
||||
mode = color_mode.FourColor
|
||||
elif not black:
|
||||
mode = color_mode.ThreeColor
|
||||
else:
|
||||
mode = color_mode.ThreeColorBlack
|
||||
|
||||
encode_format.callback(encoder=quicktex.s3tc.bc1.BC1Encoder(level, mode), four_cc='DXT1', **kwargs)
|
||||
|
||||
|
||||
@click.command('bc3')
|
||||
@click.option(
|
||||
'-l',
|
||||
'--level',
|
||||
type=click.IntRange(0, 18),
|
||||
default=18,
|
||||
help='Quality level to use. Higher values = higher quality, but slower.',
|
||||
)
|
||||
@click.option(
|
||||
'-n/-N',
|
||||
'--normal/--no-normal',
|
||||
type=bool,
|
||||
default=False,
|
||||
help='Perform a BC3nm swizzle, copying the red channel into the alpha [default: no-normal]',
|
||||
)
|
||||
def encode_bc3(level, normal, **kwargs):
|
||||
"""Encode images to BC4 (RGBA, 8-bit interpolated alpha)."""
|
||||
encode_format.callback(quicktex.s3tc.bc3.BC3Encoder(level), 'DXT5', swizzle=normal, **kwargs)
|
||||
|
||||
|
||||
@click.command('bc4')
|
||||
def encode_bc4(**kwargs):
|
||||
"""Encode images to BC4 (Single channel, 8-bit interpolated red channel)."""
|
||||
encode_format.callback(quicktex.s3tc.bc4.BC4Encoder(), 'ATI1', **kwargs)
|
||||
|
||||
|
||||
@click.command('bc5')
|
||||
def encode_bc5(**kwargs):
|
||||
"""Encode images to BC5 (2-channel, 8-bit interpolated red and green channels)."""
|
||||
encode_format.callback(quicktex.s3tc.bc5.BC5Encoder(), 'ATI2', **kwargs)
|
||||
|
||||
|
||||
encode_bc1.params += encode_format.params
|
||||
encode_bc3.params += encode_format.params
|
||||
encode_bc4.params += encode_format.params
|
||||
encode_bc5.params += encode_format.params
|
||||
|
||||
encode.add_command(encode_bc1)
|
||||
encode.add_command(encode_bc3)
|
||||
encode.add_command(encode_bc4)
|
||||
encode.add_command(encode_bc5)
|
||||
encode.add_command(encode_auto)
|
303
quicktex/dds.py
Normal file
303
quicktex/dds.py
Normal file
@ -0,0 +1,303 @@
|
||||
from __future__ import annotations
|
||||
|
||||
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
|
||||
|
||||
|
||||
class DDSFormat:
|
||||
def __init__(self, name: str, texture, encoder, decoder, four_cc: str = None):
|
||||
self.four_cc = four_cc
|
||||
self.decoder = decoder
|
||||
self.encoder = encoder
|
||||
self.texture = texture
|
||||
self.name = name
|
||||
|
||||
|
||||
dds_formats = [
|
||||
DDSFormat('BC1', bc1.BC1Texture, bc1.BC1Encoder, bc1.BC1Decoder, 'DXT1'),
|
||||
DDSFormat('BC3', bc3.BC3Texture, bc3.BC3Encoder, bc3.BC3Decoder, 'DXT5'),
|
||||
DDSFormat('BC4', bc4.BC4Texture, bc4.BC4Encoder, bc4.BC4Decoder, 'ATI1'),
|
||||
DDSFormat('BC5', bc5.BC5Texture, bc5.BC5Encoder, bc5.BC5Decoder, 'ATI2'),
|
||||
]
|
||||
|
||||
|
||||
class PFFlags(enum.IntFlag):
|
||||
"""Values which indicate what type of data is in the surface."""
|
||||
|
||||
ALPHAPIXELS = 0x1
|
||||
"""Texture contains alpha data (:py:attr:`~PixelFormat.pixel_bitmasks[3]` contains valid data)."""
|
||||
|
||||
ALPHA = 0x2
|
||||
"""Used in some older DDS files for alpha channel only uncompressed data
|
||||
(:py:attr:`~PixelFormat.pixel_size` contains the alpha channel bitcount; :py:attr:`~PixelFormat.pixel_bitmasks[3]` contains valid data)."""
|
||||
|
||||
FOURCC = 0x4
|
||||
"""Texture contains compressed RGB data; :py:attr:`~PixelFormat.four_cc` contains valid data."""
|
||||
|
||||
RGB = 0x40
|
||||
"""Texture contains uncompressed RGB data; :py:attr:`~PixelFormat.pixel_size` and the RGB masks
|
||||
(:py:attr:`~PixelFormat.pixel_bitmasks[0:3]`) contain valid data."""
|
||||
|
||||
YUV = 0x200
|
||||
"""Used in some older DDS files for YUV uncompressed data
|
||||
(:py:attr:`~PixelFormat.pixel_size` contains the YUV bit count; :py:attr:`~PixelFormat.pixel_bitmasks[0]` contains the Y mask,
|
||||
:py:attr:`~PixelFormat.pixel_bitmasks[1]` contains the U mask, :py:attr:`~PixelFormat.pixel_bitmasks[2]` contains the V mask)."""
|
||||
|
||||
LUMINANCE = 0x20000
|
||||
"""Used in some older DDS files for single channel color uncompressed data (:py:attr:`~PixelFormat.pixel_size`
|
||||
contains the luminance channel bit count; :py:attr:`~PixelFormat.pixel_bitmasks[0]` contains the channel mask).
|
||||
Can be combined with :py:attr:`ALPHAPIXELS` for a two channel uncompressed DDS file."""
|
||||
|
||||
|
||||
class DDSFlags(enum.IntFlag):
|
||||
"""Flags to indicate which members contain valid data."""
|
||||
|
||||
CAPS = 0x1
|
||||
"""Required in every .dds file."""
|
||||
|
||||
HEIGHT = 0x2
|
||||
"""Required in every .dds file."""
|
||||
|
||||
WIDTH = 0x4
|
||||
"""Required in every .dds file."""
|
||||
|
||||
PITCH = 0x8
|
||||
"""Required when :py:attr:`~DDSHeader.pitch` is provided for an uncompressed texture."""
|
||||
|
||||
PIXEL_FORMAT = 0x1000
|
||||
"""Required in every .dds file."""
|
||||
|
||||
MIPMAPCOUNT = 0x20000
|
||||
"""Required when :py:attr:`~DDSHeader.mipmap_count` is provided for a mipmapped texture."""
|
||||
|
||||
LINEAR_SIZE = 0x80000
|
||||
"""Required when :py:attr:`~DDSHeader.pitch` is provided for a compressed texture."""
|
||||
|
||||
DEPTH = 0x800000
|
||||
"""Required when :py:attr:`~DDSHeader.depth` is provided for a depth texture."""
|
||||
|
||||
TEXTURE = CAPS | HEIGHT | WIDTH | PIXEL_FORMAT
|
||||
|
||||
|
||||
class Caps0(enum.IntFlag):
|
||||
"""Flags to indicate surface complexity"""
|
||||
|
||||
COMPLEX = 0x8
|
||||
"""Optional; must be used on any file that contains more than one surface (a mipmap, a cubic environment map, or mipmapped volume texture)."""
|
||||
|
||||
MIPMAP = 0x400000
|
||||
"""Optional; should be used for a mipmap."""
|
||||
|
||||
TEXTURE = 0x1000
|
||||
"""Required"""
|
||||
|
||||
|
||||
@typing.final
|
||||
class DDSFile:
|
||||
"""
|
||||
A microsoft DDS file, containing header information and one or more textures
|
||||
|
||||
For more information, see microsoft's `Reference for DDS <https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-reference>`_.
|
||||
"""
|
||||
|
||||
magic = b'DDS '
|
||||
"""Magic bytes at the start of every DDS file."""
|
||||
|
||||
extension = 'dds'
|
||||
"""Extension for a DDS file."""
|
||||
|
||||
header_bytes = 124
|
||||
"""The size of a DDS header in bytes."""
|
||||
|
||||
def __init__(self):
|
||||
self.flags: DDSFlags = DDSFlags.TEXTURE
|
||||
"""Flags to indicate which members contain valid data."""
|
||||
|
||||
self.size: typing.Tuple[int, int] = (0, 0)
|
||||
"""Width and height of the texture or its first mipmap"""
|
||||
|
||||
self.pitch: int = 0
|
||||
"""The pitch or number of bytes per row in an uncompressed texture;
|
||||
the total number of bytes in the top level texture for a compressed texture."""
|
||||
|
||||
self.depth: int = 1
|
||||
"""Depth of a volume texture (in pixels), otherwise unused."""
|
||||
|
||||
self.mipmap_count: int = 1
|
||||
"""Number of mipmap levels, otherwise unused."""
|
||||
|
||||
self.pf_flags: PFFlags = PFFlags.FOURCC
|
||||
"""Flags representing which pixel format data is valid."""
|
||||
|
||||
self.four_cc: str = "NONE"
|
||||
"""FourCC code of the texture format. Valid texture format strings are ``DXT1``, ``DXT2``, ``DXT3``, ``DXT4``, or ``DXT5``.
|
||||
If a DirectX 10 header is used, this is ``DX10``."""
|
||||
|
||||
self.pixel_size: int = 0
|
||||
"""Number of bits in each pixel if the texture is uncompressed"""
|
||||
|
||||
self.pixel_bitmasks: typing.Tuple[int, int, int, int] = (0, 0, 0, 0)
|
||||
"""Tuple of bitmasks for each channel"""
|
||||
|
||||
self.caps: typing.Tuple[Caps0, int, int, int] = (Caps0.TEXTURE, 0, 0, 0)
|
||||
"""Specifies the complexity of the surfaces stored."""
|
||||
|
||||
self.textures: typing.List = []
|
||||
"""A list of bytes objects for each texture in the file"""
|
||||
|
||||
self.format: DDSFormat = DDSFormat('NONE', None, None, None)
|
||||
"""The format used by this dds file"""
|
||||
|
||||
def save(self, path: os.PathLike) -> None:
|
||||
"""
|
||||
Save the DDSFile to a file
|
||||
:param path: string or path-like object to write to
|
||||
"""
|
||||
with open(path, 'wb') as file:
|
||||
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('<4I4x', *self.caps))
|
||||
|
||||
assert file.tell() == 4 + DDSFile.header_bytes, 'error writing file: incorrect header size'
|
||||
|
||||
for texture in self.textures:
|
||||
file.write(texture)
|
||||
|
||||
def decode(self, mip: int = 0, *args, **kwargs) -> Image.Image:
|
||||
"""
|
||||
Decode a single texture in the file to images
|
||||
:param mip: the mip level to decode. Default: 0
|
||||
:return: The decoded image
|
||||
"""
|
||||
decoder = self.format.decoder(*args, **kwargs)
|
||||
texture = decoder.decode(self.textures[mip])
|
||||
return Image.frombuffer('RGBA', texture.size, texture)
|
||||
|
||||
def decode_all(self, *args, **kwargs) -> typing.List[Image.Image]:
|
||||
"""
|
||||
Decade all textures in the file to images
|
||||
:return: the decoded images
|
||||
"""
|
||||
decoder = self.format.decoder(*args, **kwargs)
|
||||
textures = [decoder.decode(encoded) for encoded in self.textures]
|
||||
return [Image.frombuffer('RGBA', tex.size, tex) for tex in textures]
|
||||
|
||||
|
||||
def read(path: os.PathLike) -> DDSFile:
|
||||
with open(path, 'rb') as file:
|
||||
assert file.read(4) == DDSFile.magic, "Incorrect magic bytes in DDS file."
|
||||
|
||||
dds = DDSFile()
|
||||
|
||||
# READ HEADER
|
||||
header_bytes = struct.unpack('<I', file.read(4))[0]
|
||||
assert header_bytes == DDSFile.header_bytes, "Incorrect DDS header size."
|
||||
|
||||
dds.flags = DDSFlags(struct.unpack('<I', file.read(4))[0]) # read flags enum
|
||||
dds.size = struct.unpack('<2I', file.read(8))[::-1] # read dimensions
|
||||
dds.pitch, dds.depth, dds.mipmap_count = struct.unpack('<3I', file.read(12))
|
||||
file.read(44) # skip 44 unused bytes of data
|
||||
|
||||
assert struct.unpack('<I', file.read(4))[0] == 32, "Incorrect pixel format size."
|
||||
|
||||
dds.pf_flags = PFFlags(struct.unpack('<I', file.read(4))[0])
|
||||
dds.four_cc = file.read(4).decode()
|
||||
dds.pixel_size, *pixel_bitmasks = struct.unpack('<5I', file.read(20))
|
||||
|
||||
dds.caps = struct.unpack('<4I', file.read(16))
|
||||
file.read(4) # skip 4 unused bytes of data
|
||||
|
||||
assert file.tell() == 4 + DDSFile.header_bytes, "Unexpected EOF" # make sure we are where we expect to be
|
||||
|
||||
if DDSFlags.DEPTH not in dds.flags:
|
||||
dds.depth = 1
|
||||
if DDSFlags.MIPMAPCOUNT not in dds.flags:
|
||||
dds.mipmap_count = 1
|
||||
if PFFlags.FOURCC not in dds.pf_flags:
|
||||
dds.four_cc = 'NONE'
|
||||
|
||||
# READ DX10_HEADER
|
||||
if dds.four_cc == 'DX10':
|
||||
raise NotImplementedError('DX10 headers are not yet supported')
|
||||
|
||||
# identify the format used
|
||||
dds.format = next(entry for entry in dds_formats if entry.four_cc == dds.four_cc)
|
||||
|
||||
# calculate the size of each level of the texture
|
||||
sizes = quicktex.image_utils.mip_sizes(dds.size, dds.mipmap_count)
|
||||
|
||||
# READ TEXTURES
|
||||
dds.textures = []
|
||||
for size in sizes:
|
||||
texture = dds.format.texture(*size) # make a new blocktexture of the current mip size
|
||||
nbytes = file.readinto(texture)
|
||||
|
||||
assert nbytes == texture.nbytes, 'Unexpected end of file'
|
||||
|
||||
dds.textures.append(texture)
|
||||
|
||||
return dds
|
||||
|
||||
|
||||
def encode(image: Image.Image, encoder, four_cc: str, mip_count: typing.Optional[int] = None) -> DDSFile:
|
||||
if image.mode != 'RGBA' or image.mode != 'RGBX':
|
||||
mode = 'RGBA' if 'A' in image.mode else 'RGBX'
|
||||
image.apply_transparency() # why is this necessary what
|
||||
image = image.convert(mode)
|
||||
|
||||
sizes = quicktex.image_utils.mip_sizes(image.size, mip_count)
|
||||
images = [image] + [quicktex.image_utils.resize_no_premultiply(image, size) for size in sizes[1:]]
|
||||
dds = DDSFile()
|
||||
|
||||
for i in images:
|
||||
rawtex = quicktex.RawTexture.frombytes(i.tobytes('raw', mode), *i.size)
|
||||
dds.textures.append(encoder.encode(rawtex))
|
||||
|
||||
dds.flags = DDSFlags.TEXTURE | DDSFlags.LINEAR_SIZE
|
||||
caps0 = Caps0.TEXTURE
|
||||
|
||||
if len(images) > 1:
|
||||
dds.flags |= DDSFlags.MIPMAPCOUNT
|
||||
caps0 |= Caps0.MIPMAP | Caps0.COMPLEX
|
||||
|
||||
dds.caps = (caps0, 0, 0, 0)
|
||||
dds.mipmap_count = len(images)
|
||||
dds.pitch = dds.textures[0].nbytes
|
||||
dds.size = dds.textures[0].size
|
||||
dds.pf_flags = PFFlags.FOURCC
|
||||
dds.four_cc = four_cc
|
||||
|
||||
return dds
|
52
quicktex/image_utils.py
Normal file
52
quicktex/image_utils.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""Various utilities for working with Pillow images"""
|
||||
|
||||
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]]:
|
||||
"""
|
||||
Create a chain of mipmap sizes for a given source source size, where each source is half the size of the one before.
|
||||
Note that the division by 2 rounds down. So a 63x63 texture has as its next lowest mipmap level 31x31. And so on.
|
||||
|
||||
See the `OpenGL wiki page on mipmaps <https://www.khronos.org/opengl/wiki/Texture#Mip_maps>`_ for more info.
|
||||
|
||||
:param dimensions: Size of the source source in pixels
|
||||
:param mip_count: Number of mipmap sizes to generate. By default, generate until the last mip level is 1x1.
|
||||
Resulting mip chain will be smaller if a 1x1 mip level is reached before this value.
|
||||
:return: A list of 2-tuples representing the dimensions of each mip level, including ``dimensions`` at element 0.
|
||||
"""
|
||||
assert all([dim > 0 for dim in dimensions]), "Invalid source dimensions"
|
||||
if not mip_count:
|
||||
mip_count = math.ceil(math.log2(max(dimensions)) + 1) # maximum possible number of mips for a given source
|
||||
|
||||
assert mip_count > 0, "mip_count must be greater than 0"
|
||||
|
||||
chain = []
|
||||
|
||||
for mip in range(mip_count):
|
||||
chain.append(dimensions)
|
||||
if all([dim == 1 for dim in dimensions]):
|
||||
break # we've reached a 1x1 mip and can get no smaller
|
||||
dimensions = tuple([max(dim // 2, 1) for dim in dimensions])
|
||||
|
||||
return chain
|
||||
|
||||
|
||||
def resize_no_premultiply(image: Image.Image, size: Tuple[int, int]) -> Image.Image:
|
||||
"""
|
||||
Resize an image without premulitplying the alpha. Required due to a quick in Pillow
|
||||
|
||||
:param image: Image to resize
|
||||
:param size: Size to resize to
|
||||
:return: The resized image
|
||||
"""
|
||||
if image.mode == 'RGBA':
|
||||
rgb = image.convert('RGB').resize(size, Image.BILINEAR)
|
||||
a = image.getchannel('A').resize(size, Image.BILINEAR)
|
||||
rgb.putalpha(a)
|
||||
return rgb
|
||||
else:
|
||||
return image.resize(size, Image.BILINEAR)
|
0
quicktex/py.typed
Normal file
0
quicktex/py.typed
Normal file
5
quicktex/s3tc/__init__.py
Normal file
5
quicktex/s3tc/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""
|
||||
The s3tc module provides encoders and decoders for the common S3 Texture Compression formats. BC1-5 encoding is done
|
||||
internally using a modified version of rgbcx.h.
|
||||
"""
|
||||
from _quicktex._s3tc import *
|
45
quicktex/s3tc/_bindings.cpp
Normal file
45
quicktex/s3tc/_bindings.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include "interpolator/Interpolator.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
namespace quicktex::bindings {
|
||||
|
||||
using namespace quicktex;
|
||||
using namespace quicktex::s3tc;
|
||||
|
||||
void InitInterpolator(py::module_ &s3tc);
|
||||
void InitBC1(py::module_ &s3tc);
|
||||
void InitBC3(py::module_ &s3tc);
|
||||
void InitBC4(py::module_ &s3tc);
|
||||
void InitBC5(py::module_ &s3tc);
|
||||
|
||||
void InitS3TC(py::module_ &m) {
|
||||
py::module_ s3tc = m.def_submodule("_s3tc", "s3tc compression library based on rgbcx.h written by Richard Goldreich");
|
||||
|
||||
InitInterpolator(s3tc);
|
||||
InitBC1(s3tc);
|
||||
InitBC4(s3tc);
|
||||
InitBC3(s3tc);
|
||||
InitBC5(s3tc);
|
||||
}
|
||||
} // namespace quicktex::bindings
|
47
quicktex/s3tc/bc1/BC1Block.cpp
Normal file
47
quicktex/s3tc/bc1/BC1Block.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include "BC1Block.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../../util.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
uint16_t BC1Block::GetColor0Raw() const { return Pack<uint8_t, uint16_t, 8, EndpointSize>(_color0); }
|
||||
uint16_t BC1Block::GetColor1Raw() const { return Pack<uint8_t, uint16_t, 8, EndpointSize>(_color1); }
|
||||
|
||||
void BC1Block::SetColor0Raw(uint16_t c) { _color0 = Unpack<uint16_t, uint8_t, 8, EndpointSize>(c); }
|
||||
void BC1Block::SetColor1Raw(uint16_t c) { _color1 = Unpack<uint16_t, uint8_t, 8, EndpointSize>(c); }
|
||||
|
||||
BC1Block::SelectorArray BC1Block::GetSelectors() const { return MapArray(_selectors, Unpack<uint8_t, uint8_t, SelectorBits, Width>); }
|
||||
|
||||
void BC1Block::SetSelectors(const BC1Block::SelectorArray& unpacked) {
|
||||
for (unsigned y = 0; y < (unsigned)Height; y++) {
|
||||
if (std::any_of(unpacked[y].begin(), unpacked[y].end(), [](uint8_t i) { return i > SelectorMax; }))
|
||||
throw std::invalid_argument("Selector value out of bounds.");
|
||||
}
|
||||
_selectors = MapArray(unpacked, Pack<uint8_t, uint8_t, SelectorBits, Width>);
|
||||
}
|
||||
|
||||
bool BC1Block::operator==(const BC1Block& Rhs) const { return _color0 == Rhs._color0 && _color1 == Rhs._color1 && _selectors == Rhs._selectors; }
|
||||
bool BC1Block::operator!=(const BC1Block& Rhs) const { return !(Rhs == *this); }
|
||||
|
||||
} // namespace quicktex::s3tc
|
127
quicktex/s3tc/bc1/BC1Block.h
Normal file
127
quicktex/s3tc/bc1/BC1Block.h
Normal file
@ -0,0 +1,127 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
#include "../../Color.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class alignas(8) BC1Block {
|
||||
public:
|
||||
static constexpr size_t Width = 4;
|
||||
static constexpr size_t Height = 4;
|
||||
|
||||
static constexpr size_t EndpointSize = 2; // size of a 5:6:5 endpoint in bytes
|
||||
static constexpr size_t SelectorSize = 4; // size of selector array in bytes
|
||||
static constexpr size_t SelectorBits = 2; // size of a selector in bits
|
||||
static constexpr uint8_t SelectorMax = (1 << SelectorBits) - 1; // maximum value of a selector
|
||||
|
||||
using SelectorArray = std::array<std::array<uint8_t, Width>, Height>;
|
||||
using ColorPair = std::pair<Color, Color>;
|
||||
|
||||
private:
|
||||
std::array<uint8_t, EndpointSize> _color0;
|
||||
std::array<uint8_t, EndpointSize> _color1;
|
||||
std::array<uint8_t, SelectorSize> _selectors;
|
||||
|
||||
public:
|
||||
/// Create a new BC1Blok
|
||||
constexpr BC1Block() : _color0(), _color1(), _selectors() {
|
||||
static_assert(sizeof(BC1Block) == 8);
|
||||
static_assert(sizeof(std::array<BC1Block, 10>) == 8 * 10);
|
||||
static_assert(alignof(BC1Block) >= 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new BC1Block
|
||||
* @param color0 first endpoint color
|
||||
* @param color1 second endpoint color
|
||||
* @param selectors the selectors as a 4x4 list of integers, between 0 and 3 inclusive.
|
||||
*/
|
||||
BC1Block(Color color0, Color color1, const SelectorArray& selectors) {
|
||||
SetColor0(color0);
|
||||
SetColor1(color1);
|
||||
SetSelectors(selectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new BC1Block
|
||||
* @param ep0 first endpoint
|
||||
* @param ep1 second endpoint
|
||||
* @param selectors the selectors as a 4x4 list of integers, between 0 and 3 inclusive.
|
||||
*/
|
||||
BC1Block(uint16_t ep0, uint16_t ep1, const SelectorArray& selectors) {
|
||||
SetColor0Raw(ep0);
|
||||
SetColor1Raw(ep1);
|
||||
SetSelectors(selectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new BC1Block
|
||||
* @param ep0 first endpoint
|
||||
* @param ep1 second endpoint
|
||||
* @param solid_mask single byte mask to use for each row
|
||||
*/
|
||||
BC1Block(uint16_t ep0, uint16_t ep1, uint8_t solid_mask) {
|
||||
SetColor0Raw(ep0);
|
||||
SetColor1Raw(ep1);
|
||||
_selectors.fill(solid_mask);
|
||||
}
|
||||
|
||||
uint16_t GetColor0Raw() const;
|
||||
uint16_t GetColor1Raw() const;
|
||||
|
||||
void SetColor0Raw(uint16_t c);
|
||||
void SetColor1Raw(uint16_t c);
|
||||
|
||||
Color GetColor0() const { return Color::Unpack565(GetColor0Raw()); }
|
||||
Color GetColor1() const { return Color::Unpack565(GetColor1Raw()); }
|
||||
ColorPair GetColors() const { return {GetColor0(), GetColor1()}; }
|
||||
|
||||
void SetColor0(Color c) { SetColor0Raw(c.Pack565()); }
|
||||
void SetColor1(Color c) { SetColor1Raw(c.Pack565()); }
|
||||
void SetColors(ColorPair cs) {
|
||||
SetColor0(cs.first);
|
||||
SetColor1(cs.second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this block's selectors
|
||||
* @return a 4x4 array of integers between 0 and 3 inclusive
|
||||
*/
|
||||
SelectorArray GetSelectors() const;
|
||||
|
||||
/**
|
||||
* Set this block's selectors
|
||||
* @param unpacked a 4x4 array of integers between 0 and 3 inclusive
|
||||
*/
|
||||
void SetSelectors(const SelectorArray& unpacked);
|
||||
|
||||
bool Is3Color() const { return GetColor0Raw() <= GetColor1Raw(); }
|
||||
|
||||
bool operator==(const BC1Block& Rhs) const;
|
||||
bool operator!=(const BC1Block& Rhs) const;
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -23,30 +23,32 @@
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../BlockView.h"
|
||||
#include "../Color.h"
|
||||
#include "../ndebug.h"
|
||||
#include "../../Color.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "BC1Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
void BC1Decoder::DecodeBlock(Color4x4 dest, BC1Block *const block) const noexcept(ndebug) {
|
||||
const auto l = block->GetLowColor();
|
||||
const auto h = block->GetHighColor();
|
||||
const auto selectors = block->UnpackSelectors();
|
||||
const auto colors = _interpolator->InterpolateBC1(l, h);
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
ColorBlock<4, 4> BC1Decoder::DecodeBlock(const BC1Block &block) const { return DecodeBlock(block, true); }
|
||||
|
||||
ColorBlock<4, 4> BC1Decoder::DecodeBlock(const BC1Block &block, bool use_3color) const {
|
||||
auto output = ColorBlock<4, 4>();
|
||||
const auto l = block.GetColor0Raw();
|
||||
const auto h = block.GetColor1Raw();
|
||||
const auto selectors = block.GetSelectors();
|
||||
const auto colors = _interpolator->Interpolate565BC1(l, h, use_3color);
|
||||
|
||||
for (unsigned y = 0; y < 4; y++) {
|
||||
for (unsigned x = 0; x < 4; x++) {
|
||||
const auto selector = selectors[y][x];
|
||||
const auto color = colors[selector];
|
||||
auto color = colors[selector];
|
||||
assert(selector < 4);
|
||||
assert((color.a == 0 && selector == 3 && l <= h) || color.a == UINT8_MAX);
|
||||
if (_write_alpha) {
|
||||
dest.Get(x, y).SetRGBA(color);
|
||||
} else {
|
||||
dest.Get(x, y).SetRGB(color);
|
||||
}
|
||||
if (!write_alpha) { color.a = output.Get(x, y).a; }
|
||||
output.Set(x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -21,26 +21,31 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../BlockDecoder.h"
|
||||
#include "../BlockView.h"
|
||||
#include "../Interpolator.h"
|
||||
#include "../ndebug.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../../Decoder.h"
|
||||
#include "../../Texture.h"
|
||||
#include "../interpolator/Interpolator.h"
|
||||
#include "BC1Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
class BC1Decoder final : public BlockDecoder<BC1Block, 4, 4> {
|
||||
namespace quicktex::s3tc {
|
||||
class BC1Decoder final : public BlockDecoder<BlockTexture<BC1Block>> {
|
||||
public:
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
BC1Decoder(const InterpolatorPtr interpolator = std::make_shared<Interpolator>(), bool write_alpha = false)
|
||||
: _interpolator(interpolator), _write_alpha(write_alpha) {}
|
||||
|
||||
void DecodeBlock(Color4x4 dest, BC1Block *const block) const noexcept(ndebug) override;
|
||||
BC1Decoder(bool vwrite_alpha, InterpolatorPtr interpolator) : write_alpha(vwrite_alpha), _interpolator(interpolator) {}
|
||||
|
||||
BC1Decoder(bool vwrite_alpha = false) : BC1Decoder(vwrite_alpha, std::make_shared<Interpolator>()) {}
|
||||
|
||||
BC1Decoder(InterpolatorPtr interpolator) : BC1Decoder(false, interpolator) {}
|
||||
|
||||
ColorBlock<4, 4> DecodeBlock(const BC1Block& block) const override;
|
||||
ColorBlock<4, 4> DecodeBlock(const BC1Block& block, bool use_3color) const;
|
||||
|
||||
InterpolatorPtr GetInterpolator() const { return _interpolator; }
|
||||
constexpr bool WritesAlpha() const { return _write_alpha; }
|
||||
|
||||
bool write_alpha;
|
||||
|
||||
private:
|
||||
const InterpolatorPtr _interpolator;
|
||||
const bool _write_alpha;
|
||||
};
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex::s3tc
|
998
quicktex/s3tc/bc1/BC1Encoder.cpp
Normal file
998
quicktex/s3tc/bc1/BC1Encoder.cpp
Normal file
@ -0,0 +1,998 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include "BC1Encoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../../Color.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../../Matrix4x4.h"
|
||||
#include "../../Texture.h"
|
||||
#include "../../Vector4.h"
|
||||
#include "../../Vector4Int.h"
|
||||
#include "../../bitwiseEnums.h"
|
||||
#include "../../util.h"
|
||||
#include "Histogram.h"
|
||||
#include "OrderTable.h"
|
||||
#include "SingleColorTable.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
// constructors
|
||||
|
||||
BC1Encoder::BC1Encoder(unsigned int level, ColorMode color_mode, InterpolatorPtr interpolator) : _interpolator(interpolator), _color_mode(color_mode) {
|
||||
if (color_mode != ColorMode::FourColor && color_mode != ColorMode::ThreeColor && color_mode != ColorMode::ThreeColorBlack) {
|
||||
throw std::invalid_argument("Encoder color mode must be FourColor, ThreeColor, or ThreeColorBlack");
|
||||
}
|
||||
|
||||
OrderTable<4>::Generate();
|
||||
_single_match5 = SingleColorTable<5, 4>(_interpolator);
|
||||
_single_match6 = SingleColorTable<6, 4>(_interpolator);
|
||||
|
||||
if (!OrderTable<4>::generated) throw std::runtime_error("Failed to generate 4-color order tables");
|
||||
if (!_single_match5) throw std::runtime_error("Failed to generate 5-bit 4-color single color table");
|
||||
if (!_single_match6) throw std::runtime_error("Failed to generate 6-bit 4-color single color table");
|
||||
|
||||
if (color_mode != ColorMode::FourColor) {
|
||||
OrderTable<3>::Generate();
|
||||
_single_match5_half = SingleColorTable<5, 3>(_interpolator);
|
||||
_single_match6_half = SingleColorTable<6, 3>(_interpolator);
|
||||
|
||||
if (!OrderTable<3>::generated) throw std::runtime_error("Failed to generate 3-color order tables");
|
||||
if (!_single_match5_half) throw std::runtime_error("Failed to generate 5-bit 3-color single color table");
|
||||
if (!_single_match6_half) throw std::runtime_error("Failed to generate 6-bit 3-color single color table");
|
||||
}
|
||||
|
||||
SetLevel(level);
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
void BC1Encoder::SetLevel(unsigned level) {
|
||||
if (level > 19) throw std::invalid_argument("Level out of range, bust be between 0 and 18 inclusive"); // theres a secret level 19 but shhhhhh
|
||||
|
||||
two_ls_passes = false;
|
||||
two_ep_passes = false;
|
||||
two_cf_passes = false;
|
||||
exhaustive = false;
|
||||
|
||||
_power_iterations = 4;
|
||||
_error_mode = ErrorMode::Check2;
|
||||
_endpoint_mode = EndpointMode::PCA;
|
||||
_search_rounds = 0;
|
||||
_orderings3 = 0;
|
||||
_orderings4 = 0;
|
||||
|
||||
switch (level) {
|
||||
case 0:
|
||||
// Faster/higher quality than stb_dxt default.
|
||||
_endpoint_mode = EndpointMode::BoundingBoxInt;
|
||||
break;
|
||||
case 1:
|
||||
// Faster/higher quality than stb_dxt default. a bit higher average quality vs. mode 0.
|
||||
_endpoint_mode = EndpointMode::LeastSquares;
|
||||
break;
|
||||
case 2:
|
||||
// On average mode 2 is a little weaker than modes 0/1, but it's stronger on outliers (very tough textures).
|
||||
// Slightly stronger than stb_dxt.
|
||||
// Uses default settings.
|
||||
break;
|
||||
case 3:
|
||||
// Slightly stronger than stb_dxt HIGHQUAL.
|
||||
two_ls_passes = true;
|
||||
break;
|
||||
case 4:
|
||||
two_ls_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Full;
|
||||
_power_iterations = 6;
|
||||
break;
|
||||
default:
|
||||
case 5:
|
||||
// stb_dxt HIGHQUAL + permit 3 color (if it's enabled).
|
||||
two_ls_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Faster;
|
||||
break;
|
||||
case 6:
|
||||
two_ls_passes = true;
|
||||
|
||||
_orderings4 = 1;
|
||||
_orderings3 = 1;
|
||||
_error_mode = ErrorMode::Faster;
|
||||
break;
|
||||
case 7:
|
||||
two_ls_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Faster;
|
||||
_orderings4 = 4;
|
||||
_orderings3 = 1;
|
||||
break;
|
||||
case 8:
|
||||
two_ls_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Faster;
|
||||
_orderings4 = 8;
|
||||
_orderings3 = 1;
|
||||
break;
|
||||
case 9:
|
||||
two_ls_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Check2;
|
||||
_orderings4 = 11;
|
||||
_orderings3 = 3;
|
||||
break;
|
||||
case 10:
|
||||
two_ls_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Check2;
|
||||
_orderings4 = 20;
|
||||
_orderings3 = 8;
|
||||
break;
|
||||
case 11:
|
||||
two_ls_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Check2;
|
||||
_orderings4 = 28;
|
||||
_orderings3 = 16;
|
||||
break;
|
||||
case 12:
|
||||
two_ls_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Check2;
|
||||
_orderings4 = 32;
|
||||
_orderings3 = 32;
|
||||
break;
|
||||
case 13:
|
||||
two_ls_passes = true;
|
||||
two_ep_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Full;
|
||||
_orderings4 = 32;
|
||||
_orderings3 = 32;
|
||||
_search_rounds = 20;
|
||||
_power_iterations = 6;
|
||||
break;
|
||||
case 14:
|
||||
two_ls_passes = true;
|
||||
two_ep_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Full;
|
||||
_orderings4 = 32;
|
||||
_orderings3 = 32;
|
||||
_search_rounds = 32;
|
||||
_power_iterations = 6;
|
||||
break;
|
||||
case 15:
|
||||
two_ls_passes = true;
|
||||
two_ep_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Full;
|
||||
_orderings4 = 56;
|
||||
_orderings3 = 32;
|
||||
_search_rounds = 32;
|
||||
_power_iterations = 6;
|
||||
break;
|
||||
case 16:
|
||||
two_ls_passes = true;
|
||||
two_ep_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Full;
|
||||
_orderings4 = 80;
|
||||
_orderings3 = 32;
|
||||
_search_rounds = 256;
|
||||
_power_iterations = 6;
|
||||
|
||||
break;
|
||||
case 17:
|
||||
two_ls_passes = true;
|
||||
two_ep_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Full;
|
||||
_orderings4 = 128;
|
||||
_orderings3 = 32;
|
||||
_search_rounds = 256;
|
||||
break;
|
||||
case 18:
|
||||
two_ls_passes = true;
|
||||
two_ep_passes = true;
|
||||
two_cf_passes = true;
|
||||
|
||||
_error_mode = ErrorMode::Full;
|
||||
_orderings4 = 128;
|
||||
_orderings3 = 32;
|
||||
_search_rounds = 256;
|
||||
_power_iterations = 6;
|
||||
break;
|
||||
|
||||
case 19:
|
||||
// This hidden mode is *extremely* slow and abuses the encoder. It's just for testing/training.
|
||||
|
||||
two_ls_passes = true;
|
||||
two_ep_passes = true;
|
||||
two_cf_passes = true;
|
||||
exhaustive = true;
|
||||
|
||||
_error_mode = ErrorMode::Full;
|
||||
_orderings4 = 128;
|
||||
_orderings3 = 32;
|
||||
_search_rounds = 256;
|
||||
_power_iterations = 6;
|
||||
break;
|
||||
}
|
||||
|
||||
_orderings4 = clamp(_orderings4, 1U, OrderTable<4>::BestOrderCount);
|
||||
_orderings3 = clamp(_orderings3, 1U, OrderTable<3>::BestOrderCount);
|
||||
}
|
||||
|
||||
void BC1Encoder::SetOrderings4(unsigned orderings4) { _orderings4 = clamp(orderings4, 1U, OrderTable<4>::BestOrderCount); }
|
||||
void BC1Encoder::SetOrderings3(unsigned orderings3) { _orderings3 = clamp(orderings3, 1U, OrderTable<3>::BestOrderCount); }
|
||||
void BC1Encoder::SetOrderings(OrderingPair orderings) {
|
||||
SetOrderings4(std::get<0>(orderings));
|
||||
SetOrderings3(std::get<1>(orderings));
|
||||
}
|
||||
|
||||
void BC1Encoder::SetPowerIterations(unsigned int power_iters) { _power_iterations = clamp(power_iters, min_power_iterations, max_power_iterations); }
|
||||
|
||||
// Public methods
|
||||
BC1Block BC1Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const {
|
||||
if (pixels.IsSingleColor()) {
|
||||
// single-color pixel block, do it the fast way
|
||||
return WriteBlockSolid(pixels.Get(0, 0));
|
||||
}
|
||||
|
||||
auto metrics = pixels.GetMetrics();
|
||||
|
||||
const bool use_likely_orderings = (exhaustive || _orderings3 > 0 || _orderings4 > 0);
|
||||
|
||||
bool needs_block_error = use_likely_orderings;
|
||||
needs_block_error |= (_color_mode == ColorMode::ThreeColor);
|
||||
needs_block_error |= (_color_mode == ColorMode::ThreeColorBlack) && metrics.has_black;
|
||||
needs_block_error |= (_error_mode != ErrorMode::None);
|
||||
needs_block_error |= (_search_rounds > 0);
|
||||
ErrorMode error_mode = needs_block_error ? _error_mode : ErrorMode::None;
|
||||
|
||||
assert(!((_error_mode == ErrorMode::None) && needs_block_error));
|
||||
|
||||
const unsigned total_ls_passes = two_ls_passes ? 2 : 1;
|
||||
const unsigned total_cf_passes = two_cf_passes ? 2 : 1;
|
||||
const unsigned total_ep_passes = (needs_block_error && two_ep_passes) ? 2 : 1;
|
||||
|
||||
// Initial block generation
|
||||
EncodeResults orig;
|
||||
EncodeResults result;
|
||||
for (unsigned round = 0; round < total_ep_passes; round++) {
|
||||
EndpointMode endpoint_mode = (round == 1) ? EndpointMode::BoundingBox : _endpoint_mode;
|
||||
|
||||
EncodeResults trial_orig;
|
||||
FindEndpoints(trial_orig, pixels, metrics, endpoint_mode);
|
||||
|
||||
EncodeResults trial_result = trial_orig;
|
||||
|
||||
FindSelectors<ColorMode::FourColor>(trial_result, pixels, error_mode);
|
||||
RefineBlockLS<ColorMode::FourColor>(trial_result, pixels, metrics, error_mode, total_ls_passes);
|
||||
|
||||
if (!needs_block_error || trial_result.error < result.error) {
|
||||
result = trial_result;
|
||||
orig = trial_orig;
|
||||
}
|
||||
}
|
||||
|
||||
// First refinement pass using ordered cluster fit
|
||||
if (result.error > 0 && use_likely_orderings) {
|
||||
for (unsigned iter = 0; iter < total_cf_passes; iter++) { RefineBlockCF<ColorMode::FourColor>(result, pixels, metrics, _error_mode, _orderings4); }
|
||||
}
|
||||
|
||||
// try for 3-color block
|
||||
if (result.error > 0 && (bool)(_color_mode & ColorMode::ThreeColor)) {
|
||||
EncodeResults trial_result = orig;
|
||||
|
||||
FindSelectors<ColorMode::ThreeColor>(trial_result, pixels, ErrorMode::Full);
|
||||
RefineBlockLS<ColorMode::ThreeColor>(trial_result, pixels, metrics, ErrorMode::Full, total_ls_passes);
|
||||
|
||||
// First refinement pass using ordered cluster fit
|
||||
if (trial_result.error > 0 && use_likely_orderings) {
|
||||
for (unsigned iter = 0; iter < total_cf_passes; iter++) {
|
||||
RefineBlockCF<ColorMode::ThreeColor>(trial_result, pixels, metrics, ErrorMode::Full, _orderings3);
|
||||
}
|
||||
}
|
||||
|
||||
if (trial_result.error < result.error) { result = trial_result; }
|
||||
}
|
||||
|
||||
// try for 3-color block with black
|
||||
if (result.error > 0 && (_color_mode == ColorMode::ThreeColorBlack) && metrics.has_black && !metrics.max.IsBlack()) {
|
||||
EncodeResults trial_result;
|
||||
BlockMetrics metrics_no_black = pixels.GetMetrics(true);
|
||||
|
||||
FindEndpoints(trial_result, pixels, metrics_no_black, EndpointMode::PCA, true);
|
||||
FindSelectors<ColorMode::ThreeColorBlack>(trial_result, pixels, ErrorMode::Full);
|
||||
RefineBlockLS<ColorMode::ThreeColorBlack>(trial_result, pixels, metrics_no_black, ErrorMode::Full, total_ls_passes);
|
||||
|
||||
if (trial_result.error < result.error) { result = trial_result; }
|
||||
}
|
||||
|
||||
// refine endpoints by searching for nearby colors
|
||||
if (result.error > 0 && _search_rounds > 0) { EndpointSearch(result, pixels); }
|
||||
|
||||
return WriteBlock(result);
|
||||
}
|
||||
|
||||
// Private methods
|
||||
BC1Block BC1Encoder::WriteBlockSolid(Color color) const {
|
||||
uint8_t mask = 0xAA; // 2222
|
||||
uint16_t min16, max16;
|
||||
|
||||
if ((color.r | color.g | color.b) == 0) {
|
||||
// quick shortcut for all-black blocks
|
||||
min16 = 0;
|
||||
max16 = 1;
|
||||
mask = 0x55; // 1111 (Min value only, max is ignored)
|
||||
} else {
|
||||
// why is there no subscript operator for shared_ptr<array>
|
||||
EncodeResults result;
|
||||
FindEndpointsSingleColor(result, color, false);
|
||||
|
||||
if ((bool)(_color_mode & ColorMode::ThreeColor)) {
|
||||
EncodeResults result_3color;
|
||||
FindEndpointsSingleColor(result_3color, color, true);
|
||||
|
||||
if (result_3color.error < result.error) { result = result_3color; }
|
||||
}
|
||||
|
||||
min16 = result.low.Pack565Unscaled();
|
||||
max16 = result.high.Pack565Unscaled();
|
||||
|
||||
if (result.color_mode == ColorMode::FourColor) {
|
||||
if (min16 == max16) {
|
||||
// make sure this isnt accidentally a 3-color block
|
||||
// so make max16 > min16 (l > h)
|
||||
if (min16 > 0) {
|
||||
min16--;
|
||||
mask = 0; // endpoints are equal so mask doesnt matter
|
||||
} else {
|
||||
assert(min16 == 0 && max16 == 0);
|
||||
max16 = 1;
|
||||
min16 = 0;
|
||||
mask = 0x55; // 1111 (Min value only, max is ignored)
|
||||
}
|
||||
} else if (max16 < min16) {
|
||||
std::swap(min16, max16);
|
||||
mask = 0xFF; // invert mask to 3333
|
||||
}
|
||||
assert(max16 > min16);
|
||||
} else if (max16 > min16) {
|
||||
std::swap(min16, max16); // assure 3-color blocks
|
||||
}
|
||||
}
|
||||
|
||||
return BC1Block(max16, min16, mask);
|
||||
}
|
||||
|
||||
BC1Block BC1Encoder::WriteBlock(EncodeResults &result) const {
|
||||
BC1Block::SelectorArray selectors;
|
||||
uint16_t ep1 = result.low.Pack565Unscaled();
|
||||
uint16_t ep0 = result.high.Pack565Unscaled();
|
||||
std::array<uint8_t, 4> lut;
|
||||
|
||||
assert(result.color_mode != ColorMode::Incomplete);
|
||||
|
||||
if ((bool)(result.color_mode & ColorMode::FourColor)) {
|
||||
lut = {1, 3, 2, 0};
|
||||
|
||||
if (ep1 > ep0) {
|
||||
std::swap(ep1, ep0);
|
||||
lut = {0, 2, 3, 1};
|
||||
} else if (ep1 == ep0) {
|
||||
if (ep1 > 0) {
|
||||
ep1--;
|
||||
lut = {0, 0, 0, 0};
|
||||
} else {
|
||||
assert(ep1 == 0 && ep0 == 0);
|
||||
ep0 = 1;
|
||||
ep1 = 0;
|
||||
lut = {1, 1, 1, 1};
|
||||
}
|
||||
}
|
||||
|
||||
assert(ep0 > ep1);
|
||||
} else {
|
||||
lut = {1, 2, 0, 3};
|
||||
|
||||
if (ep1 < ep0) {
|
||||
std::swap(ep1, ep0);
|
||||
lut = {0, 2, 1, 3};
|
||||
}
|
||||
|
||||
assert(ep0 <= ep1);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
unsigned x = i % 4;
|
||||
unsigned y = i / 4;
|
||||
selectors[y][x] = lut[result.selectors[i]];
|
||||
if (result.color_mode == ColorMode::ThreeColor) { assert(selectors[y][x] != 3); }
|
||||
}
|
||||
|
||||
return BC1Block(ep0, ep1, selectors);
|
||||
}
|
||||
|
||||
void BC1Encoder::FindEndpointsSingleColor(EncodeResults &result, Color color, bool is_3color) const {
|
||||
auto &match5 = is_3color ? _single_match5_half : _single_match5;
|
||||
auto &match6 = is_3color ? _single_match6_half : _single_match6;
|
||||
|
||||
BC1MatchEntry match_r = match5->at(color.r);
|
||||
BC1MatchEntry match_g = match6->at(color.g);
|
||||
BC1MatchEntry match_b = match5->at(color.b);
|
||||
|
||||
result.color_mode = is_3color ? ColorMode::ThreeColor : ColorMode::FourColor;
|
||||
result.error = match_r.error + match_g.error + match_b.error;
|
||||
result.low = Color(match_r.low, match_g.low, match_b.low);
|
||||
result.high = Color(match_r.high, match_g.high, match_b.high);
|
||||
// selectors decided when writing, no point deciding them now
|
||||
}
|
||||
|
||||
void BC1Encoder::FindEndpointsSingleColor(EncodeResults &result, const CBlock &pixels, Color color, bool is_3color) const {
|
||||
std::array<Color, 4> colors = _interpolator->InterpolateBC1(result.low, result.high, is_3color);
|
||||
Vector4Int result_vector = (Vector4Int)colors[2];
|
||||
|
||||
FindEndpointsSingleColor(result, color, is_3color);
|
||||
|
||||
result.error = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
Vector4Int pixel_vector = (Vector4Int)pixels.Get(i);
|
||||
auto diff = pixel_vector - result_vector;
|
||||
result.error += diff.SqrMag();
|
||||
result.selectors[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void BC1Encoder::FindEndpoints(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, EndpointMode endpoint_mode, bool ignore_black) const {
|
||||
if (metrics.is_greyscale) {
|
||||
// specialized greyscale case
|
||||
const unsigned fr = pixels.Get(0, 0).r;
|
||||
|
||||
if (metrics.max.r - metrics.min.r < 2) {
|
||||
// single color block
|
||||
uint8_t fr5 = (uint8_t)scale8To5(fr);
|
||||
uint8_t fr6 = (uint8_t)scale8To6(fr);
|
||||
|
||||
result.low = Color(fr5, fr6, fr5);
|
||||
result.high = result.low;
|
||||
} else {
|
||||
uint8_t lr5 = scale8To5(metrics.min.r);
|
||||
uint8_t lr6 = scale8To6(metrics.min.r);
|
||||
|
||||
uint8_t hr5 = scale8To5(metrics.max.r);
|
||||
uint8_t hr6 = scale8To6(metrics.max.r);
|
||||
|
||||
result.low = Color(lr5, lr6, lr5);
|
||||
result.high = Color(hr5, hr6, hr5);
|
||||
}
|
||||
} else if (endpoint_mode == EndpointMode::LeastSquares) {
|
||||
// 2D Least Squares approach from Humus's example, with added inset and optimal rounding.
|
||||
Color diff = Color(metrics.max.r - metrics.min.r, metrics.max.g - metrics.min.g, metrics.max.b - metrics.min.b);
|
||||
Vector4 l = {0, 0, 0};
|
||||
Vector4 h = {0, 0, 0};
|
||||
|
||||
auto &sums = metrics.sums;
|
||||
auto &min = metrics.min;
|
||||
|
||||
unsigned chan0 = (unsigned)diff.MaxChannelRGB(); // primary axis of the bounding box
|
||||
l[chan0] = (float)min[chan0];
|
||||
h[chan0] = (float)min[chan0];
|
||||
|
||||
assert((diff[chan0] >= diff[(chan0 + 1) % 3]) && (diff[chan0] >= diff[(chan0 + 2) % 3]));
|
||||
|
||||
std::array<unsigned, 3> sums_xy;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
auto val = pixels.Get(i);
|
||||
for (unsigned c = 0; c < 3; c++) { sums_xy[c] += val[chan0] * val[c]; }
|
||||
}
|
||||
|
||||
const unsigned sum_x = (unsigned)sums[chan0];
|
||||
const unsigned sum_xx = sums_xy[chan0];
|
||||
|
||||
float denominator = (float)(16 * sum_xx) - (float)(sum_x * sum_x);
|
||||
|
||||
// once per secondary axis, calculate high and low using least squares
|
||||
if (fabs(denominator) > 1e-8f) {
|
||||
for (unsigned i = 1; i < 3; i++) {
|
||||
/* each secondary axis is fitted with a linear formula of the form
|
||||
* y = ax + b
|
||||
* where y is the secondary axis and x is the primary axis
|
||||
* a = (m∑xy - ∑x∑y) / m∑x² - (∑x)²
|
||||
* b = (∑x²∑y - ∑xy∑x) / m∑x² - (∑x)²
|
||||
* see Giordano/Weir pg.103 */
|
||||
const unsigned chan = (chan0 + i) % 3;
|
||||
const unsigned sum_y = (unsigned)sums[chan];
|
||||
const unsigned sum_xy = sums_xy[chan];
|
||||
|
||||
float a = (float)((16 * sum_xy) - (sum_x * sum_y)) / denominator;
|
||||
float b = (float)((sum_xx * sum_y) - (sum_xy * sum_x)) / denominator;
|
||||
|
||||
l[chan] = b + (a * l[chan0]);
|
||||
h[chan] = b + (a * h[chan0]);
|
||||
}
|
||||
}
|
||||
|
||||
// once per axis, inset towards the center by 1/16 of the delta and scale
|
||||
for (unsigned c = 0; c < 3; c++) {
|
||||
float inset = (h[c] - l[c]) / 16.0f;
|
||||
|
||||
l[c] = ((l[c] + inset) / 255.0f);
|
||||
h[c] = ((h[c] - inset) / 255.0f);
|
||||
}
|
||||
|
||||
result.low = Color::PreciseRound565(l);
|
||||
result.high = Color::PreciseRound565(h);
|
||||
} else if (endpoint_mode == EndpointMode::BoundingBox) {
|
||||
// Algorithm from icbc.h compress_dxt1_fast()
|
||||
Vector4 l, h;
|
||||
const float bias = 8.0f / 255.0f;
|
||||
|
||||
// rescale and inset values
|
||||
for (unsigned c = 0; c < 3; c++) { // heh, c++
|
||||
l[c] = (float)metrics.min[c] / 255.0f;
|
||||
h[c] = (float)metrics.max[c] / 255.0f;
|
||||
|
||||
float inset = (h[c] - l[c] - bias) / 16.0f;
|
||||
l[c] += inset;
|
||||
h[c] -= inset;
|
||||
}
|
||||
|
||||
// Select the correct diagonal across the bounding box
|
||||
int icov_xz = 0, icov_yz = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int b = (int)pixels.Get(i).b - metrics.avg.b;
|
||||
icov_xz += b * (int)pixels.Get(i).r - metrics.avg.r;
|
||||
icov_yz += b * (int)pixels.Get(i).g - metrics.avg.g;
|
||||
}
|
||||
|
||||
if (icov_xz < 0) std::swap(l[0], h[0]);
|
||||
if (icov_yz < 0) std::swap(l[1], h[1]);
|
||||
|
||||
result.low = Color::PreciseRound565(l);
|
||||
result.high = Color::PreciseRound565(h);
|
||||
} else if (endpoint_mode == EndpointMode::BoundingBoxInt) {
|
||||
// Algorithm from icbc.h compress_dxt1_fast(), but converted to integer.
|
||||
|
||||
Color min, max;
|
||||
|
||||
// rescale and inset values
|
||||
for (unsigned c = 0; c < 3; c++) {
|
||||
int inset = ((int)(metrics.max[c] - metrics.min[c]) - 8) >> 4; // 1/16 of delta, with bias
|
||||
|
||||
min[c] = clamp255(metrics.min[c] + inset);
|
||||
max[c] = clamp255(metrics.max[c] - inset);
|
||||
}
|
||||
|
||||
int icov_xz = 0, icov_yz = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int b = (int)pixels.Get(i).b - metrics.avg.b;
|
||||
icov_xz += b * (int)pixels.Get(i).r - metrics.avg.r;
|
||||
icov_yz += b * (int)pixels.Get(i).g - metrics.avg.g;
|
||||
}
|
||||
|
||||
if (icov_xz < 0) std::swap(min.r, max.r);
|
||||
if (icov_yz < 0) std::swap(min.g, max.g);
|
||||
|
||||
result.low = min.ScaleTo565();
|
||||
result.high = max.ScaleTo565();
|
||||
} else if (endpoint_mode == EndpointMode::PCA) {
|
||||
// the slow way
|
||||
// Select 2 colors along the principle axis. (There must be a faster/simpler way.)
|
||||
auto min = Vector4::FromColorRGB(metrics.min);
|
||||
auto max = Vector4::FromColorRGB(metrics.max);
|
||||
auto avg = Vector4::FromColorRGB(metrics.avg);
|
||||
|
||||
Vector4 axis = {306, 601, 117}; // Luma vector
|
||||
Matrix4x4 covariance = Matrix4x4::Identity();
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
auto val = pixels.Get(i);
|
||||
if (ignore_black && val.IsBlack()) continue;
|
||||
|
||||
auto color_vec = Vector4::FromColorRGB(val);
|
||||
Vector4 diff = color_vec - avg;
|
||||
for (unsigned c1 = 0; c1 < 3; c1++) {
|
||||
for (unsigned c2 = c1; c2 < 3; c2++) {
|
||||
covariance[c1][c2] += (diff[c1] * diff[c2]);
|
||||
assert(c1 <= c2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
covariance /= 255.0f;
|
||||
covariance.Mirror();
|
||||
|
||||
Vector4 delta = max - min;
|
||||
|
||||
// realign r and g axes to match
|
||||
if (covariance[0][2] < 0) delta[0] = -delta[0]; // r vs b
|
||||
if (covariance[1][2] < 0) delta[1] = -delta[1]; // g vs b
|
||||
|
||||
// using the covariance matrix, stretch the delta vector towards the primary axis of the data using power iteration
|
||||
// the end result of this may actually be the same as the least squares approach, will have to do more research
|
||||
for (unsigned power_iter = 0; power_iter < _power_iterations; power_iter++) { delta = covariance * delta; }
|
||||
|
||||
// if we found any correlation, then this is our new axis. otherwise we fallback to the luma vector
|
||||
float k = delta.MaxAbs(3);
|
||||
if (k >= 2) { axis = delta * (2048.0f / k); }
|
||||
|
||||
axis *= 16;
|
||||
|
||||
float min_dot = INFINITY;
|
||||
float max_dot = -INFINITY;
|
||||
|
||||
int min_index = 0, max_index = 0;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
auto val = pixels.Get(i);
|
||||
if (ignore_black && val.IsBlack()) continue;
|
||||
|
||||
auto color_vec = Vector4::FromColorRGB(val);
|
||||
// since axis is constant here, I dont think its magnitude actually matters,
|
||||
// since we only care about the min or max dot product
|
||||
float dot = color_vec.Dot(axis);
|
||||
if (dot > max_dot) {
|
||||
max_dot = dot;
|
||||
max_index = i;
|
||||
}
|
||||
if (dot < min_dot) {
|
||||
min_dot = dot;
|
||||
min_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
result.low = pixels.Get(min_index).ScaleTo565();
|
||||
result.high = pixels.Get(max_index).ScaleTo565();
|
||||
}
|
||||
|
||||
result.color_mode = ColorMode::Incomplete;
|
||||
}
|
||||
|
||||
template <BC1Encoder::ColorMode M> void BC1Encoder::FindSelectors(EncodeResults &result, const CBlock &pixels, ErrorMode error_mode) const {
|
||||
assert(!((error_mode != ErrorMode::Full) && (bool)(M & ColorMode::ThreeColor)));
|
||||
|
||||
const int color_count = (unsigned)M & 0x0F;
|
||||
|
||||
std::array<Color, 4> colors = _interpolator->InterpolateBC1(result.low, result.high, color_count == 3);
|
||||
std::array<Vector4Int, 4> color_vectors;
|
||||
|
||||
if (color_count == 4) {
|
||||
color_vectors = {Vector4Int::FromColorRGB(colors[0]), Vector4Int::FromColorRGB(colors[2]), Vector4Int::FromColorRGB(colors[3]),
|
||||
Vector4Int::FromColorRGB(colors[1])};
|
||||
} else {
|
||||
color_vectors = {Vector4Int::FromColorRGB(colors[0]), Vector4Int::FromColorRGB(colors[2]), Vector4Int::FromColorRGB(colors[1]),
|
||||
Vector4Int::FromColorRGB(colors[3])};
|
||||
}
|
||||
|
||||
unsigned total_error = 0;
|
||||
|
||||
if (error_mode == ErrorMode::None || error_mode == ErrorMode::Faster) {
|
||||
Vector4Int axis = color_vectors[3] - color_vectors[0];
|
||||
std::array<int, 4> dots;
|
||||
for (int i = 0; i < 4; i++) { dots[i] = axis.Dot(color_vectors[i]); }
|
||||
int t0 = dots[0] + dots[1], t1 = dots[1] + dots[2], t2 = dots[2] + dots[3];
|
||||
axis *= 2;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
Vector4Int pixel_vector = Vector4Int::FromColorRGB(pixels.Get(i));
|
||||
int dot = axis.Dot(pixel_vector);
|
||||
uint8_t level = (dot <= t0) + (dot < t1) + (dot < t2);
|
||||
uint8_t selector = 3 - level;
|
||||
assert(level < 4);
|
||||
assert(selector < 4);
|
||||
|
||||
if (error_mode == ErrorMode::Faster) {
|
||||
// llvm is just going to unswitch this anyways so its not an issue
|
||||
auto diff = pixel_vector - color_vectors[selector];
|
||||
total_error += diff.SqrMag();
|
||||
if (i % 4 != 0 && total_error >= result.error) break; // check only once per row if we're generating too much error
|
||||
}
|
||||
|
||||
result.selectors[i] = selector;
|
||||
}
|
||||
} else if (error_mode == ErrorMode::Check2) {
|
||||
Vector4Int axis = color_vectors[3] - color_vectors[0];
|
||||
const float f = 4.0f / ((float)axis.SqrMag() + .00000125f);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
Vector4Int pixel_vector = Vector4Int::FromColorRGB(pixels.Get(i));
|
||||
auto diff = pixel_vector - color_vectors[0];
|
||||
float sel_f = (float)diff.Dot(axis) * f + 0.5f;
|
||||
uint8_t sel = (uint8_t)clampi((int)sel_f, 1, 3);
|
||||
|
||||
unsigned err0 = (color_vectors[sel - 1] - pixel_vector).SqrMag();
|
||||
unsigned err1 = (color_vectors[sel] - pixel_vector).SqrMag();
|
||||
|
||||
uint8_t best_sel = sel;
|
||||
unsigned best_err = err1;
|
||||
if (err0 == err1) {
|
||||
// prefer non-interpolation
|
||||
if ((best_sel) == 1) best_sel = 0;
|
||||
} else if (err0 < best_err) {
|
||||
best_sel = sel - 1;
|
||||
best_err = err0;
|
||||
}
|
||||
|
||||
total_error += best_err;
|
||||
|
||||
if (total_error >= result.error) break;
|
||||
|
||||
result.selectors[i] = best_sel;
|
||||
}
|
||||
} else if (error_mode == ErrorMode::Full) {
|
||||
unsigned max_sel = (bool)(M == ColorMode::ThreeColor) ? 3 : 4;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
unsigned best_error = UINT_MAX;
|
||||
uint8_t best_sel = 0;
|
||||
Vector4Int pixel_vector = Vector4Int::FromColorRGB(pixels.Get(i));
|
||||
|
||||
// exhasustively check every pixel's distance from each color, and calculate the error
|
||||
for (uint8_t j = 0; j < max_sel; j++) {
|
||||
auto diff = color_vectors[j] - pixel_vector;
|
||||
unsigned err = diff.SqrMag();
|
||||
if (err < best_error || ((err == best_error) && (j == 3))) {
|
||||
best_error = err;
|
||||
best_sel = j;
|
||||
}
|
||||
}
|
||||
|
||||
total_error += best_error;
|
||||
if (total_error >= result.error) { break; }
|
||||
|
||||
assert(best_sel < max_sel);
|
||||
result.selectors[i] = best_sel;
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
result.error = total_error;
|
||||
result.color_mode = M;
|
||||
}
|
||||
|
||||
template <BC1Encoder::ColorMode M> bool BC1Encoder::RefineEndpointsLS(EncodeResults &result, const CBlock &pixels, BlockMetrics metrics) const {
|
||||
const int color_count = (unsigned)M & 0x0F;
|
||||
static_assert(color_count == 3 || color_count == 4);
|
||||
assert(result.color_mode != ColorMode::Incomplete);
|
||||
|
||||
int denominator = color_count - 1;
|
||||
|
||||
Vector4 q00 = {0, 0, 0};
|
||||
Vector4 matrix = Vector4(0);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
const Color color = pixels.Get(i);
|
||||
const uint8_t sel = result.selectors[i];
|
||||
|
||||
if ((bool)(M & ColorMode::ThreeColorBlack) && color.IsBlack()) continue;
|
||||
if ((bool)(M & ColorMode::ThreeColor) && sel == 3U) continue; // NOTE: selectors for 3-color are in linear order here, but not in original
|
||||
assert(sel < color_count);
|
||||
|
||||
const Vector4Int color_vector = Vector4Int::FromColorRGB(color);
|
||||
q00 += color_vector * sel;
|
||||
|
||||
matrix += OrderTable<color_count>::Weights[sel];
|
||||
}
|
||||
|
||||
// invert matrix
|
||||
float det = matrix.Determinant2x2(); // z00 * z11 - z01 * z10;
|
||||
if (fabs(det) < 1e-8f) {
|
||||
result.color_mode = ColorMode::Incomplete;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::swap(matrix[0], matrix[3]);
|
||||
matrix *= Vector4(1, -1, -1, 1);
|
||||
matrix *= ((float)denominator / 255.0f) / det;
|
||||
|
||||
Vector4 q10 = (metrics.sums * denominator) - q00;
|
||||
|
||||
Vector4 low = (matrix[0] * q00) + (matrix[1] * q10);
|
||||
Vector4 high = (matrix[2] * q00) + (matrix[3] * q10);
|
||||
|
||||
result.color_mode = M;
|
||||
result.low = Color::PreciseRound565(low);
|
||||
result.high = Color::PreciseRound565(high);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <BC1Encoder::ColorMode M> void BC1Encoder::RefineEndpointsLS(EncodeResults &result, std::array<Vector4, 17> &sums, Vector4 &matrix, Hash hash) const {
|
||||
const int color_count = (unsigned)M & 0x0F;
|
||||
static_assert(color_count == 3 || color_count == 4);
|
||||
assert(result.color_mode != ColorMode::Incomplete);
|
||||
|
||||
int denominator = color_count - 1;
|
||||
|
||||
Vector4 q10 = {0, 0, 0};
|
||||
unsigned level = 0;
|
||||
Histogram<color_count> h = OrderTable<color_count>::Orders[hash];
|
||||
for (unsigned i = 0; i < (color_count - 1); i++) {
|
||||
level += h[i];
|
||||
q10 += sums[level];
|
||||
}
|
||||
|
||||
Vector4 q00 = (sums[16] * (float)denominator) - q10;
|
||||
|
||||
Vector4 low = (matrix[0] * q00) + (matrix[1] * q10);
|
||||
Vector4 high = (matrix[2] * q00) + (matrix[3] * q10);
|
||||
|
||||
result.color_mode = M;
|
||||
result.low = Color::PreciseRound565(low);
|
||||
result.high = Color::PreciseRound565(high);
|
||||
}
|
||||
|
||||
template <BC1Encoder::ColorMode M>
|
||||
void BC1Encoder::RefineBlockLS(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, ErrorMode error_mode, unsigned passes) const {
|
||||
assert(error_mode != ErrorMode::None || passes == 1);
|
||||
|
||||
for (unsigned pass = 0; pass < passes; pass++) {
|
||||
EncodeResults trial_result = result;
|
||||
Vector4 low, high;
|
||||
|
||||
bool multicolor = RefineEndpointsLS<ColorMode::FourColor>(trial_result, pixels, metrics);
|
||||
if (!multicolor) {
|
||||
FindEndpointsSingleColor(trial_result, pixels, metrics.avg, (M != ColorMode::FourColor));
|
||||
} else {
|
||||
FindSelectors<M>(trial_result, pixels, error_mode);
|
||||
}
|
||||
|
||||
if (trial_result.low == result.low && trial_result.high == result.high) break;
|
||||
|
||||
if (error_mode == ErrorMode::None || trial_result.error < result.error) {
|
||||
result = trial_result;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <BC1Encoder::ColorMode M>
|
||||
void BC1Encoder::RefineBlockCF(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const {
|
||||
const int color_count = (unsigned)M & 0x0F;
|
||||
static_assert(color_count == 3 || color_count == 4);
|
||||
assert(result.color_mode != ColorMode::Incomplete);
|
||||
|
||||
using OrderTable = OrderTable<color_count>;
|
||||
using Hist = Histogram<color_count>;
|
||||
|
||||
EncodeResults orig = result;
|
||||
Hist h = Hist(orig.selectors);
|
||||
|
||||
Hash start_hash = OrderTable::GetHash(h);
|
||||
|
||||
Vector4 axis = orig.high.ScaleFrom565() - orig.low.ScaleFrom565();
|
||||
std::array<Vector4, 16> color_vectors;
|
||||
std::array<uint32_t, 16> dots;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
color_vectors[(unsigned)i] = Vector4::FromColorRGB(pixels.Get(i));
|
||||
int dot = 0x1000000 + (int)color_vectors[(unsigned)i].Dot(axis);
|
||||
assert(dot >= 0);
|
||||
dots[(unsigned)i] = (uint32_t)(dot << 4) | i;
|
||||
}
|
||||
|
||||
std::sort(dots.begin(), dots.end());
|
||||
|
||||
// we now have a list of indices and their dot products along the primary axis
|
||||
std::array<Vector4, 17> sums;
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
const unsigned p = dots[i] & 0xF;
|
||||
sums[i + 1] = sums[i] + color_vectors[p];
|
||||
}
|
||||
|
||||
const unsigned q_total = exhaustive ? OrderTable::OrderCount : orderings;
|
||||
for (Hash q = 0; q < q_total; q++) {
|
||||
Hash trial_hash = exhaustive ? q : OrderTable::BestOrders[start_hash][q];
|
||||
Vector4 trial_matrix = OrderTable::GetFactors(trial_hash);
|
||||
|
||||
EncodeResults trial_result = orig;
|
||||
Vector4 low, high;
|
||||
if (OrderTable::IsSingleColor(trial_hash)) {
|
||||
FindEndpointsSingleColor(trial_result, pixels, metrics.avg, (color_count == 3));
|
||||
} else {
|
||||
RefineEndpointsLS<M>(trial_result, sums, trial_matrix, trial_hash);
|
||||
FindSelectors<M>(trial_result, pixels, error_mode);
|
||||
}
|
||||
|
||||
if (trial_result.error < result.error) { result = trial_result; }
|
||||
if (trial_result.error == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
void BC1Encoder::EndpointSearch(EncodeResults &result, const CBlock &pixels) const {
|
||||
if (result.solid) return;
|
||||
|
||||
static const std::array<Vector4Int, 16> Voxels = {{
|
||||
{1, 0, 0, 3}, // 0
|
||||
{0, 1, 0, 4}, // 1
|
||||
{0, 0, 1, 5}, // 2
|
||||
{-1, 0, 0, 0}, // 3
|
||||
{0, -1, 0, 1}, // 4
|
||||
{0, 0, -1, 2}, // 5
|
||||
{1, 1, 0, 9}, // 6
|
||||
{1, 0, 1, 10}, // 7
|
||||
{0, 1, 1, 11}, // 8
|
||||
{-1, -1, 0, 6}, // 9
|
||||
{-1, 0, -1, 7}, // 10
|
||||
{0, -1, -1, 8}, // 11
|
||||
{-1, 1, 0, 13}, // 12
|
||||
{1, -1, 0, 12}, // 13
|
||||
{0, -1, 1, 15}, // 14
|
||||
{0, 1, -1, 14}, // 15
|
||||
}};
|
||||
|
||||
unsigned prev_improvement_index = 0;
|
||||
int forbidden_direction = -1;
|
||||
|
||||
for (unsigned i = 0; i < _search_rounds; i++) {
|
||||
const unsigned voxel_index = (unsigned)(i & 15);
|
||||
assert((unsigned)Voxels[(unsigned)Voxels[voxel_index][3]][3] == voxel_index); // make sure voxels are symmetrical
|
||||
|
||||
if ((int)(i & 31) == forbidden_direction) continue;
|
||||
|
||||
Vector4Int delta = Voxels[voxel_index];
|
||||
EncodeResults trial_result = result;
|
||||
|
||||
if (i & 16) {
|
||||
trial_result.low.r = (uint8_t)clamp(trial_result.low.r + delta[0], 0, 31);
|
||||
trial_result.low.g = (uint8_t)clamp(trial_result.low.g + delta[1], 0, 63);
|
||||
trial_result.low.b = (uint8_t)clamp(trial_result.low.b + delta[2], 0, 31);
|
||||
} else {
|
||||
trial_result.high.r = (uint8_t)clamp(trial_result.high.r + delta[0], 0, 31);
|
||||
trial_result.high.g = (uint8_t)clamp(trial_result.high.g + delta[1], 0, 63);
|
||||
trial_result.high.b = (uint8_t)clamp(trial_result.high.b + delta[2], 0, 31);
|
||||
}
|
||||
|
||||
switch (result.color_mode) {
|
||||
default:
|
||||
case ColorMode::FourColor:
|
||||
FindSelectors<ColorMode::FourColor>(trial_result, pixels, _error_mode);
|
||||
break;
|
||||
case ColorMode::ThreeColor:
|
||||
FindSelectors<ColorMode::ThreeColor>(trial_result, pixels, ErrorMode::Full);
|
||||
break;
|
||||
case ColorMode::ThreeColorBlack:
|
||||
FindSelectors<ColorMode::ThreeColorBlack>(trial_result, pixels, ErrorMode::Full);
|
||||
break;
|
||||
}
|
||||
|
||||
if (trial_result.error < result.error) {
|
||||
result = trial_result;
|
||||
|
||||
forbidden_direction = delta[3] | (int)(i & 16);
|
||||
prev_improvement_index = i;
|
||||
}
|
||||
|
||||
if (i - prev_improvement_index > 32) break;
|
||||
}
|
||||
}
|
||||
} // namespace quicktex::s3tc
|
193
quicktex/s3tc/bc1/BC1Encoder.h
Normal file
193
quicktex/s3tc/bc1/BC1Encoder.h
Normal file
@ -0,0 +1,193 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
#include "../../Color.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../../Encoder.h"
|
||||
#include "../../Texture.h"
|
||||
#include "../interpolator/Interpolator.h"
|
||||
#include "BC1Block.h"
|
||||
#include "SingleColorTable.h"
|
||||
|
||||
namespace quicktex {
|
||||
class Vector4;
|
||||
}
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class BC1Encoder final : public BlockEncoder<BlockTexture<BC1Block>> {
|
||||
public:
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
using OrderingPair = std::tuple<unsigned, unsigned>;
|
||||
using CBlock = ColorBlock<4, 4>;
|
||||
|
||||
static constexpr unsigned min_power_iterations = 4;
|
||||
static constexpr unsigned max_power_iterations = 10;
|
||||
|
||||
enum class ColorMode {
|
||||
// An incomplete block with invalid selectors or endpoints
|
||||
Incomplete = 0x00,
|
||||
|
||||
// A block where color0 <= color1
|
||||
ThreeColor = 0x03,
|
||||
|
||||
// A block where color0 > color1
|
||||
FourColor = 0x04,
|
||||
|
||||
// A 3 color block with black pixels (selector 3)
|
||||
UseBlack = 0x10,
|
||||
ThreeColorBlack = ThreeColor | UseBlack,
|
||||
};
|
||||
|
||||
enum class ErrorMode {
|
||||
// Perform no error checking at all.
|
||||
None,
|
||||
|
||||
// Use a slightly lower quality, but ~30% faster MSE evaluation function for 4-color blocks.
|
||||
Faster,
|
||||
|
||||
// Default error mode.
|
||||
Check2,
|
||||
|
||||
// Examine all colors to compute selectors/MSE (slower than default).
|
||||
Full
|
||||
};
|
||||
|
||||
enum class EndpointMode {
|
||||
// Use 2D least squares+inset+optimal rounding (the method used in Humus's GPU texture encoding demo), instead of PCA.
|
||||
// Around 18% faster, very slightly lower average quality to better (depends on the content).
|
||||
LeastSquares,
|
||||
|
||||
// BoundingBox enables a fast all-integer PCA approximation on 4-color blocks.
|
||||
// At level 0 options (no other flags), this is ~15% faster, and higher *average* quality.
|
||||
BoundingBox,
|
||||
|
||||
// Same as BoundingBox, but implemented using integer math (faster, slightly less quality)
|
||||
BoundingBoxInt,
|
||||
|
||||
// Full PCA implementation
|
||||
PCA
|
||||
};
|
||||
|
||||
bool exhaustive;
|
||||
bool two_ls_passes;
|
||||
bool two_ep_passes;
|
||||
bool two_cf_passes;
|
||||
|
||||
BC1Encoder(unsigned level, ColorMode color_mode, InterpolatorPtr interpolator);
|
||||
|
||||
BC1Encoder(unsigned int level = 5, ColorMode color_mode = ColorMode::FourColor) : BC1Encoder(level, color_mode, std::make_shared<Interpolator>()) {}
|
||||
|
||||
// Getters and Setters
|
||||
void SetLevel(unsigned level);
|
||||
|
||||
ErrorMode GetErrorMode() const { return _error_mode; }
|
||||
void SetErrorMode(ErrorMode error_mode) { _error_mode = error_mode; };
|
||||
|
||||
EndpointMode GetEndpointMode() const { return _endpoint_mode; }
|
||||
void SetEndpointMode(EndpointMode endpoint_mode) { _endpoint_mode = endpoint_mode; }
|
||||
|
||||
InterpolatorPtr GetInterpolator() const { return _interpolator; }
|
||||
ColorMode GetColorMode() const { return _color_mode; }
|
||||
|
||||
unsigned int GetSearchRounds() const { return _search_rounds; }
|
||||
void SetSearchRounds(unsigned search_rounds) { _search_rounds = search_rounds; }
|
||||
|
||||
unsigned GetOrderings4() const { return _orderings4; }
|
||||
unsigned GetOrderings3() const { return _orderings3; }
|
||||
|
||||
void SetOrderings4(unsigned orderings4);
|
||||
void SetOrderings3(unsigned orderings3);
|
||||
|
||||
OrderingPair GetOrderings() const { return OrderingPair(_orderings4, _orderings3); }
|
||||
void SetOrderings(OrderingPair orderings);
|
||||
|
||||
unsigned GetPowerIterations() const { return _power_iterations; }
|
||||
void SetPowerIterations(unsigned power_iters);
|
||||
|
||||
// Public Methods
|
||||
BC1Block EncodeBlock(const CBlock &pixels) const override;
|
||||
|
||||
virtual size_t MTThreshold() const override { return 16; }
|
||||
|
||||
private:
|
||||
using Hash = uint16_t;
|
||||
using BlockMetrics = CBlock::Metrics;
|
||||
|
||||
// Unpacked BC1 block with metadata
|
||||
struct EncodeResults {
|
||||
Color low;
|
||||
Color high;
|
||||
std::array<uint8_t, 16> selectors = {0};
|
||||
ColorMode color_mode = ColorMode::Incomplete;
|
||||
bool solid = false;
|
||||
unsigned error = UINT_MAX;
|
||||
};
|
||||
|
||||
const InterpolatorPtr _interpolator;
|
||||
const ColorMode _color_mode;
|
||||
|
||||
// match tables used for single-color blocks
|
||||
// Each entry includes a high and low pair that best reproduces the 8-bit index as well as possible,
|
||||
// with an included error value
|
||||
// these depend on the interpolator
|
||||
MatchListPtr _single_match5 = SingleColorTable<5, 4>(_interpolator);
|
||||
MatchListPtr _single_match6 = SingleColorTable<6, 4>(_interpolator);
|
||||
MatchListPtr _single_match5_half = SingleColorTable<5, 3>(_interpolator);
|
||||
MatchListPtr _single_match6_half = SingleColorTable<6, 3>(_interpolator);
|
||||
|
||||
ErrorMode _error_mode;
|
||||
EndpointMode _endpoint_mode;
|
||||
|
||||
unsigned _power_iterations;
|
||||
unsigned _search_rounds;
|
||||
unsigned _orderings4;
|
||||
unsigned _orderings3;
|
||||
|
||||
BC1Block WriteBlockSolid(Color color) const;
|
||||
BC1Block WriteBlock(EncodeResults &result) const;
|
||||
|
||||
void FindEndpoints(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, EndpointMode endpoint_mode, bool ignore_black = false) const;
|
||||
void FindEndpointsSingleColor(EncodeResults &result, Color color, bool is_3color = false) const;
|
||||
void FindEndpointsSingleColor(EncodeResults &result, const CBlock &pixels, Color color, bool is_3color) const;
|
||||
|
||||
template <ColorMode M> void FindSelectors(EncodeResults &result, const CBlock &pixels, ErrorMode error_mode) const;
|
||||
|
||||
template <ColorMode M> bool RefineEndpointsLS(EncodeResults &result, const CBlock &pixels, BlockMetrics metrics) const;
|
||||
|
||||
template <ColorMode M> void RefineEndpointsLS(EncodeResults &result, std::array<Vector4, 17> &sums, Vector4 &matrix, Hash hash) const;
|
||||
|
||||
template <ColorMode M>
|
||||
void RefineBlockLS(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, ErrorMode error_mode, unsigned passes) const;
|
||||
|
||||
template <ColorMode M>
|
||||
void RefineBlockCF(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const;
|
||||
|
||||
void EndpointSearch(EncodeResults &result, const CBlock &pixels) const;
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
85
quicktex/s3tc/bc1/Histogram.h
Normal file
85
quicktex/s3tc/bc1/Histogram.h
Normal file
@ -0,0 +1,85 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
|
||||
#include "../../Vector4.h"
|
||||
#include "../../util.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
template <size_t N> class Histogram {
|
||||
public:
|
||||
using Hash = uint16_t;
|
||||
|
||||
Histogram() { _bins.fill(0); }
|
||||
|
||||
Histogram(std::array<uint8_t, 16> sels) {
|
||||
_bins.fill(0);
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
assert(sels[i] < N);
|
||||
_bins[sels[i]]++;
|
||||
}
|
||||
}
|
||||
|
||||
Histogram(std::initializer_list<uint8_t> init) {
|
||||
assert(init.size() <= N);
|
||||
_bins.fill(0);
|
||||
auto item = init.begin();
|
||||
for (unsigned i = 0; i < init.size(); i++) {
|
||||
_bins[i] = *item;
|
||||
item++;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t operator[](size_t index) const {
|
||||
assert(index < N);
|
||||
return _bins[index];
|
||||
}
|
||||
uint8_t &operator[](size_t index) {
|
||||
assert(index < N);
|
||||
return _bins[index];
|
||||
}
|
||||
|
||||
bool Any16() {
|
||||
return std::any_of(_bins.begin(), _bins.end(), [](int i) { return i == 16; });
|
||||
}
|
||||
|
||||
unsigned GetPacked() const {
|
||||
Hash packed = 0;
|
||||
|
||||
for (unsigned i = 0; i < (N-1); i++) {
|
||||
assert(_bins[i] <= (1U << 4) - 1U);
|
||||
packed |= static_cast<uint16_t>(_bins[i]) << (i * 4U);
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, N> _bins;
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
327
quicktex/s3tc/bc1/OrderTable.cpp
Normal file
327
quicktex/s3tc/bc1/OrderTable.cpp
Normal file
@ -0,0 +1,327 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include "OrderTable.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "../../Vector4.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
using Hash = uint16_t;
|
||||
|
||||
template <> std::atomic<bool> OrderTable<3>::generated = false;
|
||||
template <> std::atomic<bool> OrderTable<4>::generated = false;
|
||||
|
||||
template <> std::mutex OrderTable<3>::table_mutex = std::mutex();
|
||||
template <> std::mutex OrderTable<4>::table_mutex = std::mutex();
|
||||
|
||||
template <> std::array<OrderTable<3>::Hash, OrderTable<3>::HashCount> *OrderTable<3>::hashes = nullptr;
|
||||
template <> std::array<OrderTable<4>::Hash, OrderTable<4>::HashCount> *OrderTable<4>::hashes = nullptr;
|
||||
|
||||
template <> std::array<Vector4, OrderTable<3>::OrderCount> *OrderTable<3>::factors = nullptr;
|
||||
template <> std::array<Vector4, OrderTable<4>::OrderCount> *OrderTable<4>::factors = nullptr;
|
||||
|
||||
template <> const std::array<Vector4, 3> OrderTable<3>::Weights = {{{0, 0, 0, 4}, {1, 1, 1, 1}, {4, 0, 0, 0}}};
|
||||
template <> const std::array<Vector4, 4> OrderTable<4>::Weights = {{{0, 0, 0, 9}, {1, 2, 2, 4}, {4, 2, 2, 1}, {9, 0, 0, 0}}};
|
||||
|
||||
template <> const std::array<Hash, 3> OrderTable<3>::SingleColorHashes = {12, 15, 89};
|
||||
template <> const std::array<Hash, 4> OrderTable<4>::SingleColorHashes = {15, 700, 753, 515};
|
||||
|
||||
// region OrderTable3
|
||||
template <>
|
||||
const OrderTable<3>::OrderArray OrderTable<3>::Orders = {
|
||||
{{6, 0, 10}, {3, 6, 7}, {3, 0, 13}, {13, 3, 0}, {12, 4, 0}, {9, 1, 6}, {2, 13, 1}, {4, 7, 5}, {7, 5, 4}, {9, 6, 1}, {7, 4, 5}, {8, 6, 2}, {16, 0, 0},
|
||||
{10, 6, 0}, {2, 7, 7}, {0, 0, 16}, {0, 3, 13}, {1, 15, 0}, {0, 2, 14}, {1, 4, 11}, {15, 1, 0}, {1, 12, 3}, {9, 2, 5}, {14, 1, 1}, {8, 2, 6}, {3, 3, 10},
|
||||
{4, 2, 10}, {14, 0, 2}, {0, 14, 2}, {1, 7, 8}, {6, 6, 4}, {11, 5, 0}, {6, 4, 6}, {11, 3, 2}, {4, 3, 9}, {7, 1, 8}, {10, 4, 2}, {12, 1, 3}, {11, 0, 5},
|
||||
{9, 3, 4}, {1, 0, 15}, {9, 0, 7}, {2, 6, 8}, {12, 2, 2}, {6, 2, 8}, {6, 8, 2}, {15, 0, 1}, {4, 8, 4}, {0, 4, 12}, {8, 5, 3}, {5, 9, 2}, {11, 2, 3},
|
||||
{12, 3, 1}, {6, 3, 7}, {1, 1, 14}, {2, 9, 5}, {1, 8, 7}, {4, 10, 2}, {7, 7, 2}, {13, 1, 2}, {0, 15, 1}, {3, 2, 11}, {7, 0, 9}, {4, 4, 8}, {3, 8, 5},
|
||||
{0, 5, 11}, {13, 2, 1}, {1, 10, 5}, {4, 11, 1}, {3, 10, 3}, {5, 10, 1}, {10, 2, 4}, {0, 6, 10}, {14, 2, 0}, {11, 4, 1}, {3, 12, 1}, {1, 13, 2}, {1, 5, 10},
|
||||
{5, 11, 0}, {12, 0, 4}, {8, 1, 7}, {6, 10, 0}, {3, 13, 0}, {7, 2, 7}, {0, 7, 9}, {5, 8, 3}, {0, 12, 4}, {11, 1, 4}, {13, 0, 3}, {0, 16, 0}, {5, 7, 4},
|
||||
{10, 3, 3}, {10, 0, 6}, {0, 13, 3}, {4, 6, 6}, {2, 8, 6}, {2, 5, 9}, {7, 8, 1}, {2, 1, 13}, {2, 0, 14}, {7, 3, 6}, {5, 1, 10}, {3, 11, 2}, {5, 4, 7},
|
||||
{8, 3, 5}, {10, 5, 1}, {6, 9, 1}, {1, 3, 12}, {4, 5, 7}, {2, 2, 12}, {4, 1, 11}, {0, 8, 8}, {4, 12, 0}, {6, 5, 5}, {8, 7, 1}, {5, 5, 6}, {3, 7, 6},
|
||||
{7, 9, 0}, {4, 9, 3}, {0, 10, 6}, {8, 0, 8}, {5, 3, 8}, {10, 1, 5}, {6, 1, 9}, {7, 6, 3}, {9, 5, 2}, {0, 1, 15}, {9, 7, 0}, {2, 14, 0}, {3, 4, 9},
|
||||
{8, 4, 4}, {9, 4, 3}, {0, 9, 7}, {1, 9, 6}, {3, 9, 4}, {5, 2, 9}, {2, 3, 11}, {5, 6, 5}, {1, 14, 1}, {6, 7, 3}, {2, 4, 10}, {2, 12, 2}, {8, 8, 0},
|
||||
{2, 10, 4}, {4, 0, 12}, {0, 11, 5}, {2, 11, 3}, {1, 11, 4}, {3, 5, 8}, {5, 0, 11}, {3, 1, 12}, {1, 2, 13}, {1, 6, 9}}};
|
||||
// endregion
|
||||
|
||||
// region OrderTable4
|
||||
template <>
|
||||
const OrderTable<4>::OrderArray OrderTable<4>::Orders = {
|
||||
{{0, 8, 2, 6}, {4, 3, 9, 0}, {4, 8, 1, 3}, {12, 0, 3, 1}, {11, 3, 2, 0}, {6, 4, 6, 0}, {7, 5, 0, 4}, {6, 0, 8, 2}, {1, 0, 0, 15}, {3, 0, 8, 5},
|
||||
{1, 1, 13, 1}, {13, 1, 2, 0}, {0, 14, 1, 1}, {0, 15, 1, 0}, {0, 13, 0, 3}, {16, 0, 0, 0}, {4, 3, 4, 5}, {8, 6, 0, 2}, {0, 10, 0, 6}, {10, 0, 4, 2},
|
||||
{7, 2, 1, 6}, {4, 7, 5, 0}, {1, 4, 7, 4}, {0, 14, 2, 0}, {2, 7, 2, 5}, {9, 0, 5, 2}, {9, 2, 2, 3}, {10, 0, 5, 1}, {2, 3, 7, 4}, {4, 9, 0, 3},
|
||||
{1, 5, 0, 10}, {1, 1, 6, 8}, {6, 6, 4, 0}, {11, 5, 0, 0}, {11, 2, 0, 3}, {4, 0, 10, 2}, {2, 3, 10, 1}, {1, 13, 1, 1}, {0, 14, 0, 2}, {2, 3, 3, 8},
|
||||
{12, 3, 1, 0}, {14, 0, 0, 2}, {9, 1, 3, 3}, {6, 4, 0, 6}, {1, 1, 5, 9}, {5, 9, 0, 2}, {2, 10, 1, 3}, {12, 0, 0, 4}, {4, 6, 6, 0}, {0, 6, 4, 6},
|
||||
{3, 7, 4, 2}, {0, 13, 3, 0}, {3, 10, 0, 3}, {10, 2, 1, 3}, {1, 12, 1, 2}, {2, 0, 13, 1}, {11, 0, 5, 0}, {12, 1, 3, 0}, {6, 4, 5, 1}, {10, 4, 2, 0},
|
||||
{3, 6, 1, 6}, {7, 3, 6, 0}, {10, 4, 0, 2}, {10, 0, 2, 4}, {0, 5, 9, 2}, {0, 9, 3, 4}, {6, 4, 2, 4}, {3, 4, 7, 2}, {3, 3, 5, 5}, {4, 2, 9, 1},
|
||||
{6, 2, 8, 0}, {3, 5, 3, 5}, {4, 10, 1, 1}, {10, 1, 3, 2}, {5, 7, 0, 4}, {5, 3, 7, 1}, {6, 8, 1, 1}, {8, 8, 0, 0}, {11, 1, 0, 4}, {14, 1, 0, 1},
|
||||
{9, 3, 2, 2}, {8, 2, 1, 5}, {0, 0, 2, 14}, {3, 3, 9, 1}, {10, 1, 5, 0}, {8, 3, 1, 4}, {1, 5, 8, 2}, {6, 1, 9, 0}, {3, 2, 1, 10}, {3, 11, 1, 1},
|
||||
{7, 6, 3, 0}, {9, 0, 3, 4}, {5, 2, 5, 4}, {0, 2, 3, 11}, {15, 0, 0, 1}, {0, 6, 6, 4}, {3, 4, 9, 0}, {4, 7, 0, 5}, {0, 4, 4, 8}, {0, 13, 2, 1},
|
||||
{2, 4, 1, 9}, {3, 2, 5, 6}, {10, 6, 0, 0}, {3, 5, 6, 2}, {8, 0, 4, 4}, {1, 3, 6, 6}, {7, 7, 0, 2}, {6, 1, 4, 5}, {0, 11, 1, 4}, {2, 2, 8, 4},
|
||||
{0, 1, 2, 13}, {15, 0, 1, 0}, {7, 2, 6, 1}, {8, 1, 7, 0}, {1, 8, 4, 3}, {2, 13, 1, 0}, {1, 0, 7, 8}, {14, 2, 0, 0}, {1, 8, 1, 6}, {9, 3, 3, 1},
|
||||
{0, 0, 7, 9}, {4, 4, 1, 7}, {9, 0, 6, 1}, {10, 2, 4, 0}, {1, 7, 3, 5}, {0, 3, 8, 5}, {5, 2, 4, 5}, {1, 2, 5, 8}, {0, 8, 7, 1}, {10, 3, 2, 1},
|
||||
{12, 0, 4, 0}, {2, 1, 4, 9}, {5, 2, 2, 7}, {1, 9, 3, 3}, {15, 1, 0, 0}, {6, 3, 4, 3}, {9, 5, 0, 2}, {1, 6, 9, 0}, {6, 6, 0, 4}, {13, 2, 1, 0},
|
||||
{5, 1, 8, 2}, {0, 5, 11, 0}, {7, 1, 0, 8}, {1, 2, 12, 1}, {0, 3, 3, 10}, {7, 4, 2, 3}, {5, 1, 4, 6}, {7, 0, 3, 6}, {3, 12, 0, 1}, {3, 4, 5, 4},
|
||||
{1, 10, 0, 5}, {7, 4, 3, 2}, {10, 5, 0, 1}, {13, 3, 0, 0}, {2, 5, 4, 5}, {3, 10, 1, 2}, {5, 1, 2, 8}, {14, 0, 1, 1}, {1, 5, 4, 6}, {1, 4, 5, 6},
|
||||
{2, 3, 11, 0}, {11, 0, 4, 1}, {11, 2, 2, 1}, {5, 3, 8, 0}, {1, 3, 10, 2}, {0, 1, 13, 2}, {3, 1, 4, 8}, {4, 2, 4, 6}, {1, 5, 6, 4}, {2, 1, 11, 2},
|
||||
{1, 2, 9, 4}, {4, 7, 3, 2}, {6, 2, 5, 3}, {7, 2, 2, 5}, {8, 1, 4, 3}, {3, 2, 8, 3}, {12, 1, 0, 3}, {7, 8, 1, 0}, {7, 0, 2, 7}, {5, 10, 0, 1},
|
||||
{0, 2, 14, 0}, {2, 9, 3, 2}, {7, 0, 0, 9}, {11, 1, 4, 0}, {10, 4, 1, 1}, {2, 2, 9, 3}, {5, 7, 2, 2}, {1, 3, 1, 11}, {13, 2, 0, 1}, {4, 2, 8, 2},
|
||||
{2, 3, 1, 10}, {4, 2, 5, 5}, {7, 0, 7, 2}, {10, 0, 0, 6}, {0, 8, 5, 3}, {4, 4, 0, 8}, {12, 4, 0, 0}, {0, 1, 14, 1}, {8, 0, 1, 7}, {5, 1, 5, 5},
|
||||
{11, 0, 3, 2}, {0, 4, 1, 11}, {0, 8, 8, 0}, {0, 2, 5, 9}, {7, 3, 2, 4}, {7, 8, 0, 1}, {1, 0, 3, 12}, {7, 4, 5, 0}, {1, 6, 7, 2}, {7, 6, 1, 2},
|
||||
{9, 6, 1, 0}, {12, 2, 0, 2}, {4, 1, 6, 5}, {4, 0, 1, 11}, {8, 4, 4, 0}, {13, 0, 1, 2}, {8, 6, 2, 0}, {4, 12, 0, 0}, {2, 7, 5, 2}, {2, 0, 5, 9},
|
||||
{5, 4, 5, 2}, {3, 8, 5, 0}, {7, 3, 3, 3}, {4, 4, 8, 0}, {2, 1, 3, 10}, {5, 0, 1, 10}, {6, 4, 3, 3}, {4, 9, 1, 2}, {1, 4, 0, 11}, {11, 3, 1, 1},
|
||||
{4, 0, 12, 0}, {13, 0, 0, 3}, {6, 1, 6, 3}, {9, 0, 4, 3}, {8, 0, 0, 8}, {8, 4, 0, 4}, {0, 12, 1, 3}, {0, 4, 10, 2}, {3, 4, 8, 1}, {1, 3, 8, 4},
|
||||
{9, 2, 5, 0}, {5, 7, 4, 0}, {1, 0, 11, 4}, {4, 10, 0, 2}, {1, 3, 12, 0}, {6, 9, 0, 1}, {5, 0, 9, 2}, {5, 9, 2, 0}, {13, 1, 0, 2}, {9, 3, 4, 0},
|
||||
{9, 4, 0, 3}, {3, 1, 12, 0}, {2, 4, 3, 7}, {1, 2, 13, 0}, {2, 2, 4, 8}, {6, 8, 0, 2}, {9, 2, 1, 4}, {9, 5, 1, 1}, {2, 0, 4, 10}, {5, 4, 0, 7},
|
||||
{0, 0, 6, 10}, {1, 2, 0, 13}, {4, 7, 2, 3}, {6, 5, 5, 0}, {3, 3, 1, 9}, {1, 6, 1, 8}, {12, 2, 1, 1}, {4, 4, 5, 3}, {1, 0, 6, 9}, {0, 6, 10, 0},
|
||||
{4, 8, 3, 1}, {4, 3, 2, 7}, {2, 1, 7, 6}, {1, 9, 1, 5}, {3, 1, 3, 9}, {8, 7, 1, 0}, {1, 2, 3, 10}, {14, 1, 1, 0}, {5, 4, 4, 3}, {3, 7, 0, 6},
|
||||
{7, 4, 1, 4}, {3, 7, 5, 1}, {1, 1, 0, 14}, {0, 10, 3, 3}, {0, 4, 3, 9}, {1, 7, 7, 1}, {2, 0, 10, 4}, {5, 8, 0, 3}, {6, 7, 3, 0}, {0, 8, 4, 4},
|
||||
{5, 7, 3, 1}, {7, 9, 0, 0}, {7, 6, 2, 1}, {0, 4, 5, 7}, {6, 3, 5, 2}, {1, 2, 1, 12}, {5, 2, 0, 9}, {8, 5, 0, 3}, {4, 6, 1, 5}, {1, 1, 7, 7},
|
||||
{10, 5, 1, 0}, {1, 2, 8, 5}, {1, 8, 2, 5}, {5, 1, 0, 10}, {6, 9, 1, 0}, {13, 0, 2, 1}, {8, 3, 5, 0}, {6, 3, 6, 1}, {2, 11, 3, 0}, {3, 7, 3, 3},
|
||||
{1, 5, 2, 8}, {7, 5, 2, 2}, {0, 6, 7, 3}, {13, 1, 1, 1}, {5, 3, 4, 4}, {7, 2, 7, 0}, {5, 8, 3, 0}, {3, 13, 0, 0}, {0, 7, 9, 0}, {8, 0, 3, 5},
|
||||
{1, 3, 7, 5}, {4, 0, 2, 10}, {12, 0, 1, 3}, {1, 7, 6, 2}, {3, 9, 0, 4}, {7, 2, 0, 7}, {0, 1, 7, 8}, {2, 1, 8, 5}, {0, 13, 1, 2}, {0, 8, 1, 7},
|
||||
{5, 0, 11, 0}, {5, 6, 2, 3}, {0, 3, 0, 13}, {2, 3, 4, 7}, {5, 6, 3, 2}, {4, 2, 10, 0}, {3, 3, 7, 3}, {7, 2, 5, 2}, {1, 1, 11, 3}, {12, 3, 0, 1},
|
||||
{5, 1, 1, 9}, {1, 15, 0, 0}, {9, 7, 0, 0}, {9, 1, 2, 4}, {0, 7, 3, 6}, {3, 0, 13, 0}, {3, 0, 11, 2}, {0, 6, 5, 5}, {8, 2, 2, 4}, {6, 10, 0, 0},
|
||||
{4, 8, 4, 0}, {0, 0, 3, 13}, {0, 4, 12, 0}, {7, 1, 6, 2}, {3, 5, 0, 8}, {8, 0, 6, 2}, {6, 2, 3, 5}, {2, 10, 0, 4}, {4, 11, 0, 1}, {6, 1, 5, 4},
|
||||
{5, 1, 3, 7}, {0, 11, 3, 2}, {4, 6, 0, 6}, {2, 6, 0, 8}, {3, 1, 7, 5}, {2, 14, 0, 0}, {2, 9, 2, 3}, {0, 3, 4, 9}, {11, 0, 1, 4}, {13, 0, 3, 0},
|
||||
{8, 3, 0, 5}, {0, 5, 3, 8}, {5, 11, 0, 0}, {0, 1, 4, 11}, {2, 1, 9, 4}, {3, 4, 4, 5}, {7, 1, 2, 6}, {12, 2, 2, 0}, {9, 4, 1, 2}, {6, 0, 2, 8},
|
||||
{4, 6, 2, 4}, {11, 2, 3, 0}, {3, 2, 2, 9}, {10, 3, 1, 2}, {1, 1, 2, 12}, {0, 5, 2, 9}, {0, 1, 11, 4}, {6, 2, 4, 4}, {2, 8, 2, 4}, {0, 9, 4, 3},
|
||||
{11, 0, 2, 3}, {0, 2, 11, 3}, {6, 0, 7, 3}, {0, 3, 6, 7}, {4, 5, 5, 2}, {1, 2, 6, 7}, {7, 5, 1, 3}, {9, 0, 2, 5}, {2, 6, 4, 4}, {4, 1, 9, 2},
|
||||
{4, 8, 2, 2}, {1, 12, 3, 0}, {0, 9, 6, 1}, {0, 10, 6, 0}, {3, 1, 5, 7}, {2, 13, 0, 1}, {2, 2, 1, 11}, {3, 6, 0, 7}, {5, 6, 5, 0}, {5, 5, 4, 2},
|
||||
{4, 0, 3, 9}, {3, 4, 1, 8}, {0, 11, 2, 3}, {2, 12, 1, 1}, {7, 1, 3, 5}, {7, 0, 9, 0}, {8, 0, 8, 0}, {1, 0, 2, 13}, {3, 3, 10, 0}, {2, 4, 4, 6},
|
||||
{2, 3, 8, 3}, {1, 10, 5, 0}, {7, 3, 0, 6}, {2, 9, 0, 5}, {1, 4, 6, 5}, {6, 6, 3, 1}, {5, 6, 0, 5}, {6, 3, 0, 7}, {3, 10, 2, 1}, {2, 5, 5, 4},
|
||||
{3, 8, 4, 1}, {1, 14, 0, 1}, {10, 3, 3, 0}, {3, 5, 7, 1}, {1, 1, 3, 11}, {2, 4, 0, 10}, {9, 3, 1, 3}, {5, 10, 1, 0}, {3, 0, 6, 7}, {3, 1, 9, 3},
|
||||
{11, 2, 1, 2}, {5, 3, 3, 5}, {0, 5, 1, 10}, {4, 1, 11, 0}, {10, 2, 0, 4}, {7, 6, 0, 3}, {2, 7, 0, 7}, {4, 2, 2, 8}, {6, 1, 7, 2}, {4, 9, 2, 1},
|
||||
{0, 0, 8, 8}, {3, 7, 2, 4}, {9, 6, 0, 1}, {0, 12, 4, 0}, {6, 7, 1, 2}, {0, 7, 2, 7}, {1, 0, 10, 5}, {0, 0, 14, 2}, {2, 7, 3, 4}, {5, 0, 0, 11},
|
||||
{7, 7, 1, 1}, {6, 2, 7, 1}, {4, 5, 3, 4}, {3, 5, 1, 7}, {5, 9, 1, 1}, {6, 2, 1, 7}, {3, 2, 0, 11}, {0, 11, 0, 5}, {3, 11, 2, 0}, {10, 1, 4, 1},
|
||||
{7, 0, 4, 5}, {11, 4, 0, 1}, {10, 3, 0, 3}, {0, 2, 4, 10}, {0, 15, 0, 1}, {0, 11, 5, 0}, {6, 7, 2, 1}, {1, 12, 2, 1}, {4, 1, 3, 8}, {1, 0, 13, 2},
|
||||
{1, 8, 5, 2}, {7, 0, 1, 8}, {3, 12, 1, 0}, {9, 2, 4, 1}, {1, 7, 4, 4}, {11, 4, 1, 0}, {4, 3, 8, 1}, {2, 8, 4, 2}, {1, 11, 3, 1}, {1, 1, 4, 10},
|
||||
{4, 10, 2, 0}, {8, 2, 5, 1}, {1, 0, 9, 6}, {5, 3, 2, 6}, {0, 9, 7, 0}, {10, 2, 2, 2}, {5, 8, 1, 2}, {8, 7, 0, 1}, {0, 3, 12, 1}, {1, 0, 1, 14},
|
||||
{4, 8, 0, 4}, {3, 8, 0, 5}, {4, 6, 5, 1}, {0, 9, 5, 2}, {10, 2, 3, 1}, {2, 3, 9, 2}, {1, 0, 12, 3}, {11, 3, 0, 2}, {4, 5, 2, 5}, {0, 2, 12, 2},
|
||||
{9, 1, 0, 6}, {9, 2, 0, 5}, {1, 2, 7, 6}, {4, 7, 4, 1}, {0, 12, 2, 2}, {0, 0, 0, 16}, {2, 8, 3, 3}, {3, 6, 2, 5}, {0, 6, 3, 7}, {7, 5, 4, 0},
|
||||
{3, 3, 3, 7}, {3, 3, 0, 10}, {5, 0, 6, 5}, {0, 0, 10, 6}, {8, 5, 3, 0}, {8, 1, 5, 2}, {6, 0, 9, 1}, {11, 1, 2, 2}, {2, 11, 2, 1}, {9, 5, 2, 0},
|
||||
{3, 0, 4, 9}, {2, 2, 12, 0}, {2, 6, 6, 2}, {2, 1, 13, 0}, {6, 0, 5, 5}, {2, 0, 14, 0}, {2, 11, 1, 2}, {4, 4, 7, 1}, {2, 0, 11, 3}, {3, 1, 1, 11},
|
||||
{2, 9, 4, 1}, {3, 7, 6, 0}, {14, 0, 2, 0}, {1, 10, 4, 1}, {8, 0, 7, 1}, {3, 6, 5, 2}, {0, 3, 11, 2}, {2, 5, 6, 3}, {11, 1, 3, 1}, {6, 5, 3, 2},
|
||||
{3, 8, 1, 4}, {0, 2, 7, 7}, {2, 10, 2, 2}, {1, 6, 2, 7}, {11, 0, 0, 5}, {12, 1, 1, 2}, {12, 1, 2, 1}, {0, 7, 1, 8}, {0, 3, 9, 4}, {0, 2, 1, 13},
|
||||
{7, 1, 4, 4}, {10, 1, 0, 5}, {4, 0, 8, 4}, {5, 2, 7, 2}, {0, 2, 0, 14}, {4, 3, 7, 2}, {2, 7, 1, 6}, {1, 2, 2, 11}, {6, 3, 3, 4}, {1, 14, 1, 0},
|
||||
{2, 4, 6, 4}, {5, 3, 6, 2}, {5, 3, 5, 3}, {8, 4, 1, 3}, {1, 3, 0, 12}, {3, 5, 2, 6}, {1, 8, 7, 0}, {0, 7, 4, 5}, {2, 1, 6, 7}, {4, 11, 1, 0},
|
||||
{7, 2, 4, 3}, {6, 1, 3, 6}, {4, 5, 4, 3}, {2, 11, 0, 3}, {1, 5, 7, 3}, {12, 0, 2, 2}, {5, 0, 4, 7}, {1, 13, 0, 2}, {7, 7, 2, 0}, {4, 1, 7, 4},
|
||||
{4, 5, 0, 7}, {5, 0, 5, 6}, {6, 5, 4, 1}, {2, 4, 2, 8}, {1, 10, 1, 4}, {6, 3, 1, 6}, {3, 3, 8, 2}, {0, 7, 7, 2}, {4, 4, 2, 6}, {1, 1, 8, 6},
|
||||
{1, 12, 0, 3}, {2, 1, 12, 1}, {1, 9, 2, 4}, {1, 11, 0, 4}, {2, 5, 2, 7}, {10, 0, 3, 3}, {4, 6, 3, 3}, {3, 7, 1, 5}, {1, 9, 0, 6}, {7, 1, 7, 1},
|
||||
{1, 6, 5, 4}, {9, 2, 3, 2}, {6, 2, 2, 6}, {2, 2, 2, 10}, {8, 3, 3, 2}, {0, 1, 8, 7}, {2, 0, 8, 6}, {0, 3, 1, 12}, {9, 4, 2, 1}, {9, 4, 3, 0},
|
||||
{6, 2, 6, 2}, {1, 8, 0, 7}, {5, 1, 10, 0}, {0, 5, 5, 6}, {8, 2, 4, 2}, {2, 3, 2, 9}, {6, 0, 3, 7}, {2, 2, 6, 6}, {2, 6, 2, 6}, {1, 13, 2, 0},
|
||||
{9, 3, 0, 4}, {7, 3, 5, 1}, {6, 5, 2, 3}, {5, 2, 6, 3}, {2, 0, 12, 2}, {5, 7, 1, 3}, {8, 1, 3, 4}, {3, 1, 10, 2}, {1, 0, 15, 0}, {0, 8, 0, 8},
|
||||
{5, 0, 7, 4}, {4, 4, 6, 2}, {0, 1, 0, 15}, {10, 0, 1, 5}, {7, 3, 4, 2}, {4, 9, 3, 0}, {2, 5, 7, 2}, {3, 4, 2, 7}, {8, 3, 2, 3}, {5, 1, 6, 4},
|
||||
{0, 10, 2, 4}, {6, 6, 1, 3}, {6, 0, 0, 10}, {4, 4, 3, 5}, {1, 3, 9, 3}, {7, 5, 3, 1}, {3, 0, 7, 6}, {1, 8, 6, 1}, {4, 3, 0, 9}, {3, 11, 0, 2},
|
||||
{6, 0, 6, 4}, {0, 1, 3, 12}, {0, 4, 2, 10}, {5, 5, 6, 0}, {4, 1, 4, 7}, {8, 1, 6, 1}, {5, 6, 4, 1}, {8, 4, 2, 2}, {4, 3, 1, 8}, {3, 0, 2, 11},
|
||||
{1, 11, 4, 0}, {0, 8, 3, 5}, {5, 1, 7, 3}, {7, 0, 8, 1}, {4, 3, 5, 4}, {4, 6, 4, 2}, {3, 2, 4, 7}, {1, 6, 3, 6}, {0, 7, 8, 1}, {3, 0, 1, 12},
|
||||
{9, 1, 4, 2}, {7, 4, 0, 5}, {1, 7, 0, 8}, {5, 4, 1, 6}, {9, 1, 5, 1}, {1, 1, 9, 5}, {4, 1, 1, 10}, {5, 3, 0, 8}, {2, 2, 5, 7}, {4, 0, 0, 12},
|
||||
{9, 0, 7, 0}, {3, 4, 0, 9}, {0, 2, 6, 8}, {8, 2, 0, 6}, {3, 2, 6, 5}, {4, 2, 6, 4}, {3, 6, 4, 3}, {2, 8, 6, 0}, {5, 0, 3, 8}, {0, 4, 0, 12},
|
||||
{0, 16, 0, 0}, {0, 9, 2, 5}, {4, 0, 11, 1}, {1, 6, 4, 5}, {0, 1, 6, 9}, {3, 4, 6, 3}, {3, 0, 10, 3}, {7, 0, 6, 3}, {1, 4, 9, 2}, {1, 5, 3, 7},
|
||||
{8, 5, 2, 1}, {0, 12, 0, 4}, {7, 2, 3, 4}, {0, 5, 6, 5}, {11, 1, 1, 3}, {6, 5, 0, 5}, {2, 1, 5, 8}, {1, 4, 11, 0}, {9, 1, 1, 5}, {0, 0, 13, 3},
|
||||
{5, 8, 2, 1}, {2, 12, 0, 2}, {3, 3, 6, 4}, {4, 1, 10, 1}, {4, 0, 5, 7}, {8, 1, 0, 7}, {5, 1, 9, 1}, {4, 3, 3, 6}, {0, 2, 2, 12}, {6, 3, 2, 5},
|
||||
{0, 0, 12, 4}, {1, 5, 1, 9}, {2, 6, 5, 3}, {3, 6, 3, 4}, {2, 12, 2, 0}, {1, 6, 8, 1}, {10, 1, 1, 4}, {1, 3, 4, 8}, {7, 4, 4, 1}, {1, 11, 1, 3},
|
||||
{1, 2, 10, 3}, {3, 9, 3, 1}, {8, 5, 1, 2}, {2, 10, 4, 0}, {4, 2, 0, 10}, {2, 7, 6, 1}, {8, 2, 3, 3}, {1, 5, 5, 5}, {3, 1, 0, 12}, {3, 10, 3, 0},
|
||||
{8, 0, 5, 3}, {0, 6, 8, 2}, {0, 3, 13, 0}, {0, 0, 16, 0}, {1, 9, 4, 2}, {4, 1, 8, 3}, {1, 6, 6, 3}, {0, 10, 5, 1}, {0, 1, 12, 3}, {4, 0, 6, 6},
|
||||
{3, 8, 3, 2}, {0, 5, 4, 7}, {1, 0, 14, 1}, {0, 4, 6, 6}, {3, 9, 1, 3}, {3, 5, 8, 0}, {3, 6, 6, 1}, {5, 4, 7, 0}, {3, 0, 12, 1}, {8, 6, 1, 1},
|
||||
{2, 9, 5, 0}, {6, 1, 1, 8}, {4, 1, 2, 9}, {3, 9, 4, 0}, {5, 2, 9, 0}, {0, 12, 3, 1}, {1, 4, 10, 1}, {4, 0, 7, 5}, {3, 1, 2, 10}, {5, 4, 2, 5},
|
||||
{5, 5, 5, 1}, {4, 2, 3, 7}, {1, 7, 5, 3}, {2, 8, 0, 6}, {8, 1, 2, 5}, {3, 8, 2, 3}, {6, 1, 2, 7}, {3, 9, 2, 2}, {9, 0, 0, 7}, {0, 8, 6, 2},
|
||||
{8, 4, 3, 1}, {0, 2, 8, 6}, {6, 5, 1, 4}, {2, 3, 5, 6}, {2, 10, 3, 1}, {0, 7, 0, 9}, {4, 2, 7, 3}, {2, 4, 8, 2}, {7, 1, 1, 7}, {2, 4, 7, 3},
|
||||
{2, 4, 10, 0}, {0, 1, 10, 5}, {4, 7, 1, 4}, {0, 10, 4, 2}, {9, 0, 1, 6}, {1, 9, 6, 0}, {3, 3, 4, 6}, {4, 5, 7, 0}, {5, 5, 2, 4}, {2, 8, 1, 5},
|
||||
{2, 3, 6, 5}, {0, 1, 1, 14}, {3, 2, 3, 8}, {10, 1, 2, 3}, {9, 1, 6, 0}, {3, 4, 3, 6}, {2, 2, 0, 12}, {0, 0, 9, 7}, {4, 0, 9, 3}, {7, 0, 5, 4},
|
||||
{4, 5, 6, 1}, {2, 5, 1, 8}, {2, 5, 9, 0}, {3, 5, 4, 4}, {1, 3, 11, 1}, {7, 1, 5, 3}, {3, 2, 7, 4}, {1, 4, 2, 9}, {1, 11, 2, 2}, {2, 2, 3, 9},
|
||||
{5, 0, 10, 1}, {3, 2, 11, 0}, {1, 10, 3, 2}, {8, 3, 4, 1}, {3, 6, 7, 0}, {0, 7, 5, 4}, {1, 3, 3, 9}, {2, 2, 10, 2}, {1, 9, 5, 1}, {0, 5, 0, 11},
|
||||
{3, 0, 3, 10}, {0, 4, 8, 4}, {2, 7, 7, 0}, {2, 0, 2, 12}, {1, 2, 11, 2}, {6, 3, 7, 0}, {0, 6, 2, 8}, {0, 10, 1, 5}, {0, 9, 0, 7}, {6, 4, 4, 2},
|
||||
{6, 0, 1, 9}, {1, 5, 10, 0}, {5, 4, 6, 1}, {5, 5, 3, 3}, {0, 0, 4, 12}, {0, 3, 2, 11}, {1, 4, 1, 10}, {3, 0, 9, 4}, {5, 5, 0, 6}, {1, 7, 8, 0},
|
||||
{2, 0, 3, 11}, {6, 4, 1, 5}, {10, 0, 6, 0}, {0, 6, 0, 10}, {0, 4, 11, 1}, {3, 1, 6, 6}, {2, 5, 8, 1}, {0, 2, 10, 4}, {3, 1, 11, 1}, {6, 6, 2, 2},
|
||||
{1, 1, 10, 4}, {2, 1, 2, 11}, {6, 1, 8, 1}, {0, 2, 13, 1}, {0, 7, 6, 3}, {6, 8, 2, 0}, {3, 0, 0, 13}, {4, 4, 4, 4}, {6, 2, 0, 8}, {7, 3, 1, 5},
|
||||
{0, 11, 4, 1}, {6, 7, 0, 3}, {2, 6, 3, 5}, {5, 2, 1, 8}, {7, 1, 8, 0}, {5, 5, 1, 5}, {1, 8, 3, 4}, {8, 2, 6, 0}, {6, 0, 10, 0}, {5, 6, 1, 4},
|
||||
{1, 4, 4, 7}, {2, 7, 4, 3}, {1, 4, 8, 3}, {5, 4, 3, 4}, {1, 10, 2, 3}, {2, 9, 1, 4}, {2, 2, 11, 1}, {2, 5, 0, 9}, {0, 0, 1, 15}, {0, 0, 11, 5},
|
||||
{0, 4, 7, 5}, {0, 1, 15, 0}, {2, 1, 0, 13}, {0, 3, 10, 3}, {8, 0, 2, 6}, {3, 3, 2, 8}, {3, 5, 5, 3}, {1, 7, 1, 7}, {1, 3, 2, 10}, {4, 0, 4, 8},
|
||||
{2, 0, 9, 5}, {1, 1, 1, 13}, {2, 2, 7, 5}, {2, 1, 10, 3}, {4, 2, 1, 9}, {4, 3, 6, 3}, {1, 3, 5, 7}, {2, 5, 3, 6}, {1, 0, 8, 7}, {5, 0, 2, 9},
|
||||
{2, 8, 5, 1}, {1, 6, 0, 9}, {0, 0, 5, 11}, {0, 4, 9, 3}, {2, 0, 7, 7}, {1, 7, 2, 6}, {2, 1, 1, 12}, {2, 4, 9, 1}, {0, 5, 7, 4}, {6, 0, 4, 6},
|
||||
{3, 2, 10, 1}, {0, 6, 1, 9}, {2, 6, 1, 7}, {0, 5, 8, 3}, {4, 1, 0, 11}, {1, 2, 4, 9}, {4, 1, 5, 6}, {6, 1, 0, 9}, {1, 4, 3, 8}, {4, 5, 1, 6},
|
||||
{1, 0, 5, 10}, {5, 3, 1, 7}, {0, 9, 1, 6}, {2, 0, 1, 13}, {2, 0, 6, 8}, {8, 1, 1, 6}, {1, 5, 9, 1}, {0, 6, 9, 1}, {0, 3, 5, 8}, {0, 2, 9, 5},
|
||||
{5, 2, 8, 1}, {1, 1, 14, 0}, {3, 2, 9, 2}, {5, 0, 8, 3}, {0, 5, 10, 1}, {5, 2, 3, 6}, {2, 6, 7, 1}, {2, 3, 0, 11}, {0, 1, 9, 6}, {1, 0, 4, 11},
|
||||
{3, 0, 5, 8}, {0, 0, 15, 1}, {2, 4, 5, 5}, {0, 3, 7, 6}, {2, 0, 0, 14}, {1, 1, 12, 2}, {2, 6, 8, 0}, {3, 1, 8, 4}, {0, 1, 5, 10}}};
|
||||
// endregion
|
||||
|
||||
// region BestOrderTable3
|
||||
template <>
|
||||
const OrderTable<3>::BestOrderArray OrderTable<3>::BestOrders = {
|
||||
{{12, 1, 3, 5, 27, 2, 4, 38, 8, 7, 16, 18, 6, 10, 41, 79, 40, 23, 46, 9, 20, 88, 22, 37, 14, 19, 24, 126, 99, 119, 35, 11},
|
||||
{7, 64, 116, 14, 94, 30, 8, 42, 1, 108, 47, 55, 137, 10, 134, 95, 96, 115, 69, 32, 63, 29, 90, 113, 11, 148, 16, 103, 19, 9, 34, 25},
|
||||
{12, 1, 0, 5, 3, 7, 4, 27, 8, 6, 38, 40, 41, 16, 18, 46, 9, 10, 20, 23, 79, 62, 14, 22, 88, 99, 37, 126, 92, 19, 120, 11},
|
||||
{16, 88, 27, 18, 46, 48, 126, 107, 79, 19, 59, 38, 37, 65, 23, 66, 0, 2, 3, 43, 12, 151, 28, 25, 5, 87, 72, 40, 1, 20, 52, 92},
|
||||
{79, 48, 88, 16, 27, 65, 18, 38, 46, 19, 37, 4, 72, 33, 126, 41, 52, 0, 12, 92, 5, 1, 2, 107, 3, 77, 23, 91, 43, 51, 22, 74},
|
||||
{1, 8, 41, 122, 10, 22, 2, 0, 87, 24, 37, 120, 38, 7, 39, 4, 5, 3, 9, 92, 62, 59, 23, 16, 104, 11, 27, 79, 19, 26, 25, 32},
|
||||
{2, 76, 99, 28, 40, 86, 93, 21, 138, 60, 6, 0, 17, 128, 145, 119, 98, 144, 141, 82, 147, 54, 67, 75, 5, 12, 27, 132, 146, 1, 38, 14},
|
||||
{47, 7, 64, 90, 1, 118, 116, 85, 57, 14, 30, 94, 50, 45, 137, 134, 8, 42, 69, 139, 55, 68, 58, 108, 95, 29, 10, 115, 0, 32, 2, 11},
|
||||
{49, 8, 10, 30, 124, 11, 32, 113, 130, 58, 125, 9, 100, 53, 104, 115, 131, 103, 24, 7, 1, 39, 45, 36, 139, 0, 137, 22, 90, 44, 114, 105},
|
||||
{9, 38, 72, 125, 49, 41, 84, 11, 13, 5, 27, 0, 16, 92, 8, 2, 65, 105, 10, 18, 48, 29, 127, 131, 36, 14, 1, 46, 111, 79, 130, 12},
|
||||
{130, 8, 10, 100, 104, 131, 49, 32, 53, 39, 30, 36, 113, 24, 11, 22, 124, 44, 83, 58, 7, 103, 1, 4, 9, 125, 5, 0, 91, 33, 115, 74},
|
||||
{114, 11, 58, 8, 120, 49, 9, 124, 142, 111, 41, 30, 10, 0, 97, 130, 62, 84, 38, 5, 72, 125, 92, 127, 100, 27, 139, 113, 13, 132, 32, 1},
|
||||
{60, 46, 28, 27, 40, 20, 0, 17, 18, 2, 126, 16, 6, 38, 86, 23, 79, 54, 1, 93, 5, 88, 41, 14, 21, 111, 7, 48, 3, 84, 72, 62},
|
||||
{72, 92, 38, 65, 84, 48, 41, 79, 27, 16, 29, 111, 88, 5, 18, 46, 1, 0, 152, 14, 37, 19, 77, 42, 132, 7, 22, 13, 119, 56, 12, 2},
|
||||
{7, 55, 1, 95, 29, 56, 64, 116, 143, 8, 14, 30, 47, 94, 152, 90, 65, 67, 10, 133, 42, 72, 146, 84, 16, 48, 6, 0, 25, 108, 77, 21},
|
||||
{27, 23, 20, 5, 0, 79, 38, 2, 3, 1, 59, 46, 4, 41, 33, 86, 37, 87, 88, 92, 7, 126, 43, 8, 22, 152, 151, 150, 149, 148, 147, 146},
|
||||
{12, 0, 1, 2, 7, 6, 3, 5, 28, 4, 8, 14, 60, 40, 17, 19, 21, 86, 126, 93, 10, 18, 9, 29, 48, 99, 65, 25, 84, 119, 72, 41},
|
||||
{60, 40, 99, 2, 54, 12, 0, 1, 19, 28, 98, 93, 6, 138, 21, 5, 27, 17, 151, 14, 76, 46, 16, 18, 38, 29, 86, 144, 107, 7, 25, 41},
|
||||
{12, 0, 1, 2, 3, 5, 6, 7, 4, 28, 8, 60, 14, 40, 16, 17, 21, 10, 19, 9, 86, 38, 126, 41, 93, 27, 29, 48, 62, 84, 79, 99},
|
||||
{0, 1, 2, 10, 5, 8, 3, 25, 4, 29, 32, 34, 63, 7, 77, 26, 16, 48, 65, 56, 14, 22, 129, 103, 72, 24, 18, 152, 140, 53, 96, 42},
|
||||
{46, 126, 18, 54, 12, 16, 1, 0, 5, 2, 27, 98, 20, 23, 6, 3, 88, 48, 28, 7, 19, 8, 4, 60, 151, 38, 37, 21, 79, 14, 65, 40},
|
||||
{76, 6, 141, 86, 119, 2, 138, 67, 28, 145, 0, 93, 17, 1, 40, 60, 146, 99, 147, 14, 21, 144, 132, 7, 5, 29, 55, 27, 16, 75, 19, 12},
|
||||
{71, 5, 51, 39, 22, 80, 0, 43, 10, 122, 8, 62, 41, 24, 104, 87, 35, 37, 2, 91, 33, 120, 36, 38, 1, 131, 9, 100, 130, 66, 3, 4},
|
||||
{126, 18, 46, 27, 20, 16, 88, 23, 12, 79, 54, 59, 48, 0, 73, 1, 37, 151, 5, 19, 28, 38, 2, 66, 60, 3, 65, 98, 14, 26, 6, 43},
|
||||
{22, 10, 8, 5, 0, 71, 35, 80, 104, 39, 24, 51, 100, 1, 62, 32, 2, 130, 11, 41, 7, 9, 53, 43, 49, 83, 122, 120, 30, 44, 37, 38},
|
||||
{1, 34, 14, 129, 53, 63, 42, 26, 121, 148, 7, 44, 96, 10, 0, 24, 100, 32, 64, 116, 140, 22, 5, 19, 29, 103, 135, 108, 8, 61, 39, 83},
|
||||
{1, 7, 34, 63, 44, 25, 135, 14, 24, 108, 22, 0, 83, 94, 5, 129, 35, 101, 47, 121, 2, 19, 42, 53, 6, 110, 103, 8, 148, 10, 16, 123},
|
||||
{12, 28, 16, 60, 18, 1, 6, 21, 14, 0, 86, 19, 2, 48, 93, 17, 38, 29, 7, 5, 65, 126, 46, 72, 41, 79, 84, 119, 40, 56, 54, 88},
|
||||
{0, 2, 12, 27, 5, 46, 38, 40, 41, 79, 88, 99, 3, 23, 1, 62, 20, 4, 22, 37, 92, 35, 18, 8, 16, 24, 10, 60, 7, 120, 98, 54},
|
||||
{1, 7, 14, 56, 8, 0, 84, 67, 10, 2, 133, 72, 42, 111, 5, 30, 21, 4, 9, 3, 25, 94, 16, 116, 47, 11, 65, 18, 132, 90, 55, 64},
|
||||
{30, 8, 124, 139, 45, 11, 58, 90, 113, 137, 7, 115, 10, 32, 1, 49, 94, 85, 9, 47, 108, 103, 0, 97, 63, 14, 50, 114, 53, 106, 100, 25},
|
||||
{65, 38, 48, 27, 16, 79, 72, 18, 88, 19, 46, 77, 84, 92, 37, 41, 0, 29, 1, 14, 12, 111, 2, 5, 31, 36, 87, 74, 105, 40, 28, 51},
|
||||
{10, 8, 30, 113, 130, 100, 53, 32, 115, 103, 104, 7, 1, 121, 39, 49, 131, 44, 24, 36, 63, 137, 34, 45, 22, 90, 108, 83, 26, 11, 94, 139},
|
||||
{51, 52, 43, 33, 5, 74, 16, 37, 71, 91, 38, 3, 36, 87, 48, 22, 4, 0, 122, 41, 39, 18, 66, 27, 79, 24, 65, 88, 59, 23, 62, 92},
|
||||
{1, 7, 63, 53, 108, 121, 94, 44, 103, 100, 14, 10, 129, 47, 32, 26, 24, 25, 148, 42, 135, 22, 0, 61, 83, 8, 39, 104, 5, 64, 115, 34},
|
||||
{1, 8, 10, 7, 5, 0, 80, 32, 62, 2, 24, 44, 53, 83, 9, 41, 30, 22, 100, 11, 14, 25, 120, 4, 26, 6, 3, 16, 122, 34, 19, 35},
|
||||
{74, 4, 36, 48, 33, 91, 39, 79, 22, 16, 65, 5, 131, 38, 24, 71, 27, 52, 0, 105, 51, 18, 88, 104, 3, 31, 10, 37, 72, 19, 41, 130},
|
||||
{59, 43, 38, 79, 23, 27, 92, 51, 0, 16, 46, 5, 18, 88, 41, 37, 66, 3, 87, 20, 48, 2, 122, 4, 22, 12, 1, 126, 19, 65, 33, 24},
|
||||
{12, 28, 1, 27, 0, 16, 2, 46, 65, 60, 21, 3, 5, 18, 6, 19, 48, 14, 4, 7, 79, 88, 86, 29, 22, 72, 93, 40, 23, 8, 17, 41},
|
||||
{22, 91, 39, 33, 24, 71, 5, 131, 36, 10, 51, 0, 130, 8, 104, 2, 35, 125, 9, 43, 52, 49, 83, 80, 100, 41, 122, 3, 37, 38, 4, 16},
|
||||
{12, 0, 1, 2, 5, 3, 4, 8, 7, 27, 18, 38, 10, 6, 16, 46, 9, 20, 41, 23, 126, 79, 22, 14, 19, 99, 88, 54, 37, 48, 62, 35},
|
||||
{12, 27, 1, 2, 3, 0, 46, 4, 38, 16, 8, 28, 7, 79, 18, 5, 84, 6, 88, 10, 14, 21, 23, 20, 40, 22, 60, 19, 9, 29, 72, 65},
|
||||
{1, 14, 7, 55, 95, 29, 8, 94, 30, 56, 10, 108, 77, 116, 152, 64, 32, 48, 63, 42, 143, 148, 16, 25, 137, 65, 11, 0, 115, 9, 19, 72},
|
||||
{37, 79, 66, 38, 16, 52, 48, 59, 43, 27, 87, 33, 41, 4, 23, 51, 3, 5, 88, 18, 92, 46, 73, 122, 22, 71, 20, 0, 65, 19, 2, 120},
|
||||
{24, 32, 83, 22, 53, 1, 8, 10, 7, 30, 35, 5, 103, 0, 100, 101, 121, 113, 34, 123, 63, 2, 44, 25, 71, 115, 80, 14, 26, 108, 51, 39},
|
||||
{97, 45, 111, 58, 85, 139, 0, 90, 47, 7, 120, 106, 142, 30, 50, 132, 41, 62, 84, 1, 119, 114, 14, 56, 117, 8, 38, 29, 2, 64, 116, 5},
|
||||
{12, 28, 16, 18, 1, 60, 6, 14, 2, 21, 0, 86, 126, 19, 48, 93, 7, 27, 17, 29, 5, 65, 54, 38, 72, 79, 84, 88, 119, 145, 8, 111},
|
||||
{118, 47, 64, 116, 57, 85, 7, 14, 50, 1, 42, 0, 45, 68, 86, 69, 2, 111, 134, 28, 90, 55, 16, 29, 56, 48, 84, 144, 60, 30, 112, 41},
|
||||
{12, 1, 2, 0, 7, 6, 28, 5, 3, 4, 8, 14, 60, 21, 18, 40, 17, 86, 10, 9, 16, 29, 19, 93, 126, 79, 38, 84, 72, 27, 111, 119},
|
||||
{11, 8, 49, 130, 10, 125, 9, 124, 100, 114, 131, 30, 58, 104, 32, 39, 24, 113, 36, 105, 0, 41, 22, 120, 5, 53, 111, 38, 142, 44, 83, 35},
|
||||
{50, 70, 47, 118, 85, 57, 106, 0, 45, 7, 64, 90, 81, 14, 2, 134, 28, 62, 86, 55, 69, 1, 78, 119, 68, 56, 18, 67, 16, 60, 29, 21},
|
||||
{43, 37, 33, 87, 51, 41, 66, 5, 122, 38, 22, 59, 92, 0, 23, 91, 27, 16, 71, 79, 18, 52, 120, 4, 3, 24, 46, 20, 73, 39, 62, 36},
|
||||
{79, 48, 4, 16, 27, 88, 43, 33, 18, 38, 65, 37, 46, 3, 19, 51, 52, 22, 66, 87, 74, 5, 41, 91, 23, 59, 0, 71, 122, 72, 20, 92},
|
||||
{32, 100, 10, 8, 30, 104, 24, 44, 39, 113, 83, 103, 1, 7, 22, 53, 115, 63, 135, 121, 26, 35, 34, 5, 0, 108, 137, 90, 91, 45, 2, 130},
|
||||
{0, 1, 2, 5, 16, 12, 6, 7, 14, 3, 19, 18, 29, 20, 4, 21, 40, 8, 17, 35, 23, 48, 126, 22, 25, 56, 26, 10, 98, 27, 38, 65},
|
||||
{143, 67, 56, 146, 1, 7, 133, 55, 64, 141, 134, 69, 6, 47, 14, 29, 84, 21, 111, 147, 57, 16, 95, 72, 118, 132, 50, 0, 2, 18, 119, 42},
|
||||
{1, 7, 67, 14, 133, 111, 8, 84, 0, 21, 2, 47, 64, 132, 55, 10, 95, 147, 119, 42, 16, 5, 72, 56, 4, 3, 6, 29, 9, 25, 18, 30},
|
||||
{68, 57, 69, 112, 144, 86, 102, 2, 134, 55, 0, 70, 118, 64, 75, 47, 14, 28, 93, 143, 67, 7, 50, 149, 1, 21, 29, 56, 119, 95, 60, 78},
|
||||
{58, 97, 114, 30, 124, 45, 11, 139, 8, 90, 0, 142, 7, 10, 41, 113, 84, 62, 49, 111, 85, 1, 9, 5, 137, 120, 32, 14, 2, 117, 47, 38},
|
||||
{23, 66, 18, 79, 38, 20, 43, 27, 16, 88, 46, 59, 126, 37, 87, 12, 73, 92, 3, 5, 48, 0, 19, 54, 2, 51, 28, 1, 41, 65, 122, 22},
|
||||
{0, 12, 2, 27, 5, 40, 46, 38, 1, 41, 3, 79, 88, 23, 99, 4, 20, 62, 22, 54, 92, 18, 8, 37, 16, 35, 10, 7, 19, 120, 144, 24},
|
||||
{1, 14, 25, 26, 0, 7, 44, 34, 129, 42, 24, 5, 135, 22, 19, 148, 6, 96, 83, 2, 29, 16, 63, 35, 101, 64, 140, 136, 116, 110, 3, 10},
|
||||
{12, 1, 2, 27, 3, 4, 38, 5, 7, 8, 18, 16, 46, 6, 0, 40, 41, 10, 79, 23, 88, 9, 20, 22, 14, 19, 37, 92, 48, 126, 28, 21},
|
||||
{7, 1, 10, 32, 108, 103, 94, 47, 8, 53, 25, 14, 34, 115, 100, 129, 121, 130, 148, 42, 64, 116, 63, 26, 44, 0, 24, 30, 113, 4, 104, 22},
|
||||
{47, 134, 7, 14, 55, 69, 64, 95, 1, 29, 85, 118, 56, 116, 45, 57, 102, 143, 50, 90, 42, 30, 16, 94, 0, 8, 67, 75, 133, 2, 18, 48},
|
||||
{12, 1, 2, 0, 7, 6, 28, 8, 14, 5, 3, 4, 40, 21, 17, 18, 60, 86, 16, 93, 126, 10, 9, 29, 99, 38, 119, 25, 19, 54, 27, 84},
|
||||
{59, 16, 27, 18, 23, 88, 79, 37, 46, 66, 38, 20, 73, 126, 3, 43, 48, 87, 92, 51, 41, 12, 19, 5, 52, 107, 65, 0, 151, 122, 54, 2},
|
||||
{1, 21, 147, 7, 119, 14, 76, 132, 55, 0, 86, 145, 2, 6, 69, 67, 16, 143, 111, 138, 17, 28, 29, 60, 18, 93, 8, 19, 40, 56, 84, 5},
|
||||
{144, 86, 112, 2, 68, 102, 69, 0, 149, 93, 75, 28, 57, 55, 145, 60, 21, 67, 99, 134, 143, 40, 146, 119, 82, 110, 62, 6, 29, 26, 78, 14},
|
||||
{102, 57, 55, 69, 143, 75, 146, 67, 56, 68, 134, 2, 29, 141, 0, 21, 6, 14, 133, 118, 64, 1, 7, 95, 47, 84, 111, 28, 147, 82, 72, 119},
|
||||
{0, 70, 57, 119, 50, 145, 2, 86, 28, 118, 69, 78, 149, 47, 60, 68, 67, 55, 93, 81, 134, 21, 14, 62, 64, 7, 5, 1, 132, 85, 41, 16},
|
||||
{51, 5, 43, 71, 122, 87, 41, 37, 91, 39, 0, 22, 33, 36, 38, 24, 66, 120, 62, 2, 80, 16, 92, 10, 59, 4, 27, 23, 35, 79, 8, 3},
|
||||
{12, 1, 2, 0, 7, 6, 28, 5, 8, 14, 3, 21, 40, 4, 60, 17, 86, 18, 16, 93, 10, 9, 126, 119, 99, 29, 19, 41, 38, 27, 25, 92},
|
||||
{27, 18, 46, 126, 23, 16, 88, 79, 20, 151, 59, 73, 48, 38, 0, 54, 12, 2, 37, 1, 19, 5, 28, 60, 66, 41, 3, 109, 86, 65, 40, 6},
|
||||
{48, 79, 4, 33, 16, 74, 65, 38, 88, 27, 91, 52, 18, 36, 22, 19, 46, 0, 37, 3, 51, 5, 71, 39, 72, 43, 24, 41, 92, 87, 2, 10},
|
||||
{86, 2, 144, 93, 28, 112, 141, 6, 102, 21, 99, 60, 75, 0, 68, 82, 69, 146, 67, 149, 55, 40, 145, 76, 111, 147, 56, 119, 110, 143, 26, 132},
|
||||
{6, 138, 2, 99, 86, 17, 40, 93, 28, 21, 145, 141, 0, 60, 119, 147, 128, 76, 67, 54, 1, 12, 5, 27, 144, 14, 38, 98, 146, 41, 29, 19},
|
||||
{1, 8, 0, 10, 2, 29, 7, 5, 3, 56, 4, 25, 14, 152, 63, 32, 65, 72, 96, 42, 34, 108, 48, 9, 26, 16, 84, 103, 67, 148, 22, 129},
|
||||
{149, 145, 0, 86, 2, 28, 93, 144, 62, 60, 119, 101, 21, 41, 5, 35, 78, 99, 26, 40, 12, 68, 57, 67, 110, 120, 69, 18, 55, 76, 132, 70},
|
||||
{12, 28, 16, 1, 48, 19, 6, 60, 2, 14, 18, 21, 0, 27, 46, 65, 86, 29, 5, 7, 72, 93, 40, 3, 17, 84, 56, 88, 126, 4, 38, 8},
|
||||
{1, 8, 5, 10, 7, 24, 2, 62, 0, 41, 22, 122, 120, 9, 4, 3, 32, 87, 11, 37, 38, 83, 100, 44, 25, 104, 16, 26, 39, 80, 14, 6},
|
||||
{0, 119, 62, 86, 145, 149, 28, 132, 93, 2, 120, 67, 60, 41, 35, 5, 144, 21, 123, 38, 111, 81, 84, 56, 12, 44, 24, 50, 92, 55, 40, 22},
|
||||
{2, 93, 99, 28, 40, 144, 60, 0, 86, 150, 76, 21, 149, 98, 6, 25, 1, 61, 82, 26, 12, 5, 54, 141, 7, 18, 145, 16, 27, 138, 110, 38},
|
||||
{24, 8, 10, 22, 32, 35, 100, 5, 1, 53, 0, 7, 71, 80, 30, 123, 83, 104, 51, 11, 2, 39, 44, 113, 9, 62, 25, 103, 34, 101, 43, 41},
|
||||
{12, 1, 2, 0, 7, 6, 28, 5, 40, 60, 8, 16, 3, 18, 14, 4, 86, 21, 17, 93, 41, 10, 9, 99, 27, 119, 38, 19, 126, 22, 48, 145},
|
||||
{45, 47, 50, 7, 85, 90, 97, 1, 64, 139, 116, 118, 30, 58, 14, 106, 70, 111, 0, 57, 94, 42, 137, 142, 29, 120, 8, 56, 18, 134, 84, 41},
|
||||
{12, 0, 2, 5, 27, 38, 1, 46, 41, 40, 79, 144, 3, 22, 88, 23, 28, 60, 99, 62, 6, 24, 26, 7, 4, 16, 10, 35, 37, 18, 14, 20},
|
||||
{37, 38, 59, 92, 0, 5, 23, 51, 79, 41, 27, 22, 2, 3, 87, 16, 46, 4, 1, 43, 20, 33, 18, 88, 24, 71, 8, 10, 48, 19, 126, 122},
|
||||
{12, 28, 16, 60, 1, 18, 6, 21, 19, 14, 48, 0, 2, 86, 93, 5, 46, 29, 17, 27, 65, 7, 3, 72, 38, 126, 119, 40, 84, 37, 56, 4},
|
||||
{0, 2, 5, 1, 16, 6, 27, 28, 18, 38, 60, 7, 14, 21, 46, 40, 86, 41, 19, 48, 93, 8, 3, 79, 22, 4, 10, 37, 62, 23, 24, 111},
|
||||
{85, 7, 90, 30, 47, 139, 45, 50, 94, 58, 137, 1, 8, 64, 14, 116, 118, 115, 113, 11, 124, 108, 0, 10, 97, 57, 32, 70, 42, 106, 29, 114},
|
||||
{33, 36, 22, 71, 51, 5, 91, 39, 0, 52, 43, 24, 131, 74, 16, 37, 38, 122, 41, 3, 87, 48, 4, 104, 35, 80, 10, 2, 105, 62, 27, 18},
|
||||
{12, 1, 27, 2, 0, 16, 3, 28, 46, 18, 4, 6, 5, 72, 21, 79, 38, 7, 14, 60, 88, 8, 65, 19, 48, 29, 23, 40, 22, 20, 86, 126},
|
||||
{0, 12, 2, 27, 5, 38, 46, 41, 1, 40, 79, 3, 88, 23, 22, 99, 20, 37, 62, 4, 18, 6, 16, 35, 60, 28, 24, 7, 92, 8, 14, 10},
|
||||
{7, 47, 1, 30, 137, 8, 116, 94, 90, 64, 14, 115, 108, 118, 57, 10, 148, 113, 42, 85, 32, 11, 63, 50, 103, 45, 124, 134, 55, 9, 69, 34},
|
||||
{55, 7, 1, 29, 56, 143, 64, 47, 67, 133, 14, 146, 95, 72, 84, 8, 116, 111, 6, 134, 141, 21, 65, 0, 69, 30, 16, 45, 85, 42, 50, 10},
|
||||
{14, 1, 42, 8, 10, 29, 108, 63, 55, 148, 95, 32, 7, 19, 25, 115, 103, 34, 56, 129, 77, 0, 16, 152, 94, 30, 113, 26, 2, 5, 48, 4},
|
||||
{111, 120, 142, 97, 58, 0, 41, 45, 62, 132, 114, 84, 139, 30, 5, 8, 38, 2, 7, 85, 119, 90, 117, 1, 124, 11, 56, 47, 28, 27, 35, 72},
|
||||
{1, 0, 14, 2, 6, 5, 16, 19, 7, 29, 42, 18, 3, 25, 12, 35, 21, 8, 26, 17, 40, 4, 20, 48, 109, 99, 22, 96, 55, 101, 10, 61},
|
||||
{12, 0, 1, 5, 3, 2, 4, 7, 27, 8, 38, 6, 40, 18, 16, 10, 20, 46, 9, 41, 23, 22, 79, 14, 62, 19, 37, 126, 88, 11, 92, 48},
|
||||
{10, 8, 104, 39, 24, 32, 22, 83, 44, 100, 30, 130, 53, 91, 113, 5, 11, 1, 35, 33, 7, 49, 0, 2, 103, 71, 36, 124, 9, 80, 131, 34},
|
||||
{1, 7, 0, 14, 8, 34, 5, 25, 35, 26, 6, 63, 10, 123, 2, 16, 103, 19, 44, 32, 135, 121, 108, 80, 62, 30, 115, 94, 149, 144, 53, 18},
|
||||
{75, 68, 146, 141, 102, 67, 2, 21, 6, 57, 69, 143, 0, 55, 82, 86, 28, 144, 147, 29, 93, 112, 56, 119, 133, 14, 76, 60, 84, 134, 111, 145},
|
||||
{10, 32, 115, 7, 8, 53, 1, 108, 30, 113, 94, 137, 100, 63, 90, 34, 130, 103, 121, 47, 44, 25, 104, 39, 24, 26, 85, 14, 49, 36, 22, 131},
|
||||
{39, 24, 10, 22, 8, 130, 91, 104, 83, 49, 5, 33, 100, 11, 0, 35, 32, 131, 71, 36, 9, 44, 53, 2, 80, 51, 30, 1, 41, 7, 43, 62},
|
||||
{38, 36, 65, 105, 27, 72, 31, 79, 41, 131, 5, 48, 125, 39, 0, 16, 92, 46, 22, 13, 18, 84, 24, 37, 88, 2, 33, 74, 91, 71, 130, 49},
|
||||
{0, 106, 62, 50, 45, 119, 85, 81, 132, 28, 2, 86, 41, 47, 38, 60, 35, 117, 5, 29, 7, 30, 145, 90, 55, 70, 14, 111, 18, 67, 93, 56},
|
||||
{0, 2, 5, 1, 3, 25, 19, 26, 4, 34, 29, 10, 22, 16, 8, 7, 24, 14, 48, 65, 53, 18, 6, 77, 44, 56, 72, 61, 121, 21, 136, 40},
|
||||
{7, 1, 94, 8, 47, 115, 10, 32, 113, 103, 30, 108, 137, 63, 14, 64, 116, 148, 129, 42, 90, 25, 34, 118, 53, 57, 11, 49, 85, 9, 96, 50},
|
||||
{14, 0, 1, 26, 19, 5, 42, 2, 25, 24, 29, 22, 6, 44, 61, 16, 7, 96, 136, 3, 140, 34, 35, 55, 135, 18, 48, 77, 83, 4, 8, 10},
|
||||
{1, 7, 14, 0, 25, 6, 34, 5, 26, 16, 63, 2, 19, 8, 35, 101, 108, 29, 94, 10, 18, 42, 123, 144, 129, 47, 61, 21, 3, 62, 149, 4},
|
||||
{12, 0, 2, 1, 28, 5, 6, 120, 7, 60, 40, 16, 18, 86, 27, 14, 21, 93, 8, 62, 41, 38, 3, 17, 4, 119, 99, 48, 19, 126, 10, 9},
|
||||
{86, 144, 93, 2, 28, 149, 0, 60, 99, 112, 110, 145, 40, 21, 102, 26, 75, 62, 69, 1, 12, 101, 119, 25, 76, 67, 7, 68, 55, 5, 6, 14},
|
||||
{8, 30, 10, 32, 113, 49, 115, 137, 124, 103, 45, 90, 7, 139, 11, 1, 58, 53, 130, 94, 108, 100, 9, 63, 85, 125, 34, 47, 0, 24, 44, 104},
|
||||
{120, 142, 111, 41, 58, 114, 97, 0, 11, 62, 84, 124, 5, 30, 8, 38, 132, 127, 27, 139, 92, 10, 72, 45, 49, 9, 28, 2, 29, 56, 16, 1},
|
||||
{8, 113, 30, 137, 7, 32, 10, 90, 94, 115, 1, 103, 108, 63, 47, 85, 49, 53, 11, 45, 34, 50, 14, 25, 9, 124, 100, 130, 139, 121, 42, 26},
|
||||
{64, 7, 14, 47, 134, 55, 1, 42, 95, 69, 116, 90, 94, 30, 8, 29, 56, 137, 45, 108, 85, 10, 57, 16, 102, 143, 118, 19, 63, 32, 11, 50},
|
||||
{62, 132, 0, 119, 120, 41, 111, 86, 35, 28, 5, 84, 56, 38, 2, 93, 145, 60, 67, 12, 92, 27, 29, 72, 55, 117, 21, 24, 133, 149, 22, 45},
|
||||
{57, 68, 69, 118, 134, 64, 50, 47, 55, 14, 7, 2, 102, 144, 0, 112, 70, 86, 85, 1, 95, 29, 116, 143, 42, 75, 16, 56, 28, 45, 21, 48},
|
||||
{0, 12, 2, 1, 5, 28, 6, 40, 60, 27, 7, 38, 16, 14, 86, 18, 93, 41, 62, 46, 99, 35, 8, 23, 3, 17, 22, 21, 10, 19, 79, 20},
|
||||
{12, 1, 2, 27, 16, 3, 38, 111, 4, 0, 18, 5, 7, 46, 40, 8, 79, 6, 14, 28, 88, 10, 48, 41, 19, 84, 21, 9, 22, 23, 20, 72},
|
||||
{53, 103, 32, 7, 1, 100, 22, 63, 71, 44, 10, 115, 108, 24, 92, 104, 26, 30, 122, 94, 8, 39, 83, 34, 137, 135, 90, 91, 121, 5, 87, 47},
|
||||
{87, 37, 41, 0, 22, 38, 2, 92, 1, 24, 4, 8, 3, 59, 10, 5, 39, 23, 71, 79, 122, 27, 16, 46, 33, 7, 91, 20, 18, 51, 9, 120},
|
||||
{1, 7, 8, 10, 0, 5, 35, 32, 53, 44, 14, 30, 2, 80, 25, 34, 6, 62, 26, 103, 16, 19, 63, 9, 149, 24, 121, 41, 22, 11, 113, 83},
|
||||
{11, 58, 8, 30, 124, 49, 10, 113, 9, 114, 139, 45, 97, 32, 7, 137, 90, 1, 0, 130, 115, 125, 100, 24, 5, 94, 53, 41, 14, 13, 35, 38},
|
||||
{125, 105, 9, 36, 131, 49, 8, 130, 39, 11, 10, 5, 22, 38, 41, 104, 0, 31, 13, 24, 27, 16, 2, 72, 65, 91, 48, 32, 84, 18, 100, 74},
|
||||
{12, 1, 0, 2, 6, 3, 7, 5, 4, 8, 14, 28, 16, 60, 18, 10, 21, 17, 19, 9, 40, 27, 86, 93, 29, 38, 54, 11, 25, 48, 46, 41},
|
||||
{84, 41, 38, 72, 92, 29, 111, 5, 65, 120, 79, 0, 27, 56, 48, 14, 132, 16, 119, 22, 86, 88, 46, 28, 62, 12, 1, 2, 93, 18, 24, 127},
|
||||
{99, 28, 40, 60, 2, 93, 138, 0, 98, 17, 86, 54, 76, 12, 27, 1, 21, 144, 128, 38, 5, 14, 46, 18, 25, 16, 109, 6, 41, 145, 7, 29},
|
||||
{1, 63, 10, 32, 148, 14, 103, 34, 42, 7, 8, 108, 116, 53, 64, 96, 25, 121, 26, 94, 140, 0, 29, 19, 55, 24, 100, 136, 5, 4, 44, 115},
|
||||
{131, 100, 130, 49, 10, 8, 36, 104, 39, 0, 48, 41, 11, 38, 4, 24, 27, 22, 16, 44, 79, 5, 33, 2, 53, 9, 125, 74, 91, 120, 32, 83},
|
||||
{36, 39, 131, 74, 4, 91, 22, 33, 125, 104, 130, 48, 10, 24, 16, 5, 49, 8, 100, 105, 79, 0, 9, 65, 71, 2, 18, 83, 31, 11, 19, 44},
|
||||
{0, 12, 2, 1, 6, 5, 7, 28, 40, 60, 16, 14, 18, 62, 86, 27, 93, 8, 17, 38, 21, 41, 35, 99, 3, 19, 10, 23, 22, 4, 9, 48},
|
||||
{1, 7, 67, 14, 21, 147, 111, 55, 132, 119, 0, 8, 2, 76, 64, 16, 47, 84, 6, 18, 86, 95, 145, 10, 42, 29, 133, 5, 56, 134, 17, 72},
|
||||
{69, 55, 47, 134, 102, 143, 7, 57, 118, 95, 14, 64, 29, 56, 1, 50, 75, 67, 146, 2, 0, 133, 68, 16, 21, 6, 141, 85, 116, 18, 72, 65},
|
||||
{1, 44, 7, 24, 83, 63, 34, 103, 22, 121, 53, 32, 25, 35, 0, 115, 108, 5, 14, 8, 10, 101, 94, 30, 2, 123, 110, 26, 137, 47, 90, 19},
|
||||
{14, 1, 25, 42, 34, 0, 26, 96, 19, 29, 140, 5, 53, 10, 2, 121, 3, 24, 44, 22, 55, 77, 129, 7, 63, 16, 8, 4, 6, 61, 100, 48},
|
||||
{30, 90, 7, 8, 137, 94, 85, 1, 47, 113, 115, 108, 45, 139, 124, 11, 10, 32, 50, 58, 103, 14, 63, 64, 9, 116, 49, 42, 25, 148, 0, 53},
|
||||
{40, 99, 2, 60, 28, 17, 0, 54, 93, 98, 86, 138, 6, 12, 21, 76, 1, 5, 27, 144, 128, 38, 19, 46, 14, 41, 145, 7, 16, 67, 3, 109},
|
||||
{45, 58, 30, 139, 90, 7, 85, 137, 97, 8, 124, 47, 1, 11, 106, 114, 50, 94, 0, 113, 10, 115, 14, 32, 9, 64, 108, 41, 49, 29, 62, 116},
|
||||
{14, 42, 10, 1, 63, 96, 32, 25, 34, 8, 129, 29, 0, 103, 55, 19, 26, 53, 77, 5, 95, 2, 4, 7, 3, 16, 148, 56, 18, 24, 121, 108},
|
||||
{21, 2, 75, 86, 6, 76, 144, 28, 119, 99, 93, 147, 141, 67, 102, 145, 60, 132, 146, 128, 0, 82, 40, 138, 55, 111, 143, 17, 133, 112, 69, 14},
|
||||
{111, 120, 41, 62, 84, 132, 0, 5, 38, 119, 56, 92, 72, 142, 27, 28, 29, 35, 58, 80, 2, 86, 65, 79, 12, 14, 1, 24, 145, 16, 21, 48},
|
||||
{146, 67, 141, 69, 133, 21, 6, 143, 57, 55, 111, 147, 56, 1, 14, 132, 7, 2, 134, 102, 0, 119, 29, 84, 76, 64, 86, 72, 28, 68, 47, 75},
|
||||
{12, 1, 0, 5, 27, 3, 7, 4, 38, 8, 6, 41, 16, 40, 46, 10, 18, 79, 2, 9, 23, 86, 20, 22, 62, 14, 37, 88, 92, 19, 24, 11},
|
||||
{0, 12, 2, 1, 27, 5, 38, 28, 60, 6, 40, 7, 16, 46, 18, 14, 41, 99, 93, 62, 3, 79, 86, 23, 149, 8, 22, 35, 88, 17, 19, 10},
|
||||
{141, 6, 21, 67, 147, 102, 146, 2, 76, 119, 132, 69, 55, 111, 86, 75, 28, 133, 143, 0, 1, 145, 14, 128, 56, 99, 17, 60, 29, 93, 84, 68},
|
||||
{21, 76, 1, 119, 86, 145, 2, 0, 14, 7, 6, 138, 146, 55, 17, 28, 132, 93, 67, 40, 60, 143, 29, 147, 111, 16, 69, 141, 5, 56, 19, 133},
|
||||
{1, 8, 108, 14, 7, 116, 64, 42, 10, 63, 94, 32, 115, 103, 113, 96, 30, 34, 55, 47, 95, 148, 29, 140, 129, 25, 134, 53, 69, 26, 19, 11},
|
||||
{12, 1, 3, 5, 4, 2, 0, 7, 8, 38, 27, 16, 18, 6, 10, 20, 41, 40, 79, 46, 9, 23, 22, 88, 92, 37, 14, 24, 62, 19, 48, 99},
|
||||
{1, 14, 7, 0, 6, 25, 5, 16, 19, 2, 42, 26, 29, 35, 61, 8, 18, 129, 101, 21, 3, 110, 34, 148, 96, 10, 17, 4, 22, 40, 12, 20},
|
||||
{0, 2, 5, 1, 3, 19, 22, 26, 16, 24, 29, 7, 14, 6, 4, 25, 18, 44, 8, 48, 12, 61, 20, 21, 10, 35, 65, 56, 23, 40, 17, 107},
|
||||
{1, 7, 8, 29, 56, 0, 10, 14, 2, 42, 72, 5, 4, 65, 3, 30, 84, 94, 67, 9, 25, 133, 111, 11, 32, 108, 16, 63, 21, 96, 26, 48}}};
|
||||
// endregion
|
||||
|
||||
template class OrderTable<3>;
|
||||
template class OrderTable<4>;
|
||||
|
||||
} // namespace quicktex::s3tc
|
149
quicktex/s3tc/bc1/OrderTable.h
Normal file
149
quicktex/s3tc/bc1/OrderTable.h
Normal file
@ -0,0 +1,149 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../../Vector4.h"
|
||||
#include "Histogram.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
template <size_t N> class OrderTable {
|
||||
public:
|
||||
static constexpr unsigned HashCount = 1 << ((N - 1) * 4); // 16**(N-1)
|
||||
static constexpr unsigned OrderCount = (N == 4) ? 969 : 153; //(16+N-1)C(N-1)
|
||||
#if RGBCX_USE_SMALLER_TABLES
|
||||
static constexpr unsigned BestOrderCount = 32;
|
||||
#else
|
||||
static constexpr unsigned BestOrderCount = (N == 4) ? 128 : 32;
|
||||
#endif
|
||||
|
||||
using Hash = uint16_t;
|
||||
using OrderArray = std::array<Histogram<N>, OrderCount>;
|
||||
using BestOrderRow = std::array<Hash, BestOrderCount>;
|
||||
using BestOrderArray = std::array<BestOrderRow, OrderCount>;
|
||||
|
||||
static std::atomic<bool> generated;
|
||||
|
||||
static const OrderArray Orders;
|
||||
static const BestOrderArray BestOrders;
|
||||
static const std::array<Vector4, N> Weights;
|
||||
static const std::array<Hash, N> SingleColorHashes;
|
||||
|
||||
static bool Generate() {
|
||||
static_assert(N == 4 || N == 3);
|
||||
|
||||
std::scoped_lock{table_mutex};
|
||||
if (!generated) {
|
||||
hashes = new std::array<Hash, HashCount>();
|
||||
factors = new std::array<Vector4, OrderCount>();
|
||||
|
||||
const float denominator = (N == 4) ? 3.0f : 2.0f;
|
||||
|
||||
for (uint16_t i = 0; i < OrderCount; i++) {
|
||||
Histogram<N> h = Orders[i];
|
||||
if (!h.Any16()) hashes->at(h.GetPacked()) = i;
|
||||
|
||||
Vector4 factor_matrix = 0;
|
||||
for (unsigned sel = 0; sel < N; sel++) factor_matrix += (Weights[sel] * h[sel]);
|
||||
|
||||
float det = factor_matrix.Determinant2x2();
|
||||
if (fabs(det) < 1e-8f) {
|
||||
factors->at(i) = Vector4(0);
|
||||
} else {
|
||||
std::swap(factor_matrix[0], factor_matrix[3]);
|
||||
factor_matrix *= Vector4(1, -1, -1, 1);
|
||||
factor_matrix *= (denominator / 255.0f) / det;
|
||||
factors->at(i) = factor_matrix;
|
||||
}
|
||||
}
|
||||
|
||||
generated = true;
|
||||
}
|
||||
assert(generated);
|
||||
return true;
|
||||
}
|
||||
|
||||
static Hash GetHash(Histogram<N> &hist) {
|
||||
for (unsigned i = 0; i < N; i++) {
|
||||
if (hist[i] == 16) return SingleColorHashes[i];
|
||||
}
|
||||
|
||||
assert(generated);
|
||||
assert(hashes != nullptr);
|
||||
|
||||
auto hash = hashes->at(hist.GetPacked());
|
||||
|
||||
assert(hash < OrderCount);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static Vector4 GetFactors(Hash hash) {
|
||||
assert(generated.load());
|
||||
assert(factors != nullptr);
|
||||
|
||||
return factors->at(hash);
|
||||
}
|
||||
|
||||
static bool IsSingleColor(Hash hash) { return (std::find(SingleColorHashes.begin(), SingleColorHashes.end(), hash) != SingleColorHashes.end()); }
|
||||
|
||||
private:
|
||||
static std::mutex table_mutex;
|
||||
static std::array<Hash, HashCount> *hashes;
|
||||
static std::array<Vector4, OrderCount> *factors;
|
||||
};
|
||||
|
||||
template <> std::atomic<bool> OrderTable<3>::generated;
|
||||
template <> std::atomic<bool> OrderTable<4>::generated;
|
||||
|
||||
template <> std::mutex OrderTable<3>::table_mutex;
|
||||
template <> std::mutex OrderTable<4>::table_mutex;
|
||||
|
||||
template <> std::array<OrderTable<3>::Hash, OrderTable<3>::HashCount> *OrderTable<3>::hashes;
|
||||
template <> std::array<OrderTable<4>::Hash, OrderTable<4>::HashCount> *OrderTable<4>::hashes;
|
||||
|
||||
template <> std::array<Vector4, OrderTable<3>::OrderCount> *OrderTable<3>::factors;
|
||||
template <> std::array<Vector4, OrderTable<4>::OrderCount> *OrderTable<4>::factors;
|
||||
|
||||
template <> const std::array<Vector4, 3> OrderTable<3>::Weights;
|
||||
template <> const std::array<Vector4, 4> OrderTable<4>::Weights;
|
||||
|
||||
template <> const std::array<uint16_t, 3> OrderTable<3>::SingleColorHashes;
|
||||
template <> const std::array<uint16_t, 4> OrderTable<4>::SingleColorHashes;
|
||||
|
||||
template <> const OrderTable<3>::OrderArray OrderTable<3>::Orders;
|
||||
template <> const OrderTable<4>::OrderArray OrderTable<4>::Orders;
|
||||
|
||||
template <> const OrderTable<3>::BestOrderArray OrderTable<3>::BestOrders;
|
||||
template <> const OrderTable<4>::BestOrderArray OrderTable<4>::BestOrders;
|
||||
|
||||
extern template class OrderTable<3>;
|
||||
extern template class OrderTable<4>;
|
||||
|
||||
} // namespace quicktex::s3tc
|
File diff suppressed because it is too large
Load Diff
93
quicktex/s3tc/bc1/SingleColorTable.h
Normal file
93
quicktex/s3tc/bc1/SingleColorTable.h
Normal file
@ -0,0 +1,93 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "../../util.h"
|
||||
#include "../interpolator/Interpolator.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
struct BC1MatchEntry {
|
||||
uint8_t high;
|
||||
uint8_t low;
|
||||
uint8_t error;
|
||||
};
|
||||
|
||||
using MatchList = std::array<BC1MatchEntry, 256>;
|
||||
using MatchListPtr = std::shared_ptr<MatchList>;
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
|
||||
/**
|
||||
* Lookup table for single-color blocks
|
||||
* @tparam B Number of bits (5 or 6)
|
||||
* @tparam N Number of colors (3 or 4)
|
||||
*/
|
||||
template <size_t B, size_t N> MatchListPtr SingleColorTable(InterpolatorPtr interpolator) {
|
||||
constexpr size_t Size = 1 << B;
|
||||
MatchListPtr matches = std::make_shared<MatchList>();
|
||||
|
||||
static_assert((B == 5 && Size == 32) || (B == 6 && Size == 64));
|
||||
static_assert(N == 4 || N == 3);
|
||||
|
||||
bool ideal = interpolator->IsIdeal();
|
||||
bool use_8bit = interpolator->CanInterpolate8Bit();
|
||||
|
||||
for (unsigned i = 0; i < 256; i++) {
|
||||
unsigned error = 256;
|
||||
|
||||
// TODO: Can probably avoid testing for values that definitely wont yield good results,
|
||||
// e.g. low8 and high8 both much smaller or larger than index
|
||||
for (uint8_t low = 0; low < Size; low++) {
|
||||
uint8_t low8 = (B == 5) ? scale5To8(low) : scale6To8(low);
|
||||
|
||||
for (uint8_t high = 0; high < Size; high++) {
|
||||
uint8_t high8 = (B == 5) ? scale5To8(high) : scale6To8(high);
|
||||
uint8_t value;
|
||||
|
||||
if (use_8bit) {
|
||||
value = interpolator->Interpolate8(high8, low8);
|
||||
} else {
|
||||
value = (B == 5) ? interpolator->Interpolate5(high, low) : interpolator->Interpolate6(high, low);
|
||||
}
|
||||
|
||||
unsigned new_error = iabs(value - (int)i);
|
||||
|
||||
// We only need to factor in 3% error in BC1 ideal mode.
|
||||
if (ideal) new_error += (iabs(high8 - (int)low8) * 3) / 100;
|
||||
|
||||
if ((new_error < error) || (new_error == error && low == high)) {
|
||||
assert(new_error <= UINT8_MAX);
|
||||
|
||||
(*matches)[i].low = (uint8_t)low;
|
||||
(*matches)[i].high = (uint8_t)high;
|
||||
(*matches)[i].error = (uint8_t)new_error;
|
||||
|
||||
error = new_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
} // namespace quicktex::s3tc
|
3
quicktex/s3tc/bc1/__init__.py
Normal file
3
quicktex/s3tc/bc1/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""Classes for encoding/decoding BC1 textures"""
|
||||
|
||||
from _quicktex._s3tc._bc1 import *
|
192
quicktex/s3tc/bc1/_bindings.cpp
Normal file
192
quicktex/s3tc/bc1/_bindings.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include "../../_bindings.h"
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "../../Decoder.h"
|
||||
#include "../../Encoder.h"
|
||||
#include "../interpolator/Interpolator.h"
|
||||
#include "BC1Decoder.h"
|
||||
#include "BC1Encoder.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
namespace quicktex::bindings {
|
||||
|
||||
using namespace quicktex::s3tc;
|
||||
using namespace pybind11::literals;
|
||||
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
|
||||
void InitBC1(py::module_ &s3tc) {
|
||||
auto bc1 = s3tc.def_submodule("_bc1", "internal bc1 module");
|
||||
|
||||
// region BC1Block
|
||||
auto bc1_block = BindBlock<BC1Block>(bc1, "BC1Block");
|
||||
bc1_block.doc() = "A single BC1 block.";
|
||||
|
||||
bc1_block.def(py::init<>());
|
||||
bc1_block.def(py::init<Color, Color, BC1Block::SelectorArray>(), "color0"_a, "color1"_a, "selectors"_a, R"doc(
|
||||
Create a new BC1Block with the specified endpoints and selectors
|
||||
|
||||
:param color0: The first endpoint
|
||||
:param color1: The second endpoint
|
||||
:param selectors: the selectors as a 4x4 list of integers, between 0 and 3 inclusive.
|
||||
)doc");
|
||||
|
||||
bc1_block.def_property("endpoints", &BC1Block::GetColors, &BC1Block::SetColors, "The block's endpoint colors as a 2-tuple.");
|
||||
bc1_block.def_property("selectors", &BC1Block::GetSelectors, &BC1Block::SetSelectors, R"doc(
|
||||
The block's selectors as a 4x4 list of integers between 0 and 3 inclusive.
|
||||
|
||||
.. note::
|
||||
This is a property, so directly modifying its value will not propogate back to the block.
|
||||
Instead you must read, modify, then write the new list back to the property, like so::
|
||||
|
||||
selectors = block.selectors
|
||||
selectors[0,0] = 0
|
||||
block.selectors = selectors
|
||||
)doc");
|
||||
bc1_block.def_property_readonly("is_3color", &BC1Block::Is3Color, R"doc(
|
||||
"True if the block uses 3-color interpolation, i.e. color0 <= color1. This value should be ignored when decoding as part of a BC3 block. Readonly.
|
||||
)doc");
|
||||
// endregion
|
||||
|
||||
// region BC1Texture
|
||||
auto bc1_texture = BindBlockTexture<BC1Block>(bc1, "BC1Texture");
|
||||
bc1_texture.doc() = "A texture comprised of BC1 blocks.";
|
||||
// endregion
|
||||
|
||||
// region BC1Encoder
|
||||
py::class_<BC1Encoder> bc1_encoder(bc1, "BC1Encoder", "Encodes RGB textures to BC1.");
|
||||
|
||||
py::enum_<BC1Encoder::EndpointMode>(bc1_encoder, "EndpointMode", "Enum representing various methods of finding endpoints in a block.")
|
||||
.value("LeastSquares", BC1Encoder::EndpointMode::LeastSquares, "Find endpoints using a 2D least squares approach.")
|
||||
.value("BoundingBox", BC1Encoder::EndpointMode::BoundingBox, "Find endpoints using a simple bounding box. Fast but inaccurate.")
|
||||
.value("BoundingBoxInt", BC1Encoder::EndpointMode::BoundingBoxInt, "Same as BoundingBox but using integers, slightly faster.")
|
||||
.value("PCA", BC1Encoder::EndpointMode::PCA, "Find endpoints using Principle Component Analysis. Slowest but highest quality method.");
|
||||
|
||||
py::enum_<BC1Encoder::ErrorMode>(bc1_encoder, "ErrorMode", "Enum representing various methods of finding selectors in a block.")
|
||||
.value("None", BC1Encoder::ErrorMode::None, "The same as Faster but error is not calculated. This disables any cluster-fit options")
|
||||
.value("Faster", BC1Encoder::ErrorMode::Faster, "Use a slightly lower quality, but ~30% faster MSE evaluation function for 4-color blocks.")
|
||||
.value("Check2", BC1Encoder::ErrorMode::Check2, "Default error-checking method.")
|
||||
.value("Full", BC1Encoder::ErrorMode::Full, "Examine all colors to compute selectors/MSE. Slower but slightly higher quality.");
|
||||
|
||||
py::enum_<BC1Encoder::ColorMode>(bc1_encoder, "ColorMode", "Enum representing various methods of writing BC1 blocks.")
|
||||
.value("FourColor", BC1Encoder::ColorMode::FourColor, "Default color mode. Only 4-color blocks will be output, where color0 > color1")
|
||||
.value("ThreeColor", BC1Encoder::ColorMode::ThreeColor, "Additionally use 3-color blocks when they have a lower error, where color0 <= color1")
|
||||
.value("ThreeColorBlack", BC1Encoder::ColorMode::ThreeColorBlack,
|
||||
"Additionally use 3-color blocks with black pixels (selector 3). Note that this requires your shader/engine to not sample the alpha channel "
|
||||
"when using a BC1 texture.");
|
||||
|
||||
bc1_encoder.def(py::init<unsigned, BC1Encoder::ColorMode>(), "level"_a = 5, "color_mode"_a = BC1Encoder::ColorMode::FourColor);
|
||||
bc1_encoder.def(py::init<unsigned, BC1Encoder::ColorMode, InterpolatorPtr>(), "level"_a, "color_mode"_a, "interpolator"_a, R"doc(
|
||||
Create a new BC1 encoder with the specified preset level, color mode, and interpolator.
|
||||
|
||||
:param int level: The preset level of the resulting encoder, between 0 and 18 inclusive. See :py:meth:`set_level` for more information. Default: 5.
|
||||
:param ColorMode color_mode: The color mode of the resulting BC1Encoder. Default: :py:class:`~quicktex.s3tc.bc1.BC1Encoder.ColorMode.FourColor`.
|
||||
:param Interpolator interpolator: The interpolation mode to use for encoding. Default: :py:class:`~quicktex.s3tc.interpolator.Interpolator`.
|
||||
)doc");
|
||||
|
||||
bc1_encoder.def("encode", &BC1Encoder::Encode, "texture"_a, R"doc(
|
||||
Encode a raw texture into a new BC1Texture using the encoder's current settings.
|
||||
|
||||
:param RawTexture texture: Input texture to encode.
|
||||
:returns: A new BC1Texture with the same dimension as the input.
|
||||
)doc");
|
||||
|
||||
bc1_encoder.def("set_level", &BC1Encoder::SetLevel, "level"_a, R"doc(
|
||||
Select a preset quality level, between 0 and 18 inclusive. Higher quality levels are slower, but produce blocks that are a closer match to input.
|
||||
This has no effect on the size of the resulting texture, since BC1 is a fixed-ratio compression method. For better control, see the advanced API below
|
||||
|
||||
:param int level: The preset level of the resulting encoder, between 0 and 18 inclusive. Default: 5.
|
||||
)doc");
|
||||
|
||||
bc1_encoder.def_property_readonly("interpolator", &BC1Encoder::GetInterpolator,
|
||||
"The :py:class:`~quicktex.s3tc.interpolator.Interpolator` used by this encoder. This is a readonly property.");
|
||||
bc1_encoder.def_property_readonly("color_mode", &BC1Encoder::GetColorMode,
|
||||
"The :py:class:`~quicktex.s3tc.bc1.BC1Encoder.ColorMode` used by this encoder. This is a readonly property.");
|
||||
|
||||
// Advanced API
|
||||
|
||||
bc1_encoder.def_property("error_mode", &BC1Encoder::GetErrorMode, &BC1Encoder::SetErrorMode, "The error mode used by this encoder for finding selectors.");
|
||||
bc1_encoder.def_property("endpoint_mode", &BC1Encoder::GetEndpointMode, &BC1Encoder::SetEndpointMode, "The endpoint mode used by this encoder.");
|
||||
|
||||
bc1_encoder.def_readwrite("two_ls_passes", &BC1Encoder::two_ls_passes,
|
||||
"Use 2 least squares pass, instead of one (same as stb_dxt's HIGHQUAL option).\n"
|
||||
"Recommended if you're setting the orderings settings greater than 0.");
|
||||
|
||||
bc1_encoder.def_readwrite("two_ep_passes", &BC1Encoder::two_ep_passes, "Try 2 different ways of choosing the initial endpoints.");
|
||||
|
||||
bc1_encoder.def_readwrite("two_cf_passes", &BC1Encoder::two_cf_passes,
|
||||
"Greatly increase encode time, with very slightly higher quality.\n"
|
||||
"Same as squish's iterative cluster fit option. Not really worth the tiny boost in quality, "
|
||||
"unless you just don't care about performance at all.");
|
||||
|
||||
bc1_encoder.def_readwrite("exhaustive", &BC1Encoder::exhaustive,
|
||||
"Check all total orderings - *very* slow. The encoder is not designed to be used in this way");
|
||||
|
||||
bc1_encoder.def_property("search_rounds", &BC1Encoder::GetSearchRounds, &BC1Encoder::SetSearchRounds,
|
||||
"Setting search rounds > 0 enables refining the final endpoints by examining nearby colors. A higher value has a higher quality "
|
||||
"at the expense of performance.");
|
||||
|
||||
bc1_encoder.def_property("orderings", &BC1Encoder::GetOrderings, &BC1Encoder::SetOrderings,
|
||||
"setting the orderings > 0 enables ordered cluster fit using a lookup table of similar blocks. Value is a tuple of (4 color "
|
||||
"orders, 3 color orders), where higher values have a higher quality at the expense of performance.");
|
||||
|
||||
bc1_encoder.def_readonly_static("max_power_iterations", &BC1Encoder::max_power_iterations);
|
||||
bc1_encoder.def_readonly_static("min_power_iterations", &BC1Encoder::min_power_iterations);
|
||||
|
||||
bc1_encoder.def_property("power_iterations", &BC1Encoder::GetPowerIterations, &BC1Encoder::SetPowerIterations,
|
||||
"Number of power iterations used with the PCA endpoint mode. Value should be around 4 to 6. "
|
||||
"Automatically clamped to between :py:const:`BC1Encoder.min_power_iterations` and :py:const:`BC1Encoder.max_power_iterations`");
|
||||
// endregion
|
||||
|
||||
// region BC1Decoder
|
||||
py::class_<BC1Decoder> bc1_decoder(bc1, "BC1Decoder", R"doc(
|
||||
Decodes BC1 textures to RGB
|
||||
)doc");
|
||||
|
||||
bc1_decoder.def(py::init<bool>(), "write_alpha"_a = false);
|
||||
bc1_decoder.def(py::init<bool, InterpolatorPtr>(), "write_alpha"_a, "interpolator"_a, R"doc(
|
||||
Create a new BC1 decoder with the specificied interpolator.
|
||||
|
||||
:param bool write_alpha: Determines if the alpha channel of the output is written to. Default: False;
|
||||
:param Interpolator interpolator: The interpolation mode to use for decoding. Default: :py:class:`~quicktex.s3tc.interpolator.Interpolator`.
|
||||
)doc");
|
||||
|
||||
bc1_decoder.def("decode", &BC1Decoder::Decode, "texture"_a, R"doc(
|
||||
Decode a BC1 texture into a new RawTexture using the decoder's current settings.
|
||||
|
||||
:param RawTexture texture: Input texture to encode.
|
||||
:returns: A new RawTexture with the same dimensions as the input
|
||||
)doc");
|
||||
|
||||
bc1_decoder.def_property_readonly("interpolator", &BC1Decoder::GetInterpolator, "The interpolator used by this decoder. This is a readonly property.");
|
||||
bc1_decoder.def_readwrite("write_alpha", &BC1Decoder::write_alpha, "Determines if the alpha channel of the output is written to.");
|
||||
// endregion
|
||||
}
|
||||
} // namespace quicktex::bindings
|
60
quicktex/s3tc/bc3/BC3Block.h
Normal file
60
quicktex/s3tc/bc3/BC3Block.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "../bc1/BC1Block.h"
|
||||
#include "../bc4/BC4Block.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class alignas(8) BC3Block {
|
||||
public:
|
||||
static constexpr int Width = 4;
|
||||
static constexpr int Height = 4;
|
||||
|
||||
using BlockPair = std::pair<BC4Block, BC1Block>;
|
||||
|
||||
BC4Block alpha_block;
|
||||
BC1Block color_block;
|
||||
|
||||
constexpr BC3Block() : alpha_block(BC4Block()), color_block(BC1Block()) {
|
||||
static_assert(sizeof(BC3Block) == 16);
|
||||
static_assert(sizeof(std::array<BC3Block, 10>) == 16 * 10);
|
||||
static_assert(alignof(BC3Block) >= 8);
|
||||
}
|
||||
|
||||
BC3Block(const BC4Block &alpha, const BC1Block &color) {
|
||||
alpha_block = alpha;
|
||||
color_block = color;
|
||||
}
|
||||
|
||||
BlockPair GetBlocks() const { return BlockPair(alpha_block, color_block); }
|
||||
|
||||
void SetBlocks(const BlockPair &blocks) {
|
||||
alpha_block = blocks.first;
|
||||
color_block = blocks.second;
|
||||
}
|
||||
|
||||
bool operator==(const BC3Block &Rhs) const { return alpha_block == Rhs.alpha_block && color_block == Rhs.color_block; }
|
||||
bool operator!=(const BC3Block &Rhs) const { return !(Rhs == *this); }
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -19,16 +19,16 @@
|
||||
|
||||
#include "BC3Decoder.h"
|
||||
|
||||
#include "../BC1/BC1Decoder.h"
|
||||
#include "../BC4/BC4Decoder.h"
|
||||
#include "../BlockView.h"
|
||||
#include "../ndebug.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "BC3Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
void BC3Decoder::DecodeBlock(Color4x4 dest, BC3Block *const block) const noexcept(ndebug) {
|
||||
_bc1_decoder->DecodeBlock(dest, &(block->color_block));
|
||||
_bc4_decoder->DecodeBlock(dest, &(block->alpha_block), 3);
|
||||
ColorBlock<4, 4> BC3Decoder::DecodeBlock(const BC3Block &block) const {
|
||||
auto output = _bc1_decoder->DecodeBlock(block.color_block, false);
|
||||
|
||||
_bc4_decoder->DecodeInto(output, block.alpha_block);
|
||||
|
||||
return output;
|
||||
}
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -21,28 +21,33 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../BC1/BC1Decoder.h"
|
||||
#include "../BC4/BC4Decoder.h"
|
||||
#include "../BlockDecoder.h"
|
||||
#include "../BlockView.h"
|
||||
#include "../Interpolator.h"
|
||||
#include "../ndebug.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../../Decoder.h"
|
||||
#include "../../Texture.h"
|
||||
#include "../bc1/BC1Decoder.h"
|
||||
#include "../bc4/BC4Decoder.h"
|
||||
#include "../interpolator/Interpolator.h"
|
||||
#include "BC3Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
class BC3Decoder : public BlockDecoder<BC3Block, 4, 4> {
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class BC3Decoder : public BlockDecoder<BlockTexture<BC3Block>> {
|
||||
public:
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
using BC1DecoderPtr = std::shared_ptr<BC1Decoder>;
|
||||
using BC4DecoderPtr = std::shared_ptr<BC4Decoder>;
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
|
||||
BC3Decoder(InterpolatorPtr interpolator = std::make_shared<Interpolator>()) : BC3Decoder(std::make_shared<BC1Decoder>(interpolator)) {}
|
||||
BC3Decoder(BC1DecoderPtr bc1_decoder, BC4DecoderPtr bc4_decoder = std::make_shared<BC4Decoder>()) : _bc1_decoder(bc1_decoder), _bc4_decoder(bc4_decoder) {}
|
||||
BC3Decoder(InterpolatorPtr interpolator) : _bc1_decoder(std::make_shared<BC1Decoder>(interpolator)), _bc4_decoder(std::make_shared<BC4Decoder>(3)) {}
|
||||
|
||||
void DecodeBlock(Color4x4 dest, BC3Block *const block) const noexcept(ndebug) override;
|
||||
BC3Decoder() : BC3Decoder(std::make_shared<Interpolator>()) {}
|
||||
|
||||
ColorBlock<4, 4> DecodeBlock(const BC3Block &block) const override;
|
||||
|
||||
BC1DecoderPtr GetBC1Decoder() const { return _bc1_decoder; }
|
||||
BC4DecoderPtr GetBC4Decoder() const { return _bc4_decoder; }
|
||||
|
||||
private:
|
||||
const BC1DecoderPtr _bc1_decoder;
|
||||
const BC4DecoderPtr _bc4_decoder;
|
||||
};
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -17,18 +17,18 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "BC3Encoder.h"
|
||||
|
||||
#include "../BC1/BC1Block.h"
|
||||
#include "../BC4/BC4Block.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../bc1/BC1Block.h"
|
||||
#include "../bc4/BC4Block.h"
|
||||
#include "BC3Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
class BC3Block {
|
||||
public:
|
||||
BC4Block alpha_block;
|
||||
BC1Block color_block;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
} // namespace rgbcx
|
||||
namespace quicktex::s3tc {
|
||||
BC3Block BC3Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const {
|
||||
auto output = BC3Block();
|
||||
output.color_block = _bc1_encoder->EncodeBlock(pixels);
|
||||
output.alpha_block = _bc4_encoder->EncodeBlock(pixels);
|
||||
return output;
|
||||
}
|
||||
} // namespace quicktex::s3tc
|
54
quicktex/s3tc/bc3/BC3Encoder.h
Normal file
54
quicktex/s3tc/bc3/BC3Encoder.h
Normal file
@ -0,0 +1,54 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../../Encoder.h"
|
||||
#include "../../Texture.h"
|
||||
#include "../bc1/BC1Encoder.h"
|
||||
#include "../bc4/BC4Encoder.h"
|
||||
#include "../interpolator/Interpolator.h"
|
||||
#include "BC3Block.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class BC3Encoder : public BlockEncoder<BlockTexture<BC3Block>> {
|
||||
public:
|
||||
using BC1EncoderPtr = std::shared_ptr<BC1Encoder>;
|
||||
using BC4EncoderPtr = std::shared_ptr<BC4Encoder>;
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
|
||||
BC3Encoder(unsigned level, InterpolatorPtr interpolator)
|
||||
: _bc1_encoder(std::make_shared<BC1Encoder>(level, BC1Encoder::ColorMode::FourColor, interpolator)), _bc4_encoder(std::make_shared<BC4Encoder>(3)) {}
|
||||
|
||||
BC3Encoder(unsigned level = 5) : BC3Encoder(level, std::make_shared<Interpolator>()) {}
|
||||
|
||||
BC3Block EncodeBlock(const ColorBlock<4, 4>& pixels) const override;
|
||||
|
||||
BC1EncoderPtr GetBC1Encoder() const { return _bc1_encoder; }
|
||||
BC4EncoderPtr GetBC4Encoder() const { return _bc4_encoder; }
|
||||
|
||||
private:
|
||||
const BC1EncoderPtr _bc1_encoder;
|
||||
const BC4EncoderPtr _bc4_encoder;
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
1
quicktex/s3tc/bc3/__init__.py
Normal file
1
quicktex/s3tc/bc3/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from _quicktex._s3tc._bc3 import *
|
122
quicktex/s3tc/bc3/_bindings.cpp
Normal file
122
quicktex/s3tc/bc3/_bindings.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include "../../_bindings.h"
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "../../Decoder.h"
|
||||
#include "../../Encoder.h"
|
||||
#include "../interpolator/Interpolator.h"
|
||||
#include "BC3Decoder.h"
|
||||
#include "BC3Encoder.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
namespace quicktex::bindings {
|
||||
|
||||
using namespace quicktex::s3tc;
|
||||
using namespace pybind11::literals;
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
using BC1EncoderPtr = std::shared_ptr<BC1Encoder>;
|
||||
using BC1DecoderPtr = std::shared_ptr<BC1Decoder>;
|
||||
|
||||
void InitBC3(py::module_ &s3tc) {
|
||||
auto bc3 = s3tc.def_submodule("_bc3", "internal bc3 module");
|
||||
|
||||
// region BC3Block
|
||||
auto bc3_block = BindBlock<BC3Block>(bc3, "BC3Block");
|
||||
bc3_block.doc() = "A single BC3 block.";
|
||||
|
||||
bc3_block.def(py::init<>());
|
||||
bc3_block.def(py::init<BC4Block, BC1Block>(), "alpha_block"_a, "color_block"_a, R"doc(
|
||||
Create a new BC3Block out of a BC4 block and a BC1 block.
|
||||
|
||||
:param BC4Block alpha_block: The BC4 block used for alpha data.
|
||||
:param BC1Block color_block: The BC1 block used for RGB data.
|
||||
)doc");
|
||||
|
||||
bc3_block.def_readwrite("alpha_block", &BC3Block::alpha_block, "The BC4 block used for alpha data.");
|
||||
bc3_block.def_readwrite("color_block", &BC3Block::color_block, "The BC1 block used for rgb data.");
|
||||
bc3_block.def_property("blocks", &BC3Block::GetBlocks, &BC3Block::SetBlocks, "The BC4 and BC1 blocks that make up this block as a 2-tuple.");
|
||||
// endregion
|
||||
|
||||
// region BC3Texture
|
||||
auto bc3_texture = BindBlockTexture<BC3Block>(bc3, "BC3Texture");
|
||||
bc3_texture.doc() = "A texture comprised of BC3 blocks.";
|
||||
// endregion
|
||||
|
||||
// region BC3Encoder
|
||||
py::class_<BC3Encoder> bc3_encoder(bc3, "BC3Encoder", R"doc(
|
||||
Encodes RGBA textures to BC3
|
||||
)doc");
|
||||
|
||||
bc3_encoder.def(py::init<unsigned>(), "level"_a = 5);
|
||||
bc3_encoder.def(py::init<unsigned, InterpolatorPtr>(), "level"_a, "interpolator"_a, R"doc(
|
||||
Create a new BC3 encoder with the specified preset level and interpolator.
|
||||
|
||||
:param int level: The preset level of the resulting encoder, between 0 and 18 inclusive.
|
||||
See :py:meth:`~quicktex.s3tc.bc1.BC1Encoder.set_level` for more information. Default: 5.
|
||||
:param Interpolator interpolator: The interpolation mode to use for encoding. Default: :py:class:`~quicktex.s3tc.interpolator.Interpolator`.
|
||||
)doc");
|
||||
|
||||
bc3_encoder.def("encode", &BC3Encoder::Encode, "texture"_a, R"doc(
|
||||
Encode a raw texture into a new BC3Texture using the encoder's current settings.
|
||||
|
||||
:param RawTexture texture: Input texture to encode.
|
||||
:returns: A new BC3Texture with the same dimension as the input.
|
||||
)doc");
|
||||
|
||||
bc3_encoder.def_property_readonly("bc1_encoder", &BC3Encoder::GetBC1Encoder,
|
||||
"Internal :py:class:`~quicktex.s3tc.bc1.BC1Encoder` used for RGB data. Readonly.");
|
||||
bc3_encoder.def_property_readonly("bc4_encoder", &BC3Encoder::GetBC4Encoder,
|
||||
"Internal :py:class:`~quicktex.s3tc.bc4.BC4Encoder` used for alpha data. Readonly.");
|
||||
// endregion
|
||||
|
||||
// region BC3Decoder
|
||||
py::class_<BC3Decoder> bc3_decoder(bc3, "BC3Decoder", R"doc(
|
||||
Decodes BC3 textures to RGBA
|
||||
)doc");
|
||||
|
||||
bc3_decoder.def(py::init<>());
|
||||
bc3_decoder.def(py::init<InterpolatorPtr>(), "interpolator"_a, R"doc(
|
||||
Create a new BC3 decoder with the specified interpolator.
|
||||
|
||||
:param Interpolator interpolator: The interpolation mode to use for decoding. Default: :py:class:`~quicktex.s3tc.interpolator.Interpolator`.
|
||||
)doc");
|
||||
|
||||
bc3_decoder.def("decode", &BC3Decoder::Decode, "texture"_a, R"doc(
|
||||
Decode a BC3 texture into a new RawTexture using the decoder's current settings.
|
||||
|
||||
:param RawTexture texture: Input texture to encode.
|
||||
:returns: A new RawTexture with the same dimensions as the input
|
||||
)doc");
|
||||
|
||||
bc3_decoder.def_property_readonly("bc1_decoder", &BC3Decoder::GetBC1Decoder,
|
||||
"Internal :py:class:`~quicktex.s3tc.bc1.BC1Decoder` used for RGB data. Readonly.");
|
||||
bc3_decoder.def_property_readonly("bc4_decoder", &BC3Decoder::GetBC4Decoder,
|
||||
"Internal :py:class:`~quicktex.s3tc.bc4.BC4Decoder` used for alpha data. Readonly.");
|
||||
// endregion
|
||||
}
|
||||
} // namespace quicktex::bindings
|
69
quicktex/s3tc/bc4/BC4Block.cpp
Normal file
69
quicktex/s3tc/bc4/BC4Block.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include "BC4Block.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../../util.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
BC4Block::SelectorArray BC4Block::GetSelectors() const {
|
||||
auto packed = Pack<uint8_t, uint64_t, 8, SelectorSize>(_selectors);
|
||||
auto rows = Unpack<uint64_t, uint16_t, SelectorBits * Width, Height>(packed);
|
||||
return MapArray(rows, Unpack<uint16_t, uint8_t, SelectorBits, Width>);
|
||||
}
|
||||
|
||||
void BC4Block::SetSelectors(const BC4Block::SelectorArray& unpacked) {
|
||||
for (unsigned y = 0; y < (unsigned)Height; y++) {
|
||||
if (std::any_of(unpacked[y].begin(), unpacked[y].end(), [](uint8_t i) { return i > SelectorMax; }))
|
||||
throw std::invalid_argument("Selector value out of bounds.");
|
||||
}
|
||||
auto rows = MapArray(unpacked, Pack<uint8_t, uint16_t, SelectorBits, Width>);
|
||||
auto packed = Pack<uint16_t, uint64_t, SelectorBits * Width, Height>(rows);
|
||||
_selectors = Unpack<uint64_t, uint8_t, 8, SelectorSize>(packed);
|
||||
}
|
||||
|
||||
std::array<uint8_t, 8> BC4Block::GetValues6() const {
|
||||
return {alpha0,
|
||||
alpha1,
|
||||
static_cast<uint8_t>((alpha0 * 4 + alpha1) / 5),
|
||||
static_cast<uint8_t>((alpha0 * 3 + alpha1 * 2) / 5),
|
||||
static_cast<uint8_t>((alpha0 * 2 + alpha1 * 3) / 5),
|
||||
static_cast<uint8_t>((alpha0 + alpha1 * 4) / 5),
|
||||
0,
|
||||
0xFF};
|
||||
}
|
||||
|
||||
std::array<uint8_t, 8> BC4Block::GetValues8() const {
|
||||
return {alpha0,
|
||||
alpha1,
|
||||
static_cast<uint8_t>((alpha0 * 6 + alpha1) / 7),
|
||||
static_cast<uint8_t>((alpha0 * 5 + alpha1 * 2) / 7),
|
||||
static_cast<uint8_t>((alpha0 * 4 + alpha1 * 3) / 7),
|
||||
static_cast<uint8_t>((alpha0 * 3 + alpha1 * 4) / 7),
|
||||
static_cast<uint8_t>((alpha0 * 2 + alpha1 * 5) / 7),
|
||||
static_cast<uint8_t>((alpha0 + alpha1 * 6) / 7)};
|
||||
}
|
||||
|
||||
bool BC4Block::operator==(const BC4Block& Rhs) const { return alpha0 == Rhs.alpha0 && alpha1 == Rhs.alpha1 && _selectors == Rhs._selectors; }
|
||||
bool BC4Block::operator!=(const BC4Block& Rhs) const { return !(Rhs == *this); }
|
||||
} // namespace quicktex::s3tc
|
107
quicktex/s3tc/bc4/BC4Block.h
Normal file
107
quicktex/s3tc/bc4/BC4Block.h
Normal file
@ -0,0 +1,107 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class alignas(8) BC4Block {
|
||||
public:
|
||||
static constexpr size_t Width = 4;
|
||||
static constexpr size_t Height = 4;
|
||||
|
||||
static constexpr size_t SelectorSize = 6; // size of selector array in bytes
|
||||
static constexpr size_t SelectorBits = 3; // size of a selector in bits
|
||||
static constexpr size_t SelectorMax = (1 << SelectorBits) - 1; // maximum value of a selector
|
||||
|
||||
using SelectorArray = std::array<std::array<uint8_t, Width>, Height>;
|
||||
using AlphaPair = std::pair<uint8_t, uint8_t>;
|
||||
|
||||
uint8_t alpha0; // first endpoint
|
||||
uint8_t alpha1; // second endpoint
|
||||
|
||||
private:
|
||||
std::array<uint8_t, SelectorSize> _selectors; // internal array of selector bytes
|
||||
|
||||
public:
|
||||
// Constructors
|
||||
|
||||
/// Create a new BC4Block
|
||||
constexpr BC4Block() : alpha0(0), alpha1(0), _selectors() {
|
||||
static_assert(sizeof(BC4Block) == 8);
|
||||
static_assert(sizeof(std::array<BC4Block, 10>) == 8 * 10);
|
||||
static_assert(alignof(BC4Block) >= 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new BC4Block
|
||||
* @param valpha0 first endpoint value
|
||||
* @param valpha1 second endpoint value
|
||||
* @param selectors the selectors as a 4x4 array of integers, between 0 and 7 inclusive.
|
||||
*/
|
||||
BC4Block(uint8_t valpha0, uint8_t valpha1, const SelectorArray& selectors) {
|
||||
alpha0 = valpha0;
|
||||
alpha1 = valpha1;
|
||||
SetSelectors(selectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new solid BC4Block
|
||||
* @param alpha first endpoint value
|
||||
*/
|
||||
BC4Block(uint8_t alpha) {
|
||||
alpha0 = alpha;
|
||||
alpha1 = alpha;
|
||||
_selectors.fill(0);
|
||||
}
|
||||
|
||||
/// Get a alpha0 and alpha1 as a pair
|
||||
AlphaPair GetAlphas() const { return AlphaPair(alpha0, alpha1); }
|
||||
|
||||
/// Set alpha0 and alpha1 as a pair
|
||||
void SetAlphas(AlphaPair as) {
|
||||
alpha0 = as.first;
|
||||
alpha1 = as.second;
|
||||
}
|
||||
|
||||
/// Get the block's selectors as a 4x4 array of integers between 0 and 7 inclusive.
|
||||
SelectorArray GetSelectors() const;
|
||||
|
||||
/// Get the block's selectors as a 4x4 array of integers between 0 and 7 inclusive.
|
||||
void SetSelectors(const SelectorArray& unpacked);
|
||||
|
||||
/// True if the block uses 6-value interpolation, i.e. alpha0 <= alpha1.
|
||||
bool Is6Value() const { return alpha0 <= alpha1; }
|
||||
|
||||
/// The interpolated values of this block as an array of 8 integers.
|
||||
std::array<uint8_t, 8> GetValues() const { return Is6Value() ? GetValues6() : GetValues8(); }
|
||||
|
||||
bool operator==(const BC4Block& Rhs) const;
|
||||
bool operator!=(const BC4Block& Rhs) const;
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 8> GetValues6() const;
|
||||
std::array<uint8_t, 8> GetValues8() const;
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -22,22 +22,31 @@
|
||||
#include <array> // for array
|
||||
#include <cassert> // for assert
|
||||
|
||||
#include "../BlockView.h" // for ColorBlock
|
||||
#include "../ndebug.h" // for ndebug
|
||||
#include "../../Color.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "BC4Block.h"
|
||||
|
||||
void rgbcx::BC4Decoder::DecodeBlock(Byte4x4 dest, BC4Block *const block) const noexcept(ndebug) {
|
||||
auto l = block->GetLowAlpha();
|
||||
auto h = block->GetHighAlpha();
|
||||
|
||||
auto values = BC4Block::GetValues(l, h);
|
||||
auto selectors = block->UnpackSelectors();
|
||||
namespace quicktex::s3tc {
|
||||
void BC4Decoder::DecodeInto(ColorBlock<4, 4> &dest, const BC4Block &block) const {
|
||||
auto values = block.GetValues();
|
||||
auto selectors = block.GetSelectors();
|
||||
|
||||
for (unsigned y = 0; y < 4; y++) {
|
||||
for (unsigned x = 0; x < 4; x++) {
|
||||
const auto selector = selectors[y][x];
|
||||
assert(selector < 8);
|
||||
dest.Set(x, y, values[selector]);
|
||||
|
||||
auto color = dest.Get(x, y);
|
||||
color[_channel] = values[selector];
|
||||
dest.Set(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColorBlock<4, 4> BC4Decoder::DecodeBlock(const BC4Block &block) const {
|
||||
auto output = ColorBlock<4, 4>();
|
||||
DecodeInto(output, block);
|
||||
|
||||
return output;
|
||||
}
|
||||
} // namespace quicktex::s3tc
|
48
quicktex/s3tc/bc4/BC4Decoder.h
Normal file
48
quicktex/s3tc/bc4/BC4Decoder.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../../Decoder.h"
|
||||
#include "../../Texture.h"
|
||||
#include "BC4Block.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class BC4Decoder : public BlockDecoder<BlockTexture<BC4Block>> {
|
||||
public:
|
||||
BC4Decoder(uint8_t channel = 3) {
|
||||
if (channel >= 4U) throw std::invalid_argument("Channel out of range");
|
||||
_channel = channel;
|
||||
}
|
||||
|
||||
ColorBlock<4, 4> DecodeBlock(const BC4Block &block) const override;
|
||||
|
||||
void DecodeInto(ColorBlock<4, 4> &dest, const BC4Block &block) const;
|
||||
|
||||
uint8_t GetChannel() const { return _channel; }
|
||||
|
||||
private:
|
||||
uint8_t _channel;
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -19,30 +19,30 @@
|
||||
|
||||
#include "BC4Encoder.h"
|
||||
|
||||
#include <algorithm> // for minmax_element
|
||||
#include <array> // for array
|
||||
#include <cstdint> // for uint8_t
|
||||
#include <utility> // for pair
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#include "BC4Block.h" // for BC4Block
|
||||
#include "../../Color.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "BC4Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
void BC4Encoder::EncodeBlock(Byte4x4 pixels, BC4Block *const dest) const noexcept(ndebug) {
|
||||
auto flattened = pixels.Flatten();
|
||||
auto minmax = std::minmax_element(flattened.begin(), flattened.end());
|
||||
namespace quicktex::s3tc {
|
||||
BC4Block BC4Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const {
|
||||
uint8_t min = UINT8_MAX;
|
||||
uint8_t max = 0;
|
||||
|
||||
uint8_t min = *minmax.first;
|
||||
uint8_t max = *minmax.second;
|
||||
|
||||
dest->high_alpha = min;
|
||||
dest->low_alpha = max;
|
||||
|
||||
if (max == min) {
|
||||
dest->SetSelectorBits(0);
|
||||
return;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
auto value = pixels.Get(i)[_channel];
|
||||
min = std::min(min, value);
|
||||
max = std::max(max, value);
|
||||
}
|
||||
|
||||
std::array<uint8_t, 16> selectors = {};
|
||||
if (max == min) {
|
||||
return BC4Block(min); // solid block
|
||||
}
|
||||
|
||||
auto selectors = BC4Block::SelectorArray();
|
||||
const static std::array<uint8_t, 8> Levels = {1U, 7U, 6U, 5U, 4U, 3U, 2U, 0U}; // selector value options in linear order
|
||||
|
||||
// BC4 floors in its divisions, which we compensate for with the 4 bias.
|
||||
@ -56,16 +56,19 @@ void BC4Encoder::EncodeBlock(Byte4x4 pixels, BC4Block *const dest) const noexcep
|
||||
for (unsigned i = 0; i < 7; i++) thresholds[i] = delta * (1 + (2 * (int)i)) - bias;
|
||||
|
||||
// iterate over all values and calculate selectors
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
int value = flattened[i] * 14; // multiply by demonimator
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 4; x++) {
|
||||
int value = (int)pixels.Get(x, y)[_channel] * 14; // multiply by demonimator
|
||||
|
||||
// level = number of thresholds this value is greater than
|
||||
unsigned level = 0;
|
||||
for (unsigned c = 0; c < 7; c++) level += value >= thresholds[c];
|
||||
// level = number of thresholds this value is greater than
|
||||
unsigned level = 0;
|
||||
for (unsigned c = 0; c < 7; c++) level += value >= thresholds[c];
|
||||
|
||||
selectors[i] = Levels[level];
|
||||
selectors[(unsigned)y][(unsigned)x] = Levels[level];
|
||||
}
|
||||
}
|
||||
|
||||
dest->PackSelectors(selectors);
|
||||
return BC4Block(max, min, selectors);
|
||||
}
|
||||
} // namespace rgbcx
|
||||
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -19,25 +19,28 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../BlockEncoder.h"
|
||||
#include "../BlockView.h"
|
||||
#include "../ndebug.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../../Encoder.h"
|
||||
#include "../../Texture.h"
|
||||
#include "BC4Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class BC4Encoder : public BlockEncoder<BC4Block, 4, 4> {
|
||||
class BC4Encoder : public BlockEncoder<BlockTexture<BC4Block>> {
|
||||
public:
|
||||
BC4Encoder(const uint8_t channel) : _channel(channel) { assert(channel < 4); }
|
||||
BC4Encoder(const uint8_t channel) {
|
||||
if (channel >= 4) throw std::invalid_argument("Channel out of range");
|
||||
_channel = channel;
|
||||
}
|
||||
|
||||
void EncodeBlock(Color4x4 pixels, BC4Block *dest) const override { EncodeBlock(pixels.GetChannel(_channel), dest); }
|
||||
void EncodeBlock(Color4x4 pixels, BC4Block *const dest, uint8_t channel) const noexcept(ndebug) { EncodeBlock(pixels.GetChannel(channel), dest); }
|
||||
void EncodeBlock(Byte4x4 pixels, BC4Block *const dest) const noexcept(ndebug);
|
||||
BC4Block EncodeBlock(const ColorBlock<4, 4> &pixels) const override;
|
||||
|
||||
uint8_t GetChannel() const { return _channel; }
|
||||
|
||||
private:
|
||||
const uint8_t _channel;
|
||||
uint8_t _channel;
|
||||
};
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex::s3tc
|
1
quicktex/s3tc/bc4/__init__.py
Normal file
1
quicktex/s3tc/bc4/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from _quicktex._s3tc._bc4 import *
|
125
quicktex/s3tc/bc4/_bindings.cpp
Normal file
125
quicktex/s3tc/bc4/_bindings.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include "../../_bindings.h"
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "../../Decoder.h"
|
||||
#include "../../Encoder.h"
|
||||
#include "BC4Decoder.h"
|
||||
#include "BC4Encoder.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
namespace quicktex::bindings {
|
||||
|
||||
using namespace quicktex::s3tc;
|
||||
|
||||
void InitBC4(py::module_ &s3tc) {
|
||||
auto bc4 = s3tc.def_submodule("_bc4", "internal bc4 module");
|
||||
|
||||
// region BC4Block
|
||||
auto bc4_block = BindBlock<BC4Block>(bc4, "BC4Block");
|
||||
bc4_block.doc() = "A single BC4 block.";
|
||||
|
||||
bc4_block.def(py::init<>());
|
||||
bc4_block.def(py::init<uint8_t, uint8_t, BC4Block::SelectorArray>(), "endpoint0"_a, "endpoint1"_a, "selectors"_a, R"doc(
|
||||
Create a new BC4Block with the specified endpoints and selectors.
|
||||
|
||||
:param int endpoint0: The first endpoint.
|
||||
:param int endpoint1: The second endpoint.
|
||||
:param selectors: the selectors as a 4x4 list of integers, between 0 and 7 inclusive.
|
||||
)doc");
|
||||
|
||||
bc4_block.def_property("endpoints", &BC4Block::GetAlphas, &BC4Block::SetAlphas, "The block's endpoint values as a 2-tuple.");
|
||||
bc4_block.def_property("selectors", &BC4Block::GetSelectors, &BC4Block::SetSelectors, R"doc(
|
||||
The block's selectors as a 4x4 list of integers between 0 and 7 inclusive.
|
||||
|
||||
.. note::
|
||||
This is a property, so directly modifying its value will not propogate back to the block.
|
||||
Instead you must read, modify, then write the new list back to the property, like so::
|
||||
|
||||
selectors = block.selectors
|
||||
selectors[0,0] = 0
|
||||
block.selectors = selectors
|
||||
)doc");
|
||||
bc4_block.def_property_readonly("values", &BC4Block::GetValues, R"doc(
|
||||
The interpolated values used to decode the block, coresponding with the indices in :py:attr:`selectors`.
|
||||
)doc");
|
||||
bc4_block.def_property_readonly("is_6value", &BC4Block::Is6Value, R"doc(
|
||||
"True if the block uses 6-value interpolation, i.e. endpoint0 <= endpoint1. Readonly.
|
||||
)doc");
|
||||
// endregion
|
||||
|
||||
// region BC4Texture
|
||||
auto bc4_texture = BindBlockTexture<BC4Block>(bc4, "BC4Texture");
|
||||
bc4_texture.doc() = "A texture comprised of BC4 blocks.";
|
||||
// endregion
|
||||
|
||||
// region BC4Encoder
|
||||
py::class_<BC4Encoder> bc4_encoder(bc4, "BC4Encoder", R"doc(
|
||||
Encodes single-channel textures to BC4.
|
||||
)doc");
|
||||
|
||||
bc4_encoder.def(py::init<uint8_t>(), py::arg("channel") = 3, R"doc(
|
||||
Create a new BC4 encoder with the specified channel
|
||||
|
||||
:param int channel: the channel that will be read from. 0 to 3 inclusive. Default: 3 (alpha).
|
||||
)doc");
|
||||
|
||||
bc4_encoder.def("encode", &BC4Encoder::Encode, "texture"_a, R"doc(
|
||||
Encode a raw texture into a new BC4Texture using the encoder's current settings.
|
||||
|
||||
:param RawTexture texture: Input texture to encode.
|
||||
:returns: A new BC4Texture with the same dimension as the input.
|
||||
)doc");
|
||||
|
||||
bc4_encoder.def_property_readonly("channel", &BC4Encoder::GetChannel, "The channel that will be read from. 0 to 3 inclusive. Readonly.");
|
||||
// endregion
|
||||
|
||||
// region BC4Decoder
|
||||
py::class_<BC4Decoder> bc4_decoder(bc4, "BC4Decoder", R"doc(
|
||||
Decodes BC4 textures to a single-channel.
|
||||
)doc");
|
||||
|
||||
bc4_decoder.def(py::init<uint8_t>(), py::arg("channel") = 3, R"doc(
|
||||
Create a new BC4 decoder with the specified channel
|
||||
|
||||
:param int channel: The channel that will be written to. 0 to 3 inclusive. Default: 3 (alpha).
|
||||
)doc");
|
||||
|
||||
bc4_decoder.def("decode", &BC4Decoder::Decode, "texture"_a, R"doc(
|
||||
Decode a BC4 texture into a new RawTexture using the decoder's current settings.
|
||||
|
||||
:param RawTexture texture: Input texture to encode.
|
||||
:returns: A new RawTexture with the same dimensions as the input
|
||||
)doc");
|
||||
|
||||
bc4_decoder.def_property_readonly("channel", &BC4Decoder::GetChannel, "The channel that will be written to. 0 to 3 inclusive. Readonly.");
|
||||
// endregion
|
||||
}
|
||||
|
||||
} // namespace quicktex::bindings
|
59
quicktex/s3tc/bc5/BC5Block.h
Normal file
59
quicktex/s3tc/bc5/BC5Block.h
Normal file
@ -0,0 +1,59 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "../bc4/BC4Block.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class alignas(8) BC5Block {
|
||||
public:
|
||||
static constexpr int Width = 4;
|
||||
static constexpr int Height = 4;
|
||||
|
||||
using BlockPair = std::pair<BC4Block, BC4Block>;
|
||||
|
||||
BC4Block chan0_block;
|
||||
BC4Block chan1_block;
|
||||
|
||||
constexpr BC5Block() : chan0_block(BC4Block()), chan1_block(BC4Block()) {
|
||||
static_assert(sizeof(BC5Block) == 16);
|
||||
static_assert(sizeof(std::array<BC5Block, 10>) == 16 * 10);
|
||||
static_assert(alignof(BC5Block) >= 8);
|
||||
}
|
||||
|
||||
BC5Block(const BC4Block &chan0, const BC4Block &chan1) {
|
||||
chan0_block = chan0;
|
||||
chan1_block = chan1;
|
||||
}
|
||||
|
||||
BlockPair GetBlocks() const { return BlockPair(chan0_block, chan1_block); }
|
||||
|
||||
void SetBlocks(const BlockPair &pair) {
|
||||
chan0_block = pair.first;
|
||||
chan1_block = pair.second;
|
||||
}
|
||||
|
||||
bool operator==(const BC5Block &Rhs) const { return chan0_block == Rhs.chan0_block && chan1_block == Rhs.chan1_block; }
|
||||
bool operator!=(const BC5Block &Rhs) const { return !(Rhs == *this); }
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -19,14 +19,14 @@
|
||||
|
||||
#include "BC5Decoder.h"
|
||||
|
||||
#include "../BlockView.h"
|
||||
#include "../ndebug.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "BC5Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
|
||||
void BC5Decoder::DecodeBlock(Color4x4 dest, BC5Block *const block) const noexcept(ndebug) {
|
||||
_bc4_decoder->DecodeBlock(dest, &block->chan0_block, _chan0);
|
||||
_bc4_decoder->DecodeBlock(dest, &block->chan1_block, _chan1);
|
||||
namespace quicktex::s3tc {
|
||||
ColorBlock<4, 4> BC5Decoder::DecodeBlock(const BC5Block &block) const {
|
||||
auto output = ColorBlock<4, 4>();
|
||||
_chan0_decoder->DecodeInto(output, block.chan0_block);
|
||||
_chan1_decoder->DecodeInto(output, block.chan1_block);
|
||||
return output;
|
||||
}
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex::s3tc
|
54
quicktex/s3tc/bc5/BC5Decoder.h
Normal file
54
quicktex/s3tc/bc5/BC5Decoder.h
Normal file
@ -0,0 +1,54 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../../Decoder.h"
|
||||
#include "../../Texture.h"
|
||||
#include "../bc4/BC4Decoder.h"
|
||||
#include "BC5Block.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class BC5Decoder : public BlockDecoder<BlockTexture<BC5Block>> {
|
||||
public:
|
||||
using ChannelPair = std::tuple<uint8_t, uint8_t>;
|
||||
using BC4DecoderPtr = std::shared_ptr<BC4Decoder>;
|
||||
using BC4DecoderPair = std::tuple<BC4DecoderPtr, BC4DecoderPtr>;
|
||||
|
||||
BC5Decoder(uint8_t chan0 = 0, uint8_t chan1 = 1) : BC5Decoder(std::make_shared<BC4Decoder>(chan0), std::make_shared<BC4Decoder>(chan1)) {}
|
||||
BC5Decoder(BC4DecoderPtr chan0_decoder, BC4DecoderPtr chan1_decoder) : _chan0_decoder(chan0_decoder), _chan1_decoder(chan1_decoder) {}
|
||||
|
||||
ColorBlock<4, 4> DecodeBlock(const BC5Block &block) const override;
|
||||
|
||||
ChannelPair GetChannels() const { return ChannelPair(_chan0_decoder->GetChannel(), _chan1_decoder->GetChannel()); }
|
||||
|
||||
BC4DecoderPair GetBC4Decoders() const { return BC4DecoderPair(_chan0_decoder, _chan1_decoder); }
|
||||
|
||||
private:
|
||||
const BC4DecoderPtr _chan0_decoder;
|
||||
const BC4DecoderPtr _chan1_decoder;
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -17,17 +17,16 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "BC5Encoder.h"
|
||||
|
||||
#include "../BC4/BC4Block.h"
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../bc4/BC4Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
class BC5Block {
|
||||
public:
|
||||
BC4Block chan0_block;
|
||||
BC4Block chan1_block;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
} // namespace rgbcx
|
||||
namespace quicktex::s3tc {
|
||||
BC5Block BC5Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const {
|
||||
auto output = BC5Block();
|
||||
output.chan0_block = _chan0_encoder->EncodeBlock(pixels);
|
||||
output.chan1_block = _chan1_encoder->EncodeBlock(pixels);
|
||||
return output;
|
||||
}
|
||||
} // namespace quicktex::s3tc
|
53
quicktex/s3tc/bc5/BC5Encoder.h
Normal file
53
quicktex/s3tc/bc5/BC5Encoder.h
Normal file
@ -0,0 +1,53 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../../ColorBlock.h"
|
||||
#include "../../Encoder.h"
|
||||
#include "../../Texture.h"
|
||||
#include "../bc4/BC4Encoder.h"
|
||||
#include "BC5Block.h"
|
||||
|
||||
namespace quicktex::s3tc {
|
||||
class BC5Encoder : public BlockEncoder<BlockTexture<BC5Block>> {
|
||||
public:
|
||||
using ChannelPair = std::tuple<uint8_t, uint8_t>;
|
||||
using BC4EncoderPtr = std::shared_ptr<BC4Encoder>;
|
||||
using BC4EncoderPair = std::tuple<BC4EncoderPtr, BC4EncoderPtr>;
|
||||
|
||||
BC5Encoder(uint8_t chan0 = 0, uint8_t chan1 = 1) : BC5Encoder(std::make_shared<BC4Encoder>(chan0), std::make_shared<BC4Encoder>(chan1)) {}
|
||||
BC5Encoder(BC4EncoderPtr chan0_encoder, BC4EncoderPtr chan1_encoder) : _chan0_encoder(chan0_encoder), _chan1_encoder(chan1_encoder) {}
|
||||
|
||||
BC5Block EncodeBlock(const ColorBlock<4, 4> &pixels) const override;
|
||||
|
||||
ChannelPair GetChannels() const { return ChannelPair(_chan0_encoder->GetChannel(), _chan1_encoder->GetChannel()); }
|
||||
|
||||
BC4EncoderPair GetBC4Encoders() const { return BC4EncoderPair(_chan0_encoder, _chan1_encoder); }
|
||||
|
||||
private:
|
||||
const BC4EncoderPtr _chan0_encoder;
|
||||
const BC4EncoderPtr _chan1_encoder;
|
||||
};
|
||||
} // namespace quicktex::s3tc
|
1
quicktex/s3tc/bc5/__init__.py
Normal file
1
quicktex/s3tc/bc5/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from _quicktex._s3tc._bc5 import *
|
111
quicktex/s3tc/bc5/_bindings.cpp
Normal file
111
quicktex/s3tc/bc5/_bindings.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include "../../_bindings.h"
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../Decoder.h"
|
||||
#include "../../Encoder.h"
|
||||
#include "BC5Decoder.h"
|
||||
#include "BC5Encoder.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
namespace quicktex::bindings {
|
||||
|
||||
using namespace quicktex::s3tc;
|
||||
using namespace quicktex::s3tc;
|
||||
|
||||
void InitBC5(py::module_ &s3tc) {
|
||||
auto bc5 = s3tc.def_submodule("_bc5", "internal bc5 module");
|
||||
|
||||
// region BC5Block
|
||||
auto bc5_block = BindBlock<BC5Block>(bc5, "BC5Block");
|
||||
bc5_block.doc() = "A single BC5 block.";
|
||||
|
||||
bc5_block.def(py::init<>());
|
||||
bc5_block.def(py::init<BC4Block, BC4Block>(), "chan0_block"_a, "chan1_block"_a, R"doc(
|
||||
Create a new BC5Block out of two BC4 blocks.
|
||||
|
||||
:param BC4Block chan0_block: The BC4 block used for the first channel.
|
||||
:param BC4Block chan1_block: The BC1 block used for the second channel.
|
||||
)doc");
|
||||
|
||||
bc5_block.def_readwrite("chan0_block", &BC5Block::chan0_block, "The BC4 block used for the first channel.");
|
||||
bc5_block.def_readwrite("chan1_block", &BC5Block::chan1_block, "The BC4 block used for the second channel.");
|
||||
bc5_block.def_property("blocks", &BC5Block::GetBlocks, &BC5Block::SetBlocks, "The BC4 and BC1 blocks that make up this block as a 2-tuple.");
|
||||
// endregion
|
||||
|
||||
// region BC5Texture
|
||||
auto bc5_texture = BindBlockTexture<BC5Block>(bc5, "BC5Texture");
|
||||
bc5_texture.doc() = "A texture comprised of BC5 blocks.";
|
||||
// endregion
|
||||
|
||||
// region BC5Encoder
|
||||
py::class_<BC5Encoder> bc5_encoder(bc5, "BC5Encoder", R"doc(
|
||||
Encodes dual-channel textures to BC5.
|
||||
)doc");
|
||||
|
||||
bc5_encoder.def(py::init<uint8_t, uint8_t>(), py::arg("chan0") = 0, py::arg("chan1") = 1, R"doc(
|
||||
Create a new BC5 encoder with the specified channels
|
||||
|
||||
:param int chan0: the first channel that will be read from. 0 to 3 inclusive. Default: 0 (red).
|
||||
:param int chan1: the second channel that will be read from. 0 to 3 inclusive. Default: 1 (green).
|
||||
)doc");
|
||||
|
||||
bc5_encoder.def("encode", &BC5Encoder::Encode, "texture"_a, R"doc(
|
||||
Encode a raw texture into a new BC5Texture using the encoder's current settings.
|
||||
|
||||
:param RawTexture texture: Input texture to encode.
|
||||
:returns: A new BC5Texture with the same dimension as the input.
|
||||
)doc");
|
||||
|
||||
bc5_encoder.def_property_readonly("channels", &BC5Encoder::GetChannels, "A 2-tuple of channels that will be read from. 0 to 3 inclusive. Readonly.");
|
||||
bc5_encoder.def_property_readonly("bc4_encoders", &BC5Encoder::GetBC4Encoders,
|
||||
"2-tuple of internal :py:class:`~quicktex.s3tc.bc4.BC4Encoder` s used for each channel. Readonly.");
|
||||
// endregion
|
||||
|
||||
// region BC5Decoder
|
||||
py::class_<BC5Decoder> bc5_decoder(bc5, "BC5Decoder", R"doc(
|
||||
Decodes BC4 textures to two channels.
|
||||
)doc");
|
||||
|
||||
bc5_decoder.def(py::init<uint8_t, uint8_t>(), py::arg("chan0") = 0, py::arg("chan1") = 1, R"doc(
|
||||
Create a new BC5 decoder with the specified channels
|
||||
|
||||
:param int chan0: the first channel that will be written to. 0 to 3 inclusive. Default: 0 (red).
|
||||
:param int chan1: the second channel that will be written to. 0 to 3 inclusive. Default: 1 (green).
|
||||
)doc");
|
||||
|
||||
bc5_decoder.def("decode", &BC5Decoder::Decode, "texture"_a, R"doc(
|
||||
Decode a BC5 texture into a new RawTexture using the decoder's current settings.
|
||||
|
||||
:param RawTexture texture: Input texture to encode.
|
||||
:returns: A new RawTexture with the same dimensions as the input
|
||||
)doc");
|
||||
|
||||
bc5_decoder.def_property_readonly("channels", &BC5Decoder::GetChannels, "A 2-tuple of channels that will be written to. 0 to 3 inclusive. Readonly.");
|
||||
bc5_decoder.def_property_readonly("bc4_decoders", &BC5Decoder::GetBC4Decoders,
|
||||
"2-tuple of internal :py:class:`~quicktex.s3tc.bc4.BC4Decoder` s used for each channel. Readonly.");
|
||||
// endregion
|
||||
}
|
||||
} // namespace quicktex::bindings
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -24,69 +24,10 @@
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "util.h"
|
||||
#include "../../util.h"
|
||||
#include "../../Color.h"
|
||||
|
||||
namespace rgbcx {
|
||||
|
||||
/*
|
||||
Interpolator::Interpolator() {
|
||||
PrepSingleColorTables(_single_match5, _single_match5_half, 5);
|
||||
PrepSingleColorTables(_single_match5, _single_match5_half, 6);
|
||||
}
|
||||
|
||||
void Interpolator::PrepSingleColorTables(const MatchListPtr &matchTable, const MatchListPtr &matchTableHalf, int len) {
|
||||
int size = 1 << len;
|
||||
|
||||
assert((len == 5 && size == Size5) || (len == 6 && size == size6));
|
||||
|
||||
const uint8_t *expand = (len == 5) ? &Expand5[0] : &Expand6[0];
|
||||
|
||||
bool ideal = IsIdeal();
|
||||
bool use_e = useExpandedInMatch();
|
||||
|
||||
for (int i = 0; i < match_count; i++) {
|
||||
int lowest_error = 256;
|
||||
int lowest_half_error = 256;
|
||||
|
||||
for (int low = 0; low < size; low++) {
|
||||
const int low_e = expand[low];
|
||||
const int low_val = use_e ? low_e : low;
|
||||
|
||||
for (int high = 0; high < size; high++) {
|
||||
const int high_e = expand[high];
|
||||
const int high_val = use_e ? high_e : high;
|
||||
|
||||
int v = (len == 5) ? Interpolate5(high_val, low_val) : Interpolate6(high_val, low_val);
|
||||
int v_half = (len == 5) ? InterpolateHalf5(low_val, high_val) : InterpolateHalf6(low_val, high_val);
|
||||
|
||||
int error = PrepSingleColorTableEntry(matchTable, v, i, low, high, low_e, high_e, lowest_error, false, ideal);
|
||||
int half_error = PrepSingleColorTableEntry(matchTableHalf, v, i, low, high, low_e, high_e, lowest_error, true, ideal);
|
||||
|
||||
if (error < lowest_error) lowest_error = error;
|
||||
if (half_error < lowest_half_error) lowest_half_error = half_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int Interpolator::PrepSingleColorTableEntry(const MatchListPtr &matchTable, int v, int i, int low, int high, int low_e, int high_e, int lowest_error, bool half,
|
||||
bool ideal) {
|
||||
int e = iabs(v - i);
|
||||
|
||||
// We only need to factor in 3% error in BC1 ideal mode.
|
||||
if (ideal) e += (iabs(high_e - low_e) * 3) / 100;
|
||||
|
||||
// Favor equal endpoints, for lower error on actual GPU's which approximate the interpolation.
|
||||
if ((e < lowest_error) || (e == lowest_error && low == high)) {
|
||||
assert(e <= UINT8_MAX);
|
||||
|
||||
auto &entry = (*matchTable)[i];
|
||||
entry.low = low;
|
||||
entry.high = high;
|
||||
entry.error = e;
|
||||
}
|
||||
|
||||
return e;
|
||||
}*/
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
// region Interpolator implementation
|
||||
std::unique_ptr<Interpolator> Interpolator::MakeInterpolator(Interpolator::Type type) {
|
||||
@ -109,19 +50,24 @@ uint8_t Interpolator::Interpolate6(uint8_t v0, uint8_t v1) const { return Interp
|
||||
uint8_t Interpolator::InterpolateHalf5(uint8_t v0, uint8_t v1) const { return InterpolateHalf8(scale5To8(v0), scale5To8(v1)); }
|
||||
uint8_t Interpolator::InterpolateHalf6(uint8_t v0, uint8_t v1) const { return InterpolateHalf8(scale6To8(v0), scale6To8(v1)); }
|
||||
|
||||
std::array<Color, 4> Interpolator::InterpolateBC1(uint16_t low, uint16_t high) const {
|
||||
auto colors = std::array<Color, 4>();
|
||||
colors[0] = Color::Unpack565(low);
|
||||
colors[1] = Color::Unpack565(high);
|
||||
std::array<Color, 4> Interpolator::Interpolate565BC1(uint16_t low, uint16_t high, bool allow_3color) const {
|
||||
bool use_3color = allow_3color && (high >= low);
|
||||
return InterpolateBC1(Color::Unpack565Unscaled(low), Color::Unpack565Unscaled(high), use_3color);
|
||||
}
|
||||
|
||||
if (low > high) {
|
||||
// 4-color mode
|
||||
colors[2] = InterpolateColor24(colors[0], colors[1]);
|
||||
colors[3] = InterpolateColor24(colors[1], colors[0]);
|
||||
} else {
|
||||
std::array<Color, 4> Interpolator::InterpolateBC1(Color low, Color high, bool use_3color) const {
|
||||
auto colors = std::array<Color, 4>();
|
||||
colors[0] = low.ScaleFrom565();
|
||||
colors[1] = high.ScaleFrom565();
|
||||
|
||||
if (use_3color) {
|
||||
// 3-color mode
|
||||
colors[2] = InterpolateHalfColor24(colors[0], colors[1]);
|
||||
colors[3] = Color(0, 0, 0, 0); // transparent black
|
||||
} else {
|
||||
// 4-color mode
|
||||
colors[2] = InterpolateColor24(colors[0], colors[1]);
|
||||
colors[3] = InterpolateColor24(colors[1], colors[0]);
|
||||
}
|
||||
|
||||
return colors;
|
||||
@ -147,7 +93,7 @@ uint8_t InterpolatorNvidia::Interpolate5(uint8_t v0, uint8_t v1) const {
|
||||
|
||||
uint8_t InterpolatorNvidia::Interpolate6(uint8_t v0, uint8_t v1) const {
|
||||
assert(v0 < 64 && v1 < 64);
|
||||
const int gdiff = (int) v1 - v0;
|
||||
const int gdiff = (int)v1 - v0;
|
||||
return static_cast<uint8_t>((256 * v0 + (gdiff / 4) + 128 + gdiff * 80) >> 8);
|
||||
}
|
||||
|
||||
@ -158,25 +104,23 @@ uint8_t InterpolatorNvidia::InterpolateHalf5(uint8_t v0, uint8_t v1) const {
|
||||
|
||||
uint8_t InterpolatorNvidia::InterpolateHalf6(uint8_t v0, uint8_t v1) const {
|
||||
assert(v0 < 64 && v1 < 64);
|
||||
const int gdiff = (int) v1 - v0;
|
||||
const int gdiff = (int)v1 - v0;
|
||||
return static_cast<uint8_t>((256 * v0 + gdiff / 4 + 128 + gdiff * 128) >> 8);
|
||||
}
|
||||
|
||||
std::array<Color, 4> InterpolatorNvidia::InterpolateBC1(uint16_t low, uint16_t high) const {
|
||||
std::array<Color, 4> InterpolatorNvidia::InterpolateBC1(Color low, Color high, bool use_3color) const {
|
||||
// Nvidia is special and interpolation cant be done with 8-bit values, so we need to override the default behavior
|
||||
std::array<Color, 4> colors;
|
||||
auto low565 = Color::Unpack565Unscaled(low);
|
||||
auto high565 = Color::Unpack565Unscaled(high);
|
||||
colors[0] = low565.ScaleFrom565();
|
||||
colors[1] = high565.ScaleFrom565();
|
||||
colors[0] = low.ScaleFrom565();
|
||||
colors[1] = high.ScaleFrom565();
|
||||
|
||||
if (low > high) {
|
||||
if (!use_3color) {
|
||||
// 4-color mode
|
||||
colors[2] = InterpolateColor565(low565, high565);
|
||||
colors[3] = InterpolateColor565(high565, low565);
|
||||
colors[2] = InterpolateColor565(low, high);
|
||||
colors[3] = InterpolateColor565(high, low);
|
||||
} else {
|
||||
// 3-color mode
|
||||
colors[2] = InterpolateHalfColor565(low565, high565);
|
||||
colors[2] = InterpolateHalfColor565(low, high);
|
||||
colors[3] = Color(0, 0, 0, 0); // transparent black
|
||||
}
|
||||
|
||||
@ -194,4 +138,4 @@ uint8_t InterpolatorAMD::Interpolate8(uint8_t v0, uint8_t v1) const { return (v0
|
||||
|
||||
uint8_t InterpolatorAMD::InterpolateHalf8(uint8_t v0, uint8_t v1) const { return (v0 + v1 + 1) >> 1; }
|
||||
// endregion
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex::s3tc
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -22,9 +22,9 @@
|
||||
#include <cstdint> // for uint8_t, uint16_t
|
||||
#include <memory> // for unique_ptr
|
||||
|
||||
#include "Color.h" // for Color
|
||||
#include "../../Color.h" // for Color
|
||||
|
||||
namespace rgbcx {
|
||||
namespace quicktex::s3tc {
|
||||
|
||||
class Interpolator {
|
||||
public:
|
||||
@ -94,9 +94,19 @@ class Interpolator {
|
||||
* Generates the 4 colors for a BC1 block from the given 5:6:5-packed colors
|
||||
* @param low first 5:6:5 color for the block
|
||||
* @param high second 5:6:5 color for the block
|
||||
* @return and array of 4 Color values, with indices matching BC1 selectors
|
||||
* @param allow_3color if true, a different interpolation mode will be used if high >= low
|
||||
* @return an array of 4 Color values, with indices matching BC1 selectors
|
||||
*/
|
||||
virtual std::array<Color, 4> InterpolateBC1(uint16_t low, uint16_t high) const;
|
||||
std::array<Color, 4> Interpolate565BC1(uint16_t low, uint16_t high, bool allow_3color = true) const;
|
||||
|
||||
/**
|
||||
* Generates the 4 colors for a BC1 block from the given
|
||||
* @param low the first color for the block, as a seperated 5:6:5 Color object
|
||||
* @param high the second color for the block, as a seperated 5:6:5 Color object
|
||||
* @param use_3color if the 3-color interpolation mode should be used
|
||||
* @return an array of 4 Color values, with indices matching BC1 selectors
|
||||
*/
|
||||
virtual std::array<Color, 4> InterpolateBC1(Color low, Color high, bool use_3color) const;
|
||||
|
||||
/**
|
||||
* Gets the type of an interpolator
|
||||
@ -125,27 +135,27 @@ class Interpolator {
|
||||
}
|
||||
};
|
||||
|
||||
class InterpolatorRound : public Interpolator {
|
||||
class InterpolatorRound final : public Interpolator {
|
||||
public:
|
||||
uint8_t Interpolate5(uint8_t v0, uint8_t v1) const override;
|
||||
uint8_t Interpolate6(uint8_t v0, uint8_t v1) const override;
|
||||
uint8_t Interpolate8(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t Interpolate5(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t Interpolate6(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t Interpolate8(uint8_t v0, uint8_t v1) const override;
|
||||
|
||||
Type GetType() const noexcept override { return Type::IdealRound; }
|
||||
virtual Type GetType() const noexcept override { return Type::IdealRound; }
|
||||
};
|
||||
|
||||
class InterpolatorNvidia : public Interpolator {
|
||||
class InterpolatorNvidia final : public Interpolator {
|
||||
public:
|
||||
uint8_t Interpolate5(uint8_t v0, uint8_t v1) const override;
|
||||
uint8_t Interpolate6(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t Interpolate5(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t Interpolate6(uint8_t v0, uint8_t v1) const override;
|
||||
|
||||
uint8_t InterpolateHalf5(uint8_t v0, uint8_t v1) const override;
|
||||
uint8_t InterpolateHalf6(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t InterpolateHalf5(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t InterpolateHalf6(uint8_t v0, uint8_t v1) const override;
|
||||
|
||||
std::array<Color, 4> InterpolateBC1(uint16_t low, uint16_t high) const override;
|
||||
virtual std::array<Color, 4> InterpolateBC1(Color low, Color high, bool use_3color) const override;
|
||||
|
||||
Type GetType() const noexcept override { return Type::Nvidia; }
|
||||
bool CanInterpolate8Bit() const noexcept override { return false; }
|
||||
virtual Type GetType() const noexcept override { return Type::Nvidia; }
|
||||
virtual bool CanInterpolate8Bit() const noexcept override { return false; }
|
||||
|
||||
private:
|
||||
Color InterpolateColor565(const Color &c0, const Color &c1) const {
|
||||
@ -157,16 +167,16 @@ class InterpolatorNvidia : public Interpolator {
|
||||
}
|
||||
};
|
||||
|
||||
class InterpolatorAMD : public Interpolator {
|
||||
class InterpolatorAMD final : public Interpolator {
|
||||
public:
|
||||
uint8_t Interpolate5(uint8_t v0, uint8_t v1) const override;
|
||||
uint8_t Interpolate6(uint8_t v0, uint8_t v1) const override;
|
||||
uint8_t Interpolate8(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t Interpolate5(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t Interpolate6(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t Interpolate8(uint8_t v0, uint8_t v1) const override;
|
||||
|
||||
uint8_t InterpolateHalf5(uint8_t v0, uint8_t v1) const override;
|
||||
uint8_t InterpolateHalf6(uint8_t v0, uint8_t v1) const override;
|
||||
uint8_t InterpolateHalf8(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t InterpolateHalf5(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t InterpolateHalf6(uint8_t v0, uint8_t v1) const override;
|
||||
virtual uint8_t InterpolateHalf8(uint8_t v0, uint8_t v1) const override;
|
||||
|
||||
Type GetType() const noexcept override { return Type::AMD; }
|
||||
virtual Type GetType() const noexcept override { return Type::AMD; }
|
||||
};
|
||||
} // namespace rgbcx
|
||||
} // namespace quicktex::s3tc
|
3
quicktex/s3tc/interpolator/__init__.py
Normal file
3
quicktex/s3tc/interpolator/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""Classes representing various methods of interpolating BC1-3 blocks"""
|
||||
|
||||
from _quicktex._s3tc._interpolator import *
|
68
quicktex/s3tc/interpolator/_bindings.cpp
Normal file
68
quicktex/s3tc/interpolator/_bindings.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 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/>.
|
||||
*/
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "Interpolator.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
namespace quicktex::bindings {
|
||||
|
||||
using namespace quicktex::s3tc;
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
|
||||
void InitInterpolator(py::module_ &s3tc) {
|
||||
auto interpolator = s3tc.def_submodule("_interpolator", "internal interpolator module");
|
||||
|
||||
// Interpolator
|
||||
py::class_<Interpolator, std::shared_ptr<Interpolator>> ideal(
|
||||
interpolator, "Interpolator", R"doc(
|
||||
Interpolator base class representing the ideal interpolation mode, with no rounding for colors 2 and 3.
|
||||
This matches the `D3D10 docs`_ on BC1.
|
||||
|
||||
.. _D3D10 docs: https://docs.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression
|
||||
)doc");
|
||||
|
||||
// InterpolatorRound
|
||||
py::class_<InterpolatorRound, std::shared_ptr<InterpolatorRound>> round(interpolator, "InterpolatorRound", ideal, R"doc(
|
||||
Base: :py:class:`~quicktex.s3tc.interpolator.Interpolator`
|
||||
|
||||
Interpolator class representing the ideal rounding interpolation mode.
|
||||
Round colors 2 and 3. Matches the AMD Compressonator tool and the `D3D9 docs`_ on DXT1.
|
||||
|
||||
.. _D3D9 docs: https://docs.microsoft.com/en-us/windows/win32/direct3d9/opaque-and-1-bit-alpha-textures
|
||||
)doc");
|
||||
|
||||
// InterpolatorNvidia
|
||||
py::class_<InterpolatorNvidia, std::shared_ptr<InterpolatorNvidia>> nvidia(interpolator, "InterpolatorNvidia", ideal, R"doc(
|
||||
Base: :py:class:`~quicktex.s3tc.interpolator.Interpolator`
|
||||
|
||||
Interpolator class representing the Nvidia GPU interpolation mode.
|
||||
)doc");
|
||||
|
||||
// InterpolatorAMD
|
||||
py::class_<InterpolatorAMD, std::shared_ptr<InterpolatorAMD>> amd(interpolator, "InterpolatorAMD", ideal, R"doc(
|
||||
Base: :py:class:`~quicktex.s3tc.interpolator.Interpolator`
|
||||
|
||||
Interpolator class representing the AMD GPU interpolation mode.
|
||||
)doc");
|
||||
}
|
||||
} // namespace quicktex::bindings
|
@ -1,5 +1,5 @@
|
||||
/* Python-rgbcx Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021-2022 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
@ -21,9 +21,10 @@
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "ndebug.h"
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#define UINT5_MAX 0x1FU // 31
|
||||
#define UINT6_MAX 0x3FU // 63
|
||||
@ -39,21 +40,21 @@ template <typename S> constexpr auto iabs(S i) {
|
||||
|
||||
/**
|
||||
* Unpacks an unsigned integer into an array of smaller integers.
|
||||
* @tparam I Input data type. Must be an unsigned integral type large enough to hold C * S bits.
|
||||
* @tparam I Input data type. Must be an unsigned integral type large enough to hold C * N bits.
|
||||
* @tparam O Output data type. must be an unsigned integral type large enough to hold C bits..
|
||||
* @tparam S Number of bits in each value.
|
||||
* @tparam C Number of values to unpack.
|
||||
* @param packed Packed integer input of type I.
|
||||
* @return Unpacked std::array of type O and size C.
|
||||
*/
|
||||
template <typename I, typename O, size_t S, size_t C> constexpr auto Unpack(I packed) noexcept(ndebug) {
|
||||
template <typename I, typename O, size_t S, size_t C> constexpr std::array<O, C> Unpack(I packed) {
|
||||
// type checking
|
||||
static_assert(std::is_unsigned<I>::value, "Packed input type must be unsigned");
|
||||
static_assert(std::is_unsigned<O>::value, "Unpacked output type must be unsigned");
|
||||
static_assert(std::numeric_limits<I>::digits >= (C * S), "Packed input type must be big enough to represent the number of bits multiplied by count");
|
||||
static_assert(std::numeric_limits<O>::digits >= S, "Unpacked output type must be big enough to represent the number of bits");
|
||||
|
||||
constexpr O mask = (1U << S) - 1U; // maximum value representable by S bits
|
||||
constexpr O mask = (1U << S) - 1U; // maximum value representable by N bits
|
||||
std::array<O, C> vals; // output values array of size C
|
||||
|
||||
for (unsigned i = 0; i < C; i++) {
|
||||
@ -67,13 +68,13 @@ template <typename I, typename O, size_t S, size_t C> constexpr auto Unpack(I pa
|
||||
/**
|
||||
* Packs an array of unsigned integers into a single integer.
|
||||
* @tparam I Input data type. Must be an unsigned integral type large enough to hold C bits.
|
||||
* @tparam O Output data type. must be an unsigned integral type large enough to hold C * S bits.
|
||||
* @tparam O Output data type. must be an unsigned integral type large enough to hold C * N bits.
|
||||
* @tparam S Number of bits in each value.
|
||||
* @tparam C Number of values to unpack.
|
||||
* @param vals Unpacked std::array of type I and size C.
|
||||
* @return Packed integer input of type O.
|
||||
*/
|
||||
template <typename I, typename O, size_t S, size_t C> constexpr auto Pack(const std::array<I, C> &vals) noexcept(ndebug) {
|
||||
template <typename I, typename O, size_t S, size_t C> constexpr O Pack(const std::array<I, C> &vals) {
|
||||
// type checking
|
||||
static_assert(std::is_unsigned<I>::value, "Unpacked input type must be unsigned");
|
||||
static_assert(std::is_unsigned<O>::value, "Packed output type must be unsigned");
|
||||
@ -97,6 +98,16 @@ template <size_t Size, int Op(int)> constexpr std::array<uint8_t, Size> ExpandAr
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename Seq, typename Fn> constexpr auto MapArray(const Seq &input, Fn op) {
|
||||
using I = typename Seq::value_type;
|
||||
using O = decltype(op(std::declval<I>()));
|
||||
constexpr size_t N = std::tuple_size<Seq>::value;
|
||||
|
||||
std::array<O, N> output;
|
||||
for (unsigned i = 0; i < N; i++) { output[i] = op(input[i]); }
|
||||
return output;
|
||||
}
|
||||
|
||||
template <typename S> constexpr S scale8To5(S v) {
|
||||
auto v2 = v * 31 + 128;
|
||||
return static_cast<S>((v2 + (v2 >> 8)) >> 8);
|
||||
@ -147,3 +158,21 @@ constexpr int squarei(int a) { return a * a; }
|
||||
constexpr int absi(int a) { return (a < 0) ? -a : a; }
|
||||
|
||||
template <typename F> constexpr F lerp(F a, F b, F s) { return a + (b - a) * s; }
|
||||
|
||||
template <typename... Args> std::string Format(const char *str, const Args &...args) {
|
||||
auto output = std::string(str);
|
||||
|
||||
std::vector<std::string> values = {{args...}};
|
||||
|
||||
for (unsigned i = 0; i < values.size(); i++) {
|
||||
auto key = "{" + std::to_string(i) + "}";
|
||||
auto value = values[i];
|
||||
while (true) {
|
||||
size_t where = output.find(key);
|
||||
if (where == output.npos) break;
|
||||
output.replace(where, key.length(), value);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
111
setup.py
Normal file
111
setup.py
Normal file
@ -0,0 +1,111 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pybind11
|
||||
from setuptools import setup, Extension
|
||||
from setuptools.command.build_ext import build_ext
|
||||
|
||||
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})
|
@ -1,85 +0,0 @@
|
||||
/* Python-rgbcx 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "../Color.h"
|
||||
#include "../util.h"
|
||||
|
||||
namespace rgbcx {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
class BC1Block {
|
||||
public:
|
||||
using UnpackedSelectors = std::array<std::array<uint8_t, 4>, 4>;
|
||||
|
||||
uint16_t GetLowColor() const { return static_cast<uint16_t>(_low_color[0] | (_low_color[1] << 8U)); }
|
||||
uint16_t GetHighColor() const { return static_cast<uint16_t>(_high_color[0] | (_high_color[1] << 8U)); }
|
||||
Color GetLowColor32() const { return Color::Unpack565(GetLowColor()); }
|
||||
Color GetHighColor32() const { return Color::Unpack565(GetHighColor()); }
|
||||
|
||||
bool Is3Color() const { return GetLowColor() <= GetHighColor(); }
|
||||
void SetLowColor(uint16_t c) {
|
||||
_low_color[0] = c & 0xFF;
|
||||
_low_color[1] = (c >> 8) & 0xFF;
|
||||
}
|
||||
void SetHighColor(uint16_t c) {
|
||||
_high_color[0] = c & 0xFF;
|
||||
_high_color[1] = (c >> 8) & 0xFF;
|
||||
}
|
||||
uint32_t GetSelector(uint32_t x, uint32_t y) const {
|
||||
assert((x < 4U) && (y < 4U));
|
||||
return (selectors[y] >> (x * SelectorBits)) & SelectorMask;
|
||||
}
|
||||
void SetSelector(uint32_t x, uint32_t y, uint32_t val) {
|
||||
assert((x < 4U) && (y < 4U) && (val < 4U));
|
||||
selectors[y] &= (~(SelectorMask << (x * SelectorBits)));
|
||||
selectors[y] |= (val << (x * SelectorBits));
|
||||
}
|
||||
|
||||
UnpackedSelectors UnpackSelectors() const {
|
||||
UnpackedSelectors unpacked;
|
||||
for (unsigned i = 0; i < 4; i++) { unpacked[i] = Unpack<uint8_t, uint8_t, 2, 4>(selectors[i]); }
|
||||
return unpacked;
|
||||
}
|
||||
|
||||
void PackSelectors(const UnpackedSelectors& unpacked) {
|
||||
for (unsigned i = 0; i < 4; i++) { selectors[i] = Pack<uint8_t, uint8_t, 2, 4>(unpacked[i]); }
|
||||
}
|
||||
|
||||
constexpr static inline size_t EndpointSize = 2;
|
||||
constexpr static inline size_t SelectorSize = 4;
|
||||
constexpr static inline uint8_t SelectorBits = 2;
|
||||
constexpr static inline uint8_t SelectorValues = 1 << SelectorBits;
|
||||
constexpr static inline uint8_t SelectorMask = SelectorValues - 1;
|
||||
|
||||
private:
|
||||
std::array<uint8_t, EndpointSize> _low_color;
|
||||
std::array<uint8_t, EndpointSize> _high_color;
|
||||
|
||||
public:
|
||||
std::array<uint8_t, 4> selectors;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
} // namespace rgbcx
|
@ -1,371 +0,0 @@
|
||||
/* Python-rgbcx 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/>.
|
||||
*/
|
||||
|
||||
#include "BC1Encoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "../BlockView.h"
|
||||
#include "../Color.h"
|
||||
#include "../Matrix4x4.h"
|
||||
#include "../Vector4.h"
|
||||
#include "../bitwiseEnums.h"
|
||||
|
||||
namespace rgbcx {
|
||||
using MatchList = std::array<BC1MatchEntry, 256>;
|
||||
using MatchListPtr = std::shared_ptr<MatchList>;
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
|
||||
// region Free Functions/Templates
|
||||
inline void PrepSingleColorTableEntry(unsigned &error, MatchList &match_table, uint8_t v, unsigned i, uint8_t low, uint8_t high, uint8_t low8, uint8_t high8,
|
||||
bool ideal) {
|
||||
unsigned new_error = iabs(v - (int)i);
|
||||
|
||||
// We only need to factor in 3% error in BC1 ideal mode.
|
||||
if (ideal) new_error += (iabs(high8 - (int)low8) * 3) / 100;
|
||||
|
||||
// Favor equal endpoints, for lower error on actual GPU's which approximate the interpolation.
|
||||
if ((new_error < error) || (new_error == error && low == high)) {
|
||||
assert(new_error <= UINT8_MAX);
|
||||
|
||||
match_table[i].low = (uint8_t)low;
|
||||
match_table[i].high = (uint8_t)high;
|
||||
match_table[i].error = (uint8_t)new_error;
|
||||
|
||||
error = new_error;
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t S> void PrepSingleColorTable(MatchList &match_table, MatchList &match_table_half, Interpolator &interpolator) {
|
||||
unsigned size = 1 << S;
|
||||
|
||||
assert((S == 5 && size == 32) || (S == 6 && size == 64));
|
||||
|
||||
bool ideal = interpolator.IsIdeal();
|
||||
bool use_8bit = interpolator.CanInterpolate8Bit();
|
||||
|
||||
for (unsigned i = 0; i < 256; i++) {
|
||||
unsigned error = 256;
|
||||
unsigned error_half = 256;
|
||||
|
||||
// TODO: Can probably avoid testing for values that definitely wont yield good results,
|
||||
// e.g. low8 and high8 both much smaller or larger than index
|
||||
for (uint8_t low = 0; low < size; low++) {
|
||||
uint8_t low8 = (S == 5) ? scale5To8(low) : scale6To8(low);
|
||||
|
||||
for (uint8_t high = 0; high < size; high++) {
|
||||
uint8_t high8 = (S == 5) ? scale5To8(high) : scale6To8(high);
|
||||
uint8_t value, value_half;
|
||||
|
||||
if (use_8bit) {
|
||||
value = interpolator.Interpolate8(high8, low8);
|
||||
value_half = interpolator.InterpolateHalf8(high8, low8);
|
||||
} else {
|
||||
value = (S == 5) ? interpolator.Interpolate5(high, low) : interpolator.Interpolate6(high, low);
|
||||
value_half = (S == 5) ? interpolator.InterpolateHalf5(high, low) : interpolator.InterpolateHalf6(high, low);
|
||||
}
|
||||
|
||||
PrepSingleColorTableEntry(error, match_table, value, i, low, high, low8, high8, ideal);
|
||||
PrepSingleColorTableEntry(error_half, match_table_half, value_half, i, low, high, low8, high8, ideal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
BC1Encoder::BC1Encoder(InterpolatorPtr interpolator) : _interpolator(interpolator) {
|
||||
PrepSingleColorTable<5>(*_single_match5, *_single_match5_half, *_interpolator);
|
||||
PrepSingleColorTable<6>(*_single_match6, *_single_match6_half, *_interpolator);
|
||||
}
|
||||
|
||||
void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const {
|
||||
auto r_view = pixels.GetChannel(0);
|
||||
auto g_view = pixels.GetChannel(1);
|
||||
auto b_view = pixels.GetChannel(2);
|
||||
|
||||
Color first = pixels.Get(0, 0);
|
||||
|
||||
if (pixels.IsSingleColor()) { // for now assume (wrongly) everything is a single-color block
|
||||
// single-color pixel block, do it the fast way
|
||||
EncodeBlockSingleColor(first, dest);
|
||||
return;
|
||||
}
|
||||
|
||||
auto metrics = pixels.GetMetrics();
|
||||
}
|
||||
|
||||
void BC1Encoder::EncodeBlockSingleColor(Color color, BC1Block *dest) const {
|
||||
uint8_t mask = 0xAA; // 2222
|
||||
uint16_t min16, max16;
|
||||
|
||||
bool using_3color = false;
|
||||
|
||||
// why is there no subscript operator for shared_ptr<array>
|
||||
MatchList &match5 = *_single_match5;
|
||||
MatchList &match6 = *_single_match6;
|
||||
MatchList &match5_half = *_single_match5_half;
|
||||
MatchList &match6_half = *_single_match6_half;
|
||||
|
||||
BC1MatchEntry match_r = match5[color.r];
|
||||
BC1MatchEntry match_g = match6[color.g];
|
||||
BC1MatchEntry match_b = match5[color.b];
|
||||
|
||||
if ((_flags & (Flags::Use3ColorBlocks | Flags::Use3ColorBlocksForBlackPixels)) != Flags::None) {
|
||||
BC1MatchEntry match_r_half = match5_half[color.r];
|
||||
BC1MatchEntry match_g_half = match6_half[color.g];
|
||||
BC1MatchEntry match_b_half = match5_half[color.b];
|
||||
|
||||
const unsigned err4 = match_r.error + match_g.error + match_b.error;
|
||||
const unsigned err3 = match_r_half.error + match_g_half.error + match_b_half.error;
|
||||
|
||||
if (err3 < err4) {
|
||||
min16 = Color::Pack565Unscaled(match_r_half.low, match_g_half.low, match_b_half.low);
|
||||
max16 = Color::Pack565Unscaled(match_r_half.high, match_g_half.high, match_b_half.high);
|
||||
|
||||
if (max16 > min16) std::swap(min16, max16);
|
||||
using_3color = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!using_3color) {
|
||||
min16 = Color::Pack565Unscaled(match_r.low, match_g.low, match_b.low);
|
||||
max16 = Color::Pack565Unscaled(match_r.high, match_g.high, match_b.high);
|
||||
|
||||
if (min16 == max16) {
|
||||
// make sure this isnt accidentally a 3-color block
|
||||
// so make max16 > min16 (l > h)
|
||||
if (min16 > 0) {
|
||||
min16--;
|
||||
mask = 0; // endpoints are equal so mask doesnt matter
|
||||
} else {
|
||||
assert(min16 == 0 && max16 == 0);
|
||||
max16 = 1;
|
||||
min16 = 0;
|
||||
mask = 0x55; // 1111 (Min value only, max is ignored)
|
||||
}
|
||||
} else if (max16 < min16) {
|
||||
std::swap(min16, max16);
|
||||
mask = 0xFF; // invert mask to 3333
|
||||
}
|
||||
assert(max16 > min16);
|
||||
}
|
||||
|
||||
dest->SetLowColor(max16);
|
||||
dest->SetHighColor(min16);
|
||||
dest->selectors[0] = mask;
|
||||
dest->selectors[1] = mask;
|
||||
dest->selectors[2] = mask;
|
||||
dest->selectors[3] = mask;
|
||||
}
|
||||
|
||||
void BC1Encoder::FindEndpoints(Color4x4 pixels, BC1Encoder::Flags flags, const BC1Encoder::BlockMetrics metrics, Color &low, Color &high) const {
|
||||
if (metrics.is_greyscale) {
|
||||
// specialized greyscale case
|
||||
const unsigned fr = pixels.Get(0).r;
|
||||
|
||||
if (metrics.max.r - metrics.min.r < 2) {
|
||||
// single color block
|
||||
low.r = high.r = (uint8_t)scale8To5(fr);
|
||||
low.g = high.g = (uint8_t)scale8To6(fr);
|
||||
low.b = high.b = low.r;
|
||||
} else {
|
||||
low.r = low.b = scale8To5(metrics.min.r);
|
||||
low.g = scale8To6(metrics.min.r);
|
||||
|
||||
high.r = high.b = scale8To5(metrics.max.r);
|
||||
high.g = scale8To6(metrics.max.r);
|
||||
}
|
||||
} else if ((flags & Flags::Use2DLS) != Flags::None) {
|
||||
// 2D Least Squares approach from Humus's example, with added inset and optimal rounding.
|
||||
Color diff = Color(metrics.max.r - metrics.min.r, metrics.max.g - metrics.min.g, metrics.max.b - metrics.min.b);
|
||||
Vector4 l = {0, 0, 0};
|
||||
Vector4 h = {0, 0, 0};
|
||||
|
||||
auto &sums = metrics.sums;
|
||||
auto &min = metrics.min;
|
||||
auto &max = metrics.max;
|
||||
|
||||
unsigned chan0 = (unsigned)diff.MaxChannelRGB(); // primary axis of the bounding box
|
||||
l[chan0] = (float)min[chan0];
|
||||
h[chan0] = (float)min[chan0];
|
||||
|
||||
assert(diff[chan0] >= diff[(chan0 + 1) % 3] && diff[chan0] >= diff[(chan0 + 2) % 3]);
|
||||
|
||||
std::array<unsigned, 3> sums_xy;
|
||||
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
auto val = pixels.Get(i);
|
||||
for (unsigned c = 0; c < 3; c++) { sums_xy[c] += val[chan0] * val[c]; }
|
||||
}
|
||||
|
||||
auto &sum_x = sums[chan0];
|
||||
auto &sum_xx = sums_xy[chan0];
|
||||
|
||||
float denominator = (float)(16 * sum_xx) - (float)(sum_x * sum_x);
|
||||
|
||||
// once per secondary axis, calculate high and low using least squares
|
||||
if (fabs(denominator) > 1e-8f) {
|
||||
for (unsigned i = 1; i < 3; i++) {
|
||||
/* each secondary axis is fitted with a linear formula of the form
|
||||
* y = ax + b
|
||||
* where y is the secondary axis and x is the primary axis
|
||||
* a = (m∑xy - ∑x∑y) / m∑x² - (∑x)²
|
||||
* b = (∑x²∑y - ∑xy∑x) / m∑x² - (∑x)²
|
||||
* see Giordano/Weir pg.103 */
|
||||
const unsigned chan = (chan0 + i) % 3;
|
||||
const unsigned &sum_y = sums[chan];
|
||||
const unsigned &sum_xy = sums_xy[chan];
|
||||
|
||||
float a = (float)((16 * sum_xy) - (sum_x * sum_y)) / denominator;
|
||||
float b = (float)((sum_xx * sum_y) - (sum_xy * sum_x)) / denominator;
|
||||
|
||||
l[chan] = b + (a * l[chan0]);
|
||||
h[chan] = b + (a * h[chan0]);
|
||||
}
|
||||
}
|
||||
|
||||
// once per axis, inset towards the center by 1/16 of the delta and scale
|
||||
for (unsigned c = 0; c < 3; c++) {
|
||||
float inset = (h[c] - l[c]) / 16.0f;
|
||||
|
||||
l[c] = ((l[c] + inset) / 255.0f);
|
||||
h[c] = ((h[c] - inset) / 255.0f);
|
||||
}
|
||||
|
||||
low = Color::PreciseRound565(l);
|
||||
high = Color::PreciseRound565(h);
|
||||
} else if ((flags & Flags::BoundingBox) != Flags::None) {
|
||||
// Algorithm from icbc.h compress_dxt1_fast()
|
||||
Vector4 l, h;
|
||||
const float bias = 8.0f / 255.0f;
|
||||
|
||||
// rescale and inset values
|
||||
for (unsigned c = 0; c < 3; c++) { // heh, c++
|
||||
l[c] = (float)metrics.min[c] / 255.0f;
|
||||
h[c] = (float)metrics.max[c] / 255.0f;
|
||||
|
||||
float inset = (h[c] - l[c] - bias) / 16.0f;
|
||||
l[c] += inset;
|
||||
h[c] -= inset;
|
||||
}
|
||||
|
||||
// Select the correct diagonal across the bounding box
|
||||
int icov_xz = 0, icov_yz = 0;
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
int b = (int)pixels.Get(i).b - metrics.avg.b;
|
||||
icov_xz += b * (int)pixels.Get(i).r - metrics.avg.r;
|
||||
icov_yz += b * (int)pixels.Get(i).g - metrics.avg.g;
|
||||
}
|
||||
|
||||
if (icov_xz < 0) std::swap(l[0], h[0]);
|
||||
if (icov_yz < 0) std::swap(l[1], h[1]);
|
||||
|
||||
low = Color::PreciseRound565(l);
|
||||
high = Color::PreciseRound565(h);
|
||||
} else if ((flags & Flags::BoundingBoxInt) != Flags::None) {
|
||||
// Algorithm from icbc.h compress_dxt1_fast(), but converted to integer.
|
||||
|
||||
Color min, max;
|
||||
|
||||
// rescale and inset values
|
||||
for (unsigned c = 0; c < 3; c++) {
|
||||
int inset = ((int)(metrics.max[c] - metrics.min[c]) - 8) >> 4; // 1/16 of delta, with bias
|
||||
|
||||
min[c] = clamp255(metrics.min[c] + inset);
|
||||
max[c] = clamp255(metrics.max[c] - inset);
|
||||
}
|
||||
|
||||
int icov_xz = 0, icov_yz = 0;
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
int b = (int)pixels.Get(i).b - metrics.avg.b;
|
||||
icov_xz += b * (int)pixels.Get(i).r - metrics.avg.r;
|
||||
icov_yz += b * (int)pixels.Get(i).g - metrics.avg.g;
|
||||
}
|
||||
|
||||
if (icov_xz < 0) std::swap(min.r, max.r);
|
||||
if (icov_yz < 0) std::swap(min.g, max.g);
|
||||
|
||||
low = min.ScaleTo565();
|
||||
high = max.ScaleTo565();
|
||||
} else {
|
||||
// the slow way
|
||||
// Select 2 colors along the principle axis. (There must be a faster/simpler way.)
|
||||
auto min = Vector4::FromColorRGB(metrics.min);
|
||||
auto max = Vector4::FromColorRGB(metrics.max);
|
||||
auto avg = Vector4::FromColorRGB(metrics.avg);
|
||||
|
||||
std::array<Vector4, 16> colors;
|
||||
|
||||
Vector4 axis = {306, 601, 117}; // Luma vector
|
||||
Matrix4x4 covariance;
|
||||
const unsigned total_power_iters = (flags & Flags::Use6PowerIters) != Flags::None ? 6 : 4;
|
||||
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
colors[i] = Vector4::FromColorRGB(pixels.Get(i));
|
||||
Vector4 diff = colors[i] - avg;
|
||||
for (unsigned c1 = 0; c1 < 3; c1++) {
|
||||
for (unsigned c2 = c1; c2 < 3; c2++) {
|
||||
covariance[c1][c2] += (diff[c1] * diff[c2]);
|
||||
assert(c1 <= c2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
covariance /= 255.0f;
|
||||
covariance.Mirror();
|
||||
|
||||
Vector4 delta = max - min;
|
||||
|
||||
// realign r and g axes to match
|
||||
if (covariance[0][2] < 0) delta[0] = -delta[0]; // r vs b
|
||||
if (covariance[1][2] < 0) delta[1] = -delta[1]; // g vs b
|
||||
|
||||
// using the covariance matrix, iteratively stretch the delta vector towards the primary axis of the data
|
||||
for (unsigned power_iter = 0; power_iter < total_power_iters; power_iter++) { delta = covariance * delta; }
|
||||
|
||||
// if we found any correlation, then this is our new axis. otherwise we fallback to the luma vector
|
||||
float k = delta.MaxAbs(3);
|
||||
if (k > 2) { axis = delta * (2048.0f / k); }
|
||||
|
||||
float min_dot = INFINITY;
|
||||
float max_dot = -INFINITY;
|
||||
|
||||
unsigned min_index, max_index;
|
||||
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
// since axis is constant here, I dont think its magnitude actually matters,
|
||||
// since we only care about the min or max dot product
|
||||
float dot = colors[i].Dot(axis);
|
||||
if (dot > max_dot) {
|
||||
max_dot = dot;
|
||||
max_index = i;
|
||||
} else if (dot < min_dot) {
|
||||
min_dot = dot;
|
||||
min_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
low = pixels.Get(min_index).ScaleTo565();
|
||||
high = pixels.Get(max_index).ScaleTo565();
|
||||
}
|
||||
}
|
||||
} // namespace rgbcx
|
@ -1,156 +0,0 @@
|
||||
/* Python-rgbcx 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include "../BlockEncoder.h"
|
||||
#include "../BlockView.h"
|
||||
#include "../Interpolator.h"
|
||||
#include "../bitwiseEnums.h"
|
||||
#include "../ndebug.h"
|
||||
#include "BC1Block.h"
|
||||
#include "tables.h"
|
||||
|
||||
namespace rgbcx {
|
||||
|
||||
struct BC1MatchEntry {
|
||||
uint8_t high;
|
||||
uint8_t low;
|
||||
uint8_t error;
|
||||
};
|
||||
|
||||
class BC1Encoder : public BlockEncoder<BC1Block, 4, 4> {
|
||||
public:
|
||||
using InterpolatorPtr = std::shared_ptr<Interpolator>;
|
||||
using BlockMetrics = Color4x4::BlockMetrics;
|
||||
|
||||
enum class Flags : uint32_t {
|
||||
None = 0,
|
||||
|
||||
// Try to improve quality using the most likely total orderings.
|
||||
// The total_orderings_to_try parameter will then control the number of total orderings to try for 4 color blocks, and the
|
||||
// total_orderings_to_try3 parameter will control the number of total orderings to try for 3 color blocks (if they are enabled).
|
||||
UseLikelyTotalOrderings = 2,
|
||||
|
||||
// Use 2 least squares pass, instead of one (same as stb_dxt's HIGHQUAL option).
|
||||
// Recommended if you're enabling UseLikelyTotalOrderings.
|
||||
TwoLeastSquaresPasses = 4,
|
||||
|
||||
// Use3ColorBlocksForBlackPixels allows the BC1 encoder to use 3-color blocks for blocks containing black or very dark pixels.
|
||||
// You shader/engine MUST ignore the alpha channel on textures encoded with this flag.
|
||||
// Average quality goes up substantially for my 100 texture corpus (~.5 dB), so it's worth using if you can.
|
||||
// Note the BC1 encoder does not actually support transparency in 3-color mode.
|
||||
// Don't set when encoding to BC3.
|
||||
Use3ColorBlocksForBlackPixels = 8,
|
||||
|
||||
// If Use3ColorBlocks is set, the encoder can use 3-color mode for a small but noticeable gain in average quality, but lower perf.
|
||||
// If you also specify the UseLikelyTotalOrderings flag, set the total_orderings_to_try3 paramter to the number of total orderings to try.
|
||||
// Don't set when encoding to BC3.
|
||||
Use3ColorBlocks = 16,
|
||||
|
||||
// Iterative will greatly increase encode time, but is very slightly higher quality.
|
||||
// Same as squish's iterative cluster fit option. Not really worth the tiny boost in quality, unless you just don't care about perf. at all.
|
||||
Iterative = 32,
|
||||
|
||||
// BoundingBox enables a fast all-integer PCA approximation on 4-color blocks.
|
||||
// At level 0 options (no other flags), this is ~15% faster, and higher *average* quality.
|
||||
BoundingBox = 64,
|
||||
|
||||
// Use a slightly lower quality, but ~30% faster MSE evaluation function for 4-color blocks.
|
||||
UseFasterMSEEval = 128,
|
||||
|
||||
// Examine all colors to compute selectors/MSE (slower than default)
|
||||
UseFullMSEEval = 256,
|
||||
|
||||
// Use 2D least squares+inset+optimal rounding (the method used in Humus's GPU texture encoding demo), instead of PCA.
|
||||
// Around 18% faster, very slightly lower average quality to better (depends on the content).
|
||||
Use2DLS = 512,
|
||||
|
||||
// Use 6 power iterations vs. 4 for PCA.
|
||||
Use6PowerIters = 2048,
|
||||
|
||||
// Check all total orderings - *very* slow. The encoder is not designed to be used in this way.
|
||||
Exhaustive = 8192,
|
||||
|
||||
// Try 2 different ways of choosing the initial endpoints.
|
||||
TryAllInitialEndponts = 16384,
|
||||
|
||||
// Same as BoundingBox, but implemented using integer math (faster, slightly less quality)
|
||||
BoundingBoxInt = 32768,
|
||||
|
||||
// Try refining the final endpoints by examining nearby colors.
|
||||
EndpointSearchRoundsShift = 22,
|
||||
EndpointSearchRoundsMask = 1023U << EndpointSearchRoundsShift,
|
||||
};
|
||||
|
||||
BC1Encoder(InterpolatorPtr interpolator);
|
||||
|
||||
void EncodeBlock(Color4x4 pixels, BC1Block *dest) const override;
|
||||
|
||||
private:
|
||||
using Vec3F = std::array<float, 3>;
|
||||
|
||||
const InterpolatorPtr _interpolator;
|
||||
|
||||
Flags _flags;
|
||||
unsigned _search_rounds;
|
||||
unsigned _orderings4;
|
||||
unsigned _orderings3;
|
||||
|
||||
// Unpacked BC1 block with metadata
|
||||
struct EncodeResults {
|
||||
Color low;
|
||||
Color high;
|
||||
std::array<uint8_t, 16> selectors;
|
||||
bool is_3_color;
|
||||
};
|
||||
|
||||
void EncodeBlockSingleColor(Color color, BC1Block *dest) const;
|
||||
void FindEndpoints(Color4x4 pixels, Flags flags, BlockMetrics const metrics, Color &low, Color &high) const;
|
||||
|
||||
// match tables used for single-color blocks
|
||||
// Each entry includes a high and low pair that best reproduces the 8-bit index as well as possible,
|
||||
// with an included error value
|
||||
// these depend on the interpolator
|
||||
using MatchList = std::array<BC1MatchEntry, 256>;
|
||||
using MatchListPtr = std::shared_ptr<MatchList>;
|
||||
|
||||
const MatchListPtr _single_match5 = std::make_shared<MatchList>();
|
||||
const MatchListPtr _single_match6 = std::make_shared<MatchList>();
|
||||
const MatchListPtr _single_match5_half = std::make_shared<MatchList>();
|
||||
const MatchListPtr _single_match6_half = std::make_shared<MatchList>();
|
||||
|
||||
// static lookup tables, generated the first time an encoder is created
|
||||
// the mutex prevents race conditions if two encoders are created on different threads
|
||||
static std::mutex _luts_mutex;
|
||||
static bool _luts_initialized;
|
||||
|
||||
// lookup table for hash values
|
||||
static uint16_t g_total_ordering4_hash[4096];
|
||||
static uint16_t g_total_ordering3_hash[256];
|
||||
|
||||
static float g_selector_factors4[NUM_UNIQUE_TOTAL_ORDERINGS4][3];
|
||||
static float g_selector_factors3[NUM_UNIQUE_TOTAL_ORDERINGS3][3];
|
||||
};
|
||||
} // namespace rgbcx
|
1317
src/BC1/tables.cpp
1317
src/BC1/tables.cpp
File diff suppressed because it is too large
Load Diff
@ -1,69 +0,0 @@
|
||||
// rgbcx.h v1.12
|
||||
// High-performance scalar BC1-5 encoders. Public Domain or MIT license (you choose - see below), written by Richard Geldreich 2020 <richgel99@gmail.com>.
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
const uint32_t MIN_TOTAL_ORDERINGS = 1;
|
||||
const uint32_t MAX_TOTAL_ORDERINGS3 = 32;
|
||||
|
||||
#if RGBCX_USE_SMALLER_TABLES
|
||||
const uint32_t MAX_TOTAL_ORDERINGS4 = 32;
|
||||
#else
|
||||
const uint32_t MAX_TOTAL_ORDERINGS4 = 128;
|
||||
#endif
|
||||
|
||||
extern const float g_midpoint5[32];
|
||||
extern const float g_midpoint6[64];
|
||||
|
||||
const uint32_t NUM_UNIQUE_TOTAL_ORDERINGS4 = 969;
|
||||
extern const uint8_t g_unique_total_orders4[NUM_UNIQUE_TOTAL_ORDERINGS4][4];
|
||||
|
||||
const uint32_t NUM_UNIQUE_TOTAL_ORDERINGS3 = 153;
|
||||
extern const uint8_t g_unique_total_orders3[NUM_UNIQUE_TOTAL_ORDERINGS3][3];
|
||||
|
||||
extern const uint16_t g_best_total_orderings4[NUM_UNIQUE_TOTAL_ORDERINGS4][MAX_TOTAL_ORDERINGS4];
|
||||
|
||||
extern const uint8_t g_best_total_orderings3[NUM_UNIQUE_TOTAL_ORDERINGS3][32];
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------
|
||||
This software is available under 2 licenses -- choose whichever you prefer.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE A - MIT License
|
||||
Copyright(c) 2020 Richard Geldreich, Jr.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files(the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions :
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE B - Public Domain(www.unlicense.org)
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non - commercial, and by any means.
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain.We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors.We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
@ -1,122 +0,0 @@
|
||||
/* Python-rgbcx 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "../BC1/BC1Block.h"
|
||||
#include "../Color.h"
|
||||
#include "../util.h"
|
||||
|
||||
namespace rgbcx {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
class BC4Block {
|
||||
public:
|
||||
using UnpackedSelectors = std::array<std::array<uint8_t, 4>, 4>;
|
||||
|
||||
inline uint32_t GetLowAlpha() const { return low_alpha; }
|
||||
inline uint32_t GetHighAlpha() const { return high_alpha; }
|
||||
inline bool Is6Alpha() const { return GetLowAlpha() <= GetHighAlpha(); }
|
||||
|
||||
inline uint64_t GetSelectorBits() const {
|
||||
auto packed = Pack<uint8_t, uint64_t, 8, 6>(selectors);
|
||||
assert(packed <= SelectorBitsMax);
|
||||
return packed;
|
||||
}
|
||||
|
||||
void SetSelectorBits(uint64_t packed) {
|
||||
assert(packed <= SelectorBitsMax);
|
||||
selectors = Unpack<uint64_t, uint8_t, 8, 6>(packed);
|
||||
}
|
||||
|
||||
UnpackedSelectors UnpackSelectors() const {
|
||||
UnpackedSelectors unpacked;
|
||||
auto rows = Unpack<uint64_t, uint16_t, 12, 4>(GetSelectorBits());
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
auto row = Unpack<uint16_t, uint8_t, SelectorBits, 4>(rows[i]);
|
||||
unpacked[i] = row;
|
||||
}
|
||||
|
||||
return unpacked;
|
||||
}
|
||||
|
||||
void PackSelectors(const UnpackedSelectors& unpacked) {
|
||||
std::array<uint16_t, 4> rows;
|
||||
for (unsigned i = 0; i < 4; i++) { rows[i] = Pack<uint8_t, uint16_t, SelectorBits, 4>(unpacked[i]); }
|
||||
auto packed = Pack<uint16_t, uint64_t, 12, 4>(rows);
|
||||
SetSelectorBits(packed);
|
||||
}
|
||||
|
||||
void PackSelectors(const std::array<uint8_t, 16>& unpacked) {
|
||||
auto packed = Pack<uint8_t, uint64_t, 3, 16>(unpacked);
|
||||
SetSelectorBits(packed);
|
||||
}
|
||||
|
||||
inline uint32_t GetSelector(uint32_t x, uint32_t y, uint64_t selector_bits) const {
|
||||
assert((x < 4U) && (y < 4U));
|
||||
return (selector_bits >> (((y * 4) + x) * SelectorBits)) & (SelectorMask);
|
||||
}
|
||||
|
||||
static inline std::array<uint8_t, 8> GetValues6(uint32_t l, uint32_t h) {
|
||||
return {static_cast<uint8_t>(l),
|
||||
static_cast<uint8_t>(h),
|
||||
static_cast<uint8_t>((l * 4 + h) / 5),
|
||||
static_cast<uint8_t>((l * 3 + h * 2) / 5),
|
||||
static_cast<uint8_t>((l * 2 + h * 3) / 5),
|
||||
static_cast<uint8_t>((l + h * 4) / 5),
|
||||
0,
|
||||
255};
|
||||
}
|
||||
|
||||
static inline std::array<uint8_t, 8> GetValues8(uint32_t l, uint32_t h) {
|
||||
return {static_cast<uint8_t>(l),
|
||||
static_cast<uint8_t>(h),
|
||||
static_cast<uint8_t>((l * 6 + h) / 7),
|
||||
static_cast<uint8_t>((l * 5 + h * 2) / 7),
|
||||
static_cast<uint8_t>((l * 4 + h * 3) / 7),
|
||||
static_cast<uint8_t>((l * 3 + h * 4) / 7),
|
||||
static_cast<uint8_t>((l * 2 + h * 5) / 7),
|
||||
static_cast<uint8_t>((l + h * 6) / 7)};
|
||||
}
|
||||
|
||||
static inline std::array<uint8_t, 8> GetValues(uint32_t l, uint32_t h) {
|
||||
if (l > h)
|
||||
return GetValues8(l, h);
|
||||
else
|
||||
return GetValues6(l, h);
|
||||
}
|
||||
|
||||
constexpr static inline size_t EndpointSize = 1;
|
||||
constexpr static inline size_t SelectorSize = 6;
|
||||
constexpr static inline uint8_t SelectorBits = 3;
|
||||
constexpr static inline uint8_t SelectorValues = 1 << SelectorBits;
|
||||
constexpr static inline uint8_t SelectorMask = SelectorValues - 1;
|
||||
constexpr static inline uint64_t SelectorBitsMax = (1ULL << (8U * SelectorSize)) - 1U;
|
||||
|
||||
uint8_t low_alpha;
|
||||
uint8_t high_alpha;
|
||||
std::array<uint8_t, SelectorSize> selectors;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
} // namespace rgbcx
|
@ -1,44 +0,0 @@
|
||||
/* Python-rgbcx 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../BlockDecoder.h"
|
||||
#include "../BlockView.h"
|
||||
#include "../ndebug.h"
|
||||
#include "BC4Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
class BC4Decoder : public BlockDecoder<BC4Block, 4, 4> {
|
||||
public:
|
||||
BC4Decoder(uint8_t channel = 3) : _channel(channel) { assert(channel < 4U); }
|
||||
|
||||
void DecodeBlock(Color4x4 dest, BC4Block *const block) const noexcept(ndebug) override { DecodeBlock(dest.GetChannel(_channel), block); }
|
||||
void DecodeBlock(Color4x4 dest, BC4Block *const block, uint8_t channel) const noexcept(ndebug) { DecodeBlock(dest.GetChannel(channel), block); }
|
||||
void DecodeBlock(Byte4x4 dest, BC4Block *const block) const noexcept(ndebug);
|
||||
|
||||
constexpr uint8_t GetChannel() const { return _channel; }
|
||||
|
||||
private:
|
||||
const uint8_t _channel;
|
||||
};
|
||||
} // namespace rgbcx
|
@ -1,55 +0,0 @@
|
||||
/* Python-rgbcx 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "../BC4/BC4Decoder.h"
|
||||
#include "../BlockDecoder.h"
|
||||
#include "../BlockView.h"
|
||||
#include "../ndebug.h"
|
||||
#include "BC5Block.h"
|
||||
|
||||
namespace rgbcx {
|
||||
class BC5Decoder : public BlockDecoder<BC5Block, 4, 4> {
|
||||
public:
|
||||
using BC4DecoderPtr = std::shared_ptr<BC4Decoder>;
|
||||
|
||||
BC5Decoder(uint8_t chan0 = 0, uint8_t chan1 = 1) : BC5Decoder(std::make_shared<BC4Decoder>(), chan0, chan1) {}
|
||||
BC5Decoder(BC4DecoderPtr bc4_decoder, uint8_t chan0 = 0, uint8_t chan1 = 1) : _bc4_decoder(bc4_decoder), _chan0(chan0), _chan1(chan1) {
|
||||
assert(chan0 < 4U);
|
||||
assert(chan1 < 4U);
|
||||
assert(chan0 != chan1);
|
||||
}
|
||||
|
||||
void DecodeBlock(Color4x4 dest, BC5Block *const block) const noexcept(ndebug) override;
|
||||
|
||||
constexpr size_t GetChannel0() const { return _chan0; }
|
||||
constexpr size_t GetChannel1() const { return _chan1; }
|
||||
|
||||
private:
|
||||
const BC4DecoderPtr _bc4_decoder;
|
||||
const uint8_t _chan0;
|
||||
const uint8_t _chan1;
|
||||
};
|
||||
} // namespace rgbcx
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user