Refactor to add Texture classes

This commit is contained in:
Andrew Cassidy 2021-03-28 02:36:47 -07:00
parent c843871ac1
commit fb56c6af04
36 changed files with 918 additions and 627 deletions

View File

@ -19,6 +19,14 @@
#pragma once
#include <algorithm>
#include <array>
#include <climits>
#include <stdexcept>
#include "Color.h"
#include "Vector4Int.h"
namespace quicktex {
/**
@ -39,4 +47,92 @@ template <int N, int M> class Block {
static constexpr int Height = M;
};
template <int N, int M> class ColorBlock : public Block<N, M> {
public:
using Base = Block<N, M>;
struct Metrics {
Color min;
Color max;
Color avg;
bool is_greyscale;
bool has_black;
Vector4Int sums;
};
constexpr Color Get(int x, int y) const {
if (x >= Base::Width || x < 0) throw std::invalid_argument("x value out of range");
if (y >= Base::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 >= Base::Width || x < 0) throw std::invalid_argument("x value out of range");
if (y >= Base::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 >= Base::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 >= Base::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

View File

@ -1,90 +0,0 @@
/* Python-rgbcx Texture Compression Library
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich 2020 <richgel99@gmail.com>
and licenced under the public domain
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 <cmath>
#include <cstdint>
#include <memory>
#include <span>
#include <vector>
#include "BlockView.h"
#include "ndebug.h"
#include "util.h"
namespace quicktex {
class BlockDecoder {
public:
using DecoderPtr = std::shared_ptr<BlockDecoder>;
virtual ~BlockDecoder() = default;
virtual void DecodeImage(uint8_t *encoded, Color *decoded, unsigned image_width, unsigned image_height) const = 0;
virtual size_t BlockSize() const = 0;
virtual size_t BlockWidth() const = 0;
virtual size_t BlockHeight() const = 0;
};
template <class B, size_t M, size_t N>
class BlockDecoderTemplate : public BlockDecoder {
public:
using DecodedBlock = ColorBlockView<M, N>;
using EncodedBlock = B;
BlockDecoderTemplate() noexcept = default;
virtual ~BlockDecoderTemplate() noexcept = default;
virtual void DecodeBlock(DecodedBlock dest, EncodedBlock *const block) const noexcept(ndebug) = 0;
virtual void DecodeImage(uint8_t *encoded, Color *decoded, unsigned image_width, unsigned image_height) const override {
assert(image_width % N == 0);
assert(image_width % M == 0);
unsigned block_width = image_width / N;
unsigned block_height = image_height / M;
auto blocks = reinterpret_cast<B *>(encoded);
// 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 (unsigned y = 0; y < block_height; y++) {
for (unsigned x = 0; x < block_width; x++) {
unsigned pixel_x = x * N;
unsigned pixel_y = y * M;
assert(pixel_x >= 0);
assert(pixel_y >= 0);
assert(pixel_y + M <= image_height);
assert(pixel_x + N <= image_width);
unsigned top_left = pixel_x + (pixel_y * image_width);
auto dest = DecodedBlock(&decoded[top_left], (int)image_width);
DecodeBlock(dest, &blocks[x + block_width * y]);
}
}
}
virtual size_t BlockSize() const override { return sizeof(B); }
virtual size_t BlockWidth() const override { return N; }
virtual size_t BlockHeight() const override { return M; }
};
} // namespace quicktex

View File

@ -1,92 +0,0 @@
/* Python-rgbcx Texture Compression Library
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich 2020 <richgel99@gmail.com>
and licenced under the public domain
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 <climits>
#include <cstdint>
#include <memory>
#include <string>
#include "BlockView.h"
namespace quicktex {
class BlockEncoder {
public:
using EncoderPtr = std::shared_ptr<BlockEncoder>;
virtual ~BlockEncoder() = default;
virtual void EncodeImage(uint8_t *encoded, Color *decoded, unsigned image_width, unsigned image_height) const = 0;
virtual size_t BlockSize() const = 0;
virtual size_t BlockWidth() const = 0;
virtual size_t BlockHeight() const = 0;
};
template <class B, size_t M, size_t N> class BlockEncoderTemplate : public BlockEncoder {
public:
using DecodedBlock = ColorBlockView<M, N>;
using EncodedBlock = B;
BlockEncoderTemplate() noexcept = default;
virtual ~BlockEncoderTemplate() noexcept = default;
virtual void EncodeBlock(DecodedBlock pixels, EncodedBlock *dest) const = 0;
virtual void EncodeImage(uint8_t *encoded, Color *decoded, unsigned image_width, unsigned image_height) const override {
assert(image_width % N == 0);
assert(image_width % M == 0);
unsigned block_width = image_width / N;
unsigned block_height = image_height / M;
auto blocks = reinterpret_cast<B *>(encoded);
// from experimentation, multithreading this using OpenMP sometimes actually makes decoding slower
// due to thread creation/teardown taking longer than the decoding 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 (block_width * block_height >= MTThreshold())
for (int y = 0; y < (int)block_height; y++) {
for (int x = 0; x < (int)block_width; x++) {
unsigned pixel_x = (unsigned)x * N;
unsigned pixel_y = (unsigned)y * M;
assert(pixel_x >= 0);
assert(pixel_y >= 0);
assert(pixel_y + M <= image_height);
assert(pixel_x + N <= image_width);
unsigned top_left = pixel_x + (pixel_y * image_width);
unsigned block_index = (unsigned)x + (block_width * (unsigned)y);
auto src = DecodedBlock(&decoded[top_left], (int)image_width);
EncodeBlock(src, &blocks[block_index]);
}
}
}
virtual size_t BlockSize() const override { return sizeof(B); }
virtual size_t BlockWidth() const override { return N; }
virtual size_t BlockHeight() const override { return M; }
virtual size_t MTThreshold() const { return SIZE_MAX; };
};
} // namespace quicktex

View File

@ -1,170 +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 <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <vector>
#include "Color.h"
#include "Vector4Int.h"
#include "ndebug.h"
namespace quicktex {
template <typename S, size_t N> class RowView {
public:
RowView(S *start, int pixel_stride = 1) : start(start), pixel_stride(pixel_stride) {}
constexpr S &operator[](size_t index) noexcept(ndebug) {
assert(index < N);
return start[index * pixel_stride];
}
constexpr const S &operator[](size_t index) const noexcept(ndebug) {
assert(index < N);
return start[index * pixel_stride];
}
constexpr int Size() noexcept { return N; }
S *const start;
const int pixel_stride;
};
template <typename S, size_t M, size_t N> class BlockView {
public:
using Row = RowView<S, N>;
BlockView(S *start, int row_stride = N, int pixel_stride = 1) : start(start), row_stride(row_stride), pixel_stride(pixel_stride) {}
constexpr Row operator[](unsigned index) noexcept(ndebug) {
assert(index < M);
return RowView<S, N>(&start[row_stride * (int)index], pixel_stride);
}
constexpr int Width() noexcept { return N; }
constexpr int Height() noexcept { return M; }
constexpr int Size() noexcept { return N * M; }
constexpr S &Get(unsigned x, unsigned y) noexcept(ndebug) {
assert(x < N);
assert(y < M);
return start[(row_stride * (int)y) + (pixel_stride * (int)x)];
}
constexpr S Get(unsigned x, unsigned y) const noexcept(ndebug) {
assert(x < N);
assert(y < M);
return start[(row_stride * (int)y) + (pixel_stride * (int)x)];
}
constexpr void Set(unsigned x, unsigned y, S value) noexcept(ndebug) {
assert(x < N);
assert(y < M);
start[(row_stride * (int)y) + (pixel_stride * (int)x)] = value;
}
constexpr S &Get(unsigned i) noexcept(ndebug) { return Get(i % N, i / N); }
constexpr S Get(unsigned i) const noexcept(ndebug) { return Get(i % N, i / N); }
constexpr std::array<S, M * N> Flatten() noexcept {
std::array<S, M * N> result;
for (unsigned x = 0; x < N; x++) {
for (unsigned y = 0; y < M; y++) { result[x + (N * y)] = Get(x, y); }
}
return result;
}
S *const start;
const int row_stride;
const int pixel_stride;
};
template <size_t M, size_t N> class ColorBlockView : public BlockView<Color, M, N> {
public:
using Base = BlockView<Color, M, N>;
using ChannelView = BlockView<uint8_t, M, N>;
struct BlockMetrics {
Color min;
Color max;
Color avg;
bool is_greyscale;
bool has_black;
Vector4Int sums;
};
ColorBlockView(Color *start, int row_stride = N, int pixel_stride = 1) : Base(start, row_stride, pixel_stride) {}
constexpr ChannelView GetChannel(uint8_t index) noexcept(ndebug) {
assert(index < 4U);
auto channelStart = reinterpret_cast<uint8_t *>(Base::start) + index;
return ChannelView(channelStart, Base::row_stride * 4, Base::pixel_stride * 4);
}
void SetRGB(unsigned x, unsigned y, Color value) noexcept(ndebug) { Base::Get(x, y).SetRGB(value); }
bool IsSingleColor() {
auto first = Base::Get(0, 0);
for (unsigned j = 1; j < M * N; j++) {
if (Base::Get(j) != first) return false;
}
return true;
}
BlockMetrics GetMetrics(bool ignore_black = false) {
BlockMetrics 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 = Base::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;
}
};
using Color4x4 = ColorBlockView<4, 4>;
using Byte4x4 = BlockView<uint8_t, 4, 4>;
} // namespace quicktex

View File

@ -26,10 +26,6 @@
namespace quicktex {
constexpr Color::Color() { SetRGBA(0, 0, 0, 0xFF); }
constexpr Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { SetRGBA(r, g, b, a); }
Color::Color(Vector4Int v) { SetRGBA((uint8_t)v[0], (uint8_t)v[1], (uint8_t)v[2], (uint8_t)v[3]); }
uint16_t Color::Pack565Unscaled(uint8_t r, uint8_t g, uint8_t b) {

View File

@ -34,9 +34,9 @@ class Color {
uint8_t b;
uint8_t a;
constexpr Color();
constexpr Color() { SetRGBA(0, 0, 0, 0xFF); }
constexpr Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0xFF);
constexpr Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0xFF) { SetRGBA(r, g, b, a); }
Color(Vector4Int v);

68
quicktex/Decoder.h Normal file
View File

@ -0,0 +1,68 @@
/* Python-rgbcx Texture Compression Library
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich 2020 <richgel99@gmail.com>
and licenced under the public domain
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 "Block.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;
};
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
View File

@ -0,0 +1,72 @@
/* Python-rgbcx Texture Compression Library
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich 2020 <richgel99@gmail.com>
and licenced under the public domain
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 "Block.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;
};
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

61
quicktex/Texture.cpp Normal file
View File

@ -0,0 +1,61 @@
/* 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 "Texture.h"
#include <cstdint>
#include <stdexcept>
#include "Color.h"
namespace quicktex {
RawTexture::RawTexture(int width, int height) : Base(width, height) { _pixels = new Color[(size_t)(_width * _height)]; }
RawTexture::RawTexture(RawTexture&& other) : Base(other._width, other._height) {
_pixels = other._pixels;
other._pixels = nullptr;
}
RawTexture::RawTexture(const RawTexture& other) : RawTexture(other._width, other._height) {
std::memcpy(_pixels, other._pixels, (size_t)(_width * _height) * sizeof(Color));
}
RawTexture& RawTexture::operator=(RawTexture other) noexcept {
swap(*this, other);
return *this;
}
void swap(RawTexture& first, RawTexture& second) noexcept {
using std::swap; // enable ADL
swap(first._pixels, second._pixels);
swap(first._width, second._width);
swap(first._height, second._height);
}
Color RawTexture::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[x + (y * _width)];
}
void RawTexture::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[x + (y * _width)] = val;
}
} // namespace quicktex

219
quicktex/Texture.h Normal file
View File

@ -0,0 +1,219 @@
/* 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 <cstdint>
#include <tuple>
#include <vector>
#include "Block.h"
#include "Color.h"
#include "ndebug.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> Dimensions() const { return std::tuple<int, int>(_width, _height); }
/**
* The texture's total size
* @return The size of the texture in bytes.
*/
virtual size_t Size() const 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");
}
virtual uint8_t *DataMutable() = 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);
/**
* Move constructor
* @param other object to move
*/
RawTexture(RawTexture &&other);
/**
* Copy constructor
* @param other object to copy
*/
RawTexture(const RawTexture &other);
/**
* assignment operator
* @param other object to copy (passed by value)
* @return this
*/
RawTexture &operator=(RawTexture other) noexcept;
virtual ~RawTexture() override { delete[] _pixels; }
friend void swap(RawTexture &first, RawTexture &second) noexcept;
virtual Color GetPixel(int x, int y) const;
virtual void SetPixel(int x, int y, Color val);
virtual size_t Size() 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 || (block_x + 1) * N > _width) throw std::invalid_argument("x value out of range.");
if (block_y < 0 || (block_y + 1) * M > _height) throw std::invalid_argument("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::invalid_argument("x value out of range.");
if (block_y < 0) throw std::invalid_argument("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)); }
}
}
};
protected:
virtual uint8_t *DataMutable() override { return reinterpret_cast<uint8_t *>(_pixels); }
Color *_pixels;
};
template <typename B> class BlockTexture : public Texture {
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) { _blocks = new B[(size_t)(BlocksX() * BlocksY())]; }
/**
* Move constructor
* @param other object to move
*/
BlockTexture(BlockTexture<B> &&other) : Base(other._width, other._height) {
_blocks = other._blocks;
other._blocks = nullptr;
}
/**
* Copy constructor
* @param other object to copy
*/
BlockTexture(const BlockTexture<B> &other) : BlockTexture(other.width, other.height) { std::memcpy(_blocks, other._blocks, Size()); }
/**
* assignment operator
* @param other object to copy (passed by value)
* @return this
*/
BlockTexture &operator=(BlockTexture<B> other) {
swap(*this, other);
return *this;
}
friend void swap(BlockTexture<B> &first, BlockTexture<B> &second) noexcept {
using std::swap; // enable ADL
swap(first._blocks, second._blocks);
swap(first._width, second._width);
swap(first._height, second._height);
}
~BlockTexture() { delete[] _blocks; }
constexpr int BlocksX() const { return _width / B::Width; }
constexpr int BlocksY() const { return _height / B::Height; }
virtual B GetBlock(int x, int y) const {
if (x < 0 || x >= BlocksX()) throw std::invalid_argument("x value out of range.");
if (y < 0 || y >= BlocksY()) throw std::invalid_argument("y value out of range.");
return _blocks[x + (y * _width)];
}
virtual void SetBlock(int x, int y, const B &val) {
if (x < 0 || x >= BlocksX()) throw std::invalid_argument("x value out of range.");
if (y < 0 || y >= BlocksY()) throw std::invalid_argument("y value out of range.");
_blocks[x + (y * _width)] = val;
}
virtual size_t Size() const noexcept override { return (size_t)(BlocksX() * BlocksY()) * sizeof(B); }
protected:
virtual uint8_t *DataMutable() override { return reinterpret_cast<uint8_t *>(_blocks); }
B *_blocks;
};
} // namespace quicktex

View File

@ -17,17 +17,73 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "_bindings.h"
#include <pybind11/pybind11.h>
#include "BlockDecoder.h"
#include "BlockEncoder.h"
#include "Color.h"
#include "Decoder.h"
#include "Encoder.h"
#include "Texture.h"
#include "_bindings.h"
namespace py = pybind11;
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 policy, handle parent) {
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 quicktex::bindings {
void InitS3TC(py::module_ &m);
py::bytes EncodeImage(const BlockEncoder &self, py::bytes decoded, unsigned image_width, unsigned image_height) {
/*py::bytes EncodeImage(const BlockEncoder &self, py::bytes decoded, unsigned image_width, unsigned image_height) {
if (image_width % self.BlockWidth() != 0) throw std::invalid_argument("Width is not an even multiple of block_width");
if (image_height % self.BlockHeight() != 0) throw std::invalid_argument("Height is not an even multiple of block_height");
if (image_width == 0 || image_height == 0) throw std::invalid_argument("Image has zero size");
@ -49,12 +105,12 @@ py::bytes EncodeImage(const BlockEncoder &self, py::bytes decoded, unsigned imag
}
py::bytes DecodeImage(const BlockDecoder &self, py::bytes encoded, unsigned image_width, unsigned image_height) {
if (image_width % self.BlockWidth() != 0) throw std::invalid_argument("Width is not an even multiple of block_width");
if (image_height % self.BlockHeight() != 0) throw std::invalid_argument("Height is not an even multiple of block_height");
if (image_width % self.WidthInBlocks() != 0) throw std::invalid_argument("Width is not an even multiple of block_width");
if (image_height % self.HeightInBlocks() != 0) throw std::invalid_argument("Height is not an even multiple of block_height");
if (image_width == 0 || image_height == 0) throw std::invalid_argument("Image has zero size");
size_t size = image_width * image_height;
size_t block_size = (size / (self.BlockHeight() * self.BlockWidth())) * self.BlockSize();
size_t block_size = (size / (self.HeightInBlocks() * self.WidthInBlocks())) * self.BlockSize();
size_t color_size = size * sizeof(Color);
std::string encoded_str = (std::string)encoded; // encoded data is copied here, unfortunately
@ -67,28 +123,25 @@ py::bytes DecodeImage(const BlockDecoder &self, py::bytes encoded, unsigned imag
auto bytes = py::bytes(decoded_str); // decoded data is copied here, unfortunately
return bytes;
}
}*/
PYBIND11_MODULE(_quicktex, m) {
m.doc() = "More Stuff";
py::options options;
// BlockDecoder
py::class_<BlockDecoder> block_decoder(m, "BlockDecoder");
py::class_<Texture> texture(m, "Texture");
block_decoder.def("decode_image", &DecodeImage);
block_decoder.def_property_readonly("block_size", &BlockDecoder::BlockSize);
block_decoder.def_property_readonly("block_width", &BlockDecoder::BlockWidth);
block_decoder.def_property_readonly("block_height", &BlockDecoder::BlockHeight);
texture.def_property_readonly("size", &Texture::Size);
texture.def_property_readonly("dimensions", &Texture::Dimensions);
texture.def_property_readonly("width", &Texture::Width);
texture.def_property_readonly("height", &Texture::Height);
// BlockEncoder
py::class_<BlockEncoder> block_encoder(m, "BlockEncoder");
py::class_<RawTexture> raw_texture(m, "RawTexture", texture);
block_encoder.def("encode_image", &EncodeImage);
block_encoder.def_property_readonly("block_size", &BlockEncoder::BlockSize);
block_encoder.def_property_readonly("block_width", &BlockEncoder::BlockWidth);
block_encoder.def_property_readonly("block_height", &BlockEncoder::BlockHeight);
raw_texture.def(py::init<int, int>(), "width"_a, "height"_a);
raw_texture.def("get_pixel", &RawTexture::GetPixel);
raw_texture.def("set_pixel", &RawTexture::SetPixel);
InitS3TC(m);
}

54
quicktex/_bindings.h Normal file
View File

@ -0,0 +1,54 @@
/* Quicktex Texture Compression Library
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <pybind11/pybind11.h>
#include <memory>
#include <stdexcept>
#include "Block.h"
#include "Color.h"
#include "Texture.h"
namespace pybind11::detail {
template <> struct type_caster<quicktex::Color>;
} // namespace pybind11::detail
namespace py = pybind11;
namespace quicktex::bindings {
using namespace pybind11::literals;
template <typename B> py::class_<BlockTexture<B>> BindBlockTexture(py::module_& m, const char* name) {
using BTex = BlockTexture<B>;
py::type texture = py::type::of<Texture>();
py::class_<BTex> block_texture(m, name, texture);
block_texture.def(py::init<int, int>(), "width"_a, "height"_a);
block_texture.def("get_block", &BTex::GetBlock, "x"_a, "y"_a);
block_texture.def("set_block", &BTex::SetBlock, "x"_a, "y"_a, "block"_a);
block_texture.def_property_readonly("blocks_x", &BTex::BlocksX);
block_texture.def_property_readonly("blocks_y", &BTex::BlocksY);
}
} // namespace quicktex::bindings

View File

@ -24,15 +24,18 @@
#include <cstdint>
#include <cstdlib>
#include "../../Block.h"
#include "../../Color.h"
#include "../../util.h"
namespace quicktex::s3tc {
namespace quicktex::s3tc {
#pragma pack(push, 1)
class BC1Block {
class BC1Block : public Block<4, 4> {
public:
using UnpackedSelectors = std::array<std::array<uint8_t, 4>, 4>;
using UnpackedSelectors = std::array<std::array<uint8_t, Width>, Height>;
constexpr BC1Block() { static_assert(sizeof(BC1Block) == 8); }
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)); }

View File

@ -23,34 +23,32 @@
#include <cassert>
#include <cstdint>
#include "../../BlockView.h"
#include "../../Color.h"
#include "../../ndebug.h"
#include "BC1Block.h"
namespace quicktex::s3tc {
void BC1Decoder::DecodeBlock(Color4x4 dest, BC1Block *const block) const noexcept(ndebug) {
DecodeBlock(dest, block, true);
}
namespace quicktex::s3tc {
void BC1Decoder::DecodeBlock(Color4x4 dest, BC1Block *const block, bool allow_3color) 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, allow_3color);
ColorBlock<4, 4> BC1Decoder::DecodeBlock(const BC1Block &block) const { return DecodeBlock(block, false); }
ColorBlock<4, 4> BC1Decoder::DecodeBlock(const BC1Block &block, bool use_3color) const {
auto output = ColorBlock<4, 4>();
const auto l = block.GetLowColor();
const auto h = block.GetHighColor();
const auto selectors = block.UnpackSelectors();
const auto colors = _interpolator->InterpolateBC1(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 quicktex::s3tc

View File

@ -22,14 +22,14 @@
#include <memory>
#include <type_traits>
#include "../../BlockDecoder.h"
#include "../../BlockView.h"
#include "../../Decoder.h"
#include "../../Texture.h"
#include "../../ndebug.h"
#include "../interpolator/Interpolator.h"
#include "BC1Block.h"
namespace quicktex::s3tc {
class BC1Decoder final : public BlockDecoderTemplate<BC1Block, 4, 4> {
class BC1Decoder final : public BlockDecoder<BlockTexture<BC1Block>> {
public:
using InterpolatorPtr = std::shared_ptr<Interpolator>;
@ -39,9 +39,8 @@ class BC1Decoder final : public BlockDecoderTemplate<BC1Block, 4, 4> {
BC1Decoder(InterpolatorPtr interpolator) : BC1Decoder(false, interpolator) {}
void DecodeBlock(Color4x4 dest, BC1Block *const block) const noexcept(ndebug) override;
void DecodeBlock(Color4x4 dest, BC1Block *const block, bool allow_3color) const noexcept(ndebug);
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; }

View File

@ -28,7 +28,7 @@
#include <stdexcept>
#include <type_traits>
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../Color.h"
#include "../../Matrix4x4.h"
#include "../../Vector4.h"
@ -42,6 +42,9 @@
namespace quicktex::s3tc {
using CBlock = ColorBlock<4, 4>;
using BlockMetrics = CBlock::Metrics;
// constructors
BC1Encoder::BC1Encoder(unsigned int level, ColorMode color_mode, InterpolatorPtr interpolator) : _interpolator(interpolator), _color_mode(color_mode) {
@ -258,11 +261,10 @@ void BC1Encoder::SetOrderings(OrderingPair orderings) {
void BC1Encoder::SetPowerIterations(unsigned int power_iters) { _power_iterations = clamp(power_iters, min_power_iterations, max_power_iterations); }
// Public methods
void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const {
BC1Block BC1Encoder::EncodeBlock(const ColorBlock<4, 4> &pixels) const {
if (pixels.IsSingleColor()) {
// single-color pixel block, do it the fast way
WriteBlockSolid(pixels.Get(0, 0), dest);
return;
return WriteBlockSolid(pixels.Get(0, 0));
}
auto metrics = pixels.GetMetrics();
@ -289,12 +291,12 @@ void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const {
EndpointMode endpoint_mode = (round == 1) ? EndpointMode::BoundingBox : _endpoint_mode;
EncodeResults trial_orig;
FindEndpoints(pixels, trial_orig, metrics, endpoint_mode);
FindEndpoints(trial_orig, pixels, metrics, endpoint_mode);
EncodeResults trial_result = trial_orig;
FindSelectors<ColorMode::FourColor>(pixels, trial_result, error_mode);
RefineBlockLS<ColorMode::FourColor>(pixels, trial_result, metrics, error_mode, total_ls_passes);
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;
@ -304,20 +306,20 @@ void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const {
// 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>(pixels, result, metrics, _error_mode, _orderings4); }
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>(pixels, trial_result, ErrorMode::Full);
RefineBlockLS<ColorMode::ThreeColor>(pixels, trial_result, metrics, ErrorMode::Full, total_ls_passes);
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>(pixels, trial_result, metrics, ErrorMode::Full, _orderings3);
RefineBlockCF<ColorMode::ThreeColor>(trial_result, pixels, metrics, ErrorMode::Full, _orderings3);
}
}
@ -329,21 +331,22 @@ void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const {
EncodeResults trial_result;
BlockMetrics metrics_no_black = pixels.GetMetrics(true);
FindEndpoints(pixels, trial_result, metrics_no_black, EndpointMode::PCA, true);
FindSelectors<ColorMode::ThreeColorBlack>(pixels, trial_result, ErrorMode::Full);
RefineBlockLS<ColorMode::ThreeColorBlack>(pixels, trial_result, metrics_no_black, ErrorMode::Full, total_ls_passes);
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(pixels, result); }
if (result.error > 0 && _search_rounds > 0) { EndpointSearch(result, pixels); }
WriteBlock(result, dest);
WriteBlock(result);
}
// Private methods
void BC1Encoder::WriteBlockSolid(Color color, BC1Block *dest) const {
BC1Block BC1Encoder::WriteBlockSolid(Color color) const {
BC1Block block;
uint8_t mask = 0xAA; // 2222
uint16_t min16, max16;
@ -390,23 +393,25 @@ void BC1Encoder::WriteBlockSolid(Color color, BC1Block *dest) const {
}
}
dest->SetLowColor(max16);
dest->SetHighColor(min16);
dest->selectors[0] = mask;
dest->selectors[1] = mask;
dest->selectors[2] = mask;
dest->selectors[3] = mask;
block.SetLowColor(max16);
block.SetHighColor(min16);
block.selectors[0] = mask;
block.selectors[1] = mask;
block.selectors[2] = mask;
block.selectors[3] = mask;
return block;
}
void BC1Encoder::WriteBlock(EncodeResults &block, BC1Block *dest) const {
BC1Block BC1Encoder::WriteBlock(EncodeResults &result) const {
BC1Block block;
BC1Block::UnpackedSelectors selectors;
uint16_t color1 = block.low.Pack565Unscaled();
uint16_t color0 = block.high.Pack565Unscaled();
uint16_t color1 = result.low.Pack565Unscaled();
uint16_t color0 = result.high.Pack565Unscaled();
std::array<uint8_t, 4> lut;
assert(block.color_mode != ColorMode::Incomplete);
assert(result.color_mode != ColorMode::Incomplete);
if ((bool)(block.color_mode & ColorMode::FourColor)) {
if ((bool)(result.color_mode & ColorMode::FourColor)) {
lut = {1, 3, 2, 0};
if (color1 > color0) {
@ -439,16 +444,16 @@ void BC1Encoder::WriteBlock(EncodeResults &block, BC1Block *dest) const {
for (unsigned i = 0; i < 16; i++) {
unsigned x = i % 4;
unsigned y = i / 4;
selectors[y][x] = lut[block.selectors[i]];
if (block.color_mode == ColorMode::ThreeColor) { assert(selectors[y][x] != 3); }
selectors[y][x] = lut[result.selectors[i]];
if (result.color_mode == ColorMode::ThreeColor) { assert(selectors[y][x] != 3); }
}
dest->SetLowColor(color0);
dest->SetHighColor(color1);
dest->PackSelectors(selectors);
block.SetLowColor(color0);
block.SetHighColor(color1);
block.PackSelectors(selectors);
}
void BC1Encoder::FindEndpointsSingleColor(EncodeResults &block, Color color, bool is_3color) const {
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;
@ -456,40 +461,40 @@ void BC1Encoder::FindEndpointsSingleColor(EncodeResults &block, Color color, boo
BC1MatchEntry match_g = match6->at(color.g);
BC1MatchEntry match_b = match5->at(color.b);
block.color_mode = is_3color ? ColorMode::ThreeColor : ColorMode::FourColor;
block.error = match_r.error + match_g.error + match_b.error;
block.low = Color(match_r.low, match_g.low, match_b.low);
block.high = Color(match_r.high, match_g.high, match_b.high);
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 &block, Color4x4 &pixels, Color color, bool is_3color) const {
std::array<Color, 4> colors = _interpolator->InterpolateBC1(block.low, block.high, is_3color);
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(block, color, is_3color);
FindEndpointsSingleColor(result, color, is_3color);
block.error = 0;
result.error = 0;
for (unsigned i = 0; i < 16; i++) {
Vector4Int pixel_vector = (Vector4Int)pixels.Get(i);
auto diff = pixel_vector - result_vector;
block.error += diff.SqrMag();
block.selectors[i] = 1;
result.error += diff.SqrMag();
result.selectors[i] = 1;
}
}
void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const BlockMetrics &metrics, EndpointMode endpoint_mode, bool ignore_black) const {
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).r;
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);
block.low = Color(fr5, fr6, fr5);
block.high = block.low;
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);
@ -497,8 +502,8 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc
uint8_t hr5 = scale8To5(metrics.max.r);
uint8_t hr6 = scale8To6(metrics.max.r);
block.low = Color(lr5, lr6, lr5);
block.high = Color(hr5, hr6, hr5);
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.
@ -556,8 +561,8 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc
h[c] = ((h[c] - inset) / 255.0f);
}
block.low = Color::PreciseRound565(l);
block.high = Color::PreciseRound565(h);
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;
@ -584,8 +589,8 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc
if (icov_xz < 0) std::swap(l[0], h[0]);
if (icov_yz < 0) std::swap(l[1], h[1]);
block.low = Color::PreciseRound565(l);
block.high = Color::PreciseRound565(h);
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.
@ -609,8 +614,8 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc
if (icov_xz < 0) std::swap(min.r, max.r);
if (icov_yz < 0) std::swap(min.g, max.g);
block.low = min.ScaleTo565();
block.high = max.ScaleTo565();
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.)
@ -677,19 +682,19 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc
}
}
block.low = pixels.Get(min_index).ScaleTo565();
block.high = pixels.Get(max_index).ScaleTo565();
result.low = pixels.Get(min_index).ScaleTo565();
result.high = pixels.Get(max_index).ScaleTo565();
}
block.color_mode = ColorMode::Incomplete;
result.color_mode = ColorMode::Incomplete;
}
template <BC1Encoder::ColorMode M> void BC1Encoder::FindSelectors(Color4x4 &pixels, EncodeResults &block, ErrorMode error_mode) const {
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(block.low, block.high, color_count == 3);
std::array<Color, 4> colors = _interpolator->InterpolateBC1(result.low, result.high, color_count == 3);
std::array<Vector4Int, 4> color_vectors;
if (color_count == 4) {
@ -721,10 +726,10 @@ template <BC1Encoder::ColorMode M> void BC1Encoder::FindSelectors(Color4x4 &pixe
// 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 >= block.error) break; // check only once per row if we're generating too much error
if (i % 4 != 0 && total_error >= result.error) break; // check only once per row if we're generating too much error
}
block.selectors[i] = selector;
result.selectors[i] = selector;
}
} else if (error_mode == ErrorMode::Check2) {
Vector4Int axis = color_vectors[3] - color_vectors[0];
@ -751,9 +756,9 @@ template <BC1Encoder::ColorMode M> void BC1Encoder::FindSelectors(Color4x4 &pixe
total_error += best_err;
if (total_error >= block.error) break;
if (total_error >= result.error) break;
block.selectors[i] = best_sel;
result.selectors[i] = best_sel;
}
} else if (error_mode == ErrorMode::Full) {
unsigned max_sel = (bool)(M == ColorMode::ThreeColor) ? 3 : 4;
@ -774,22 +779,22 @@ template <BC1Encoder::ColorMode M> void BC1Encoder::FindSelectors(Color4x4 &pixe
}
total_error += best_error;
if (total_error >= block.error) { break; }
if (total_error >= result.error) { break; }
assert(best_sel < max_sel);
block.selectors[i] = best_sel;
result.selectors[i] = best_sel;
}
} else {
assert(false);
}
block.error = total_error;
block.color_mode = M;
result.error = total_error;
result.color_mode = M;
}
template <BC1Encoder::ColorMode M> bool BC1Encoder::RefineEndpointsLS(Color4x4 pixels, EncodeResults &block, BlockMetrics metrics) const {
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(block.color_mode != ColorMode::Incomplete);
assert(result.color_mode != ColorMode::Incomplete);
int denominator = color_count - 1;
@ -798,7 +803,7 @@ template <BC1Encoder::ColorMode M> bool BC1Encoder::RefineEndpointsLS(Color4x4 p
for (unsigned i = 0; i < 16; i++) {
const Color color = pixels.Get(i);
const uint8_t sel = block.selectors[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
@ -813,7 +818,7 @@ template <BC1Encoder::ColorMode M> bool BC1Encoder::RefineEndpointsLS(Color4x4 p
// invert matrix
float det = matrix.Determinant2x2(); // z00 * z11 - z01 * z10;
if (fabs(det) < 1e-8f) {
block.color_mode = ColorMode::Incomplete;
result.color_mode = ColorMode::Incomplete;
return false;
}
@ -826,16 +831,16 @@ template <BC1Encoder::ColorMode M> bool BC1Encoder::RefineEndpointsLS(Color4x4 p
Vector4 low = (matrix[0] * q00) + (matrix[1] * q10);
Vector4 high = (matrix[2] * q00) + (matrix[3] * q10);
block.color_mode = M;
block.low = Color::PreciseRound565(low);
block.high = Color::PreciseRound565(high);
result.color_mode = M;
result.low = Color::PreciseRound565(low);
result.high = Color::PreciseRound565(high);
return true;
}
template <BC1Encoder::ColorMode M> void BC1Encoder::RefineEndpointsLS(std::array<Vector4, 17> &sums, EncodeResults &block, Vector4 &matrix, Hash hash) const {
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(block.color_mode != ColorMode::Incomplete);
assert(result.color_mode != ColorMode::Incomplete);
int denominator = color_count - 1;
@ -852,30 +857,30 @@ template <BC1Encoder::ColorMode M> void BC1Encoder::RefineEndpointsLS(std::array
Vector4 low = (matrix[0] * q00) + (matrix[1] * q10);
Vector4 high = (matrix[2] * q00) + (matrix[3] * q10);
block.color_mode = M;
block.low = Color::PreciseRound565(low);
block.high = Color::PreciseRound565(high);
result.color_mode = M;
result.low = Color::PreciseRound565(low);
result.high = Color::PreciseRound565(high);
}
template <BC1Encoder::ColorMode M>
void BC1Encoder::RefineBlockLS(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned passes) const {
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 = block;
EncodeResults trial_result = result;
Vector4 low, high;
bool multicolor = RefineEndpointsLS<ColorMode::FourColor>(pixels, trial_result, metrics);
bool multicolor = RefineEndpointsLS<ColorMode::FourColor>(trial_result, pixels, metrics);
if (!multicolor) {
FindEndpointsSingleColor(trial_result, pixels, metrics.avg, (M != ColorMode::FourColor));
} else {
FindSelectors<M>(pixels, trial_result, error_mode);
FindSelectors<M>(trial_result, pixels, error_mode);
}
if (trial_result.low == block.low && trial_result.high == block.high) break;
if (trial_result.low == result.low && trial_result.high == result.high) break;
if (error_mode == ErrorMode::None || trial_result.error < block.error) {
block = trial_result;
if (error_mode == ErrorMode::None || trial_result.error < result.error) {
result = trial_result;
} else {
return;
}
@ -883,15 +888,15 @@ void BC1Encoder::RefineBlockLS(Color4x4 &pixels, EncodeResults &block, BlockMetr
}
template <BC1Encoder::ColorMode M>
void BC1Encoder::RefineBlockCF(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const {
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(block.color_mode != ColorMode::Incomplete);
assert(result.color_mode != ColorMode::Incomplete);
using OrderTable = OrderTable<color_count>;
using Hist = Histogram<color_count>;
EncodeResults orig = block;
EncodeResults orig = result;
Hist h = Hist(orig.selectors);
Hash start_hash = OrderTable::GetHash(h);
@ -900,11 +905,11 @@ void BC1Encoder::RefineBlockCF(Color4x4 &pixels, EncodeResults &block, BlockMetr
std::array<Vector4, 16> color_vectors;
std::array<uint32_t, 16> dots;
for (unsigned i = 0; i < 16; i++) {
color_vectors[i] = Vector4::FromColorRGB(pixels.Get(i));
int dot = 0x1000000 + (int)color_vectors[i].Dot(axis);
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[i] = (uint32_t)(dot << 4) | i;
dots[(unsigned)i] = (uint32_t)(dot << 4) | i;
}
std::sort(dots.begin(), dots.end());
@ -926,17 +931,17 @@ void BC1Encoder::RefineBlockCF(Color4x4 &pixels, EncodeResults &block, BlockMetr
if (OrderTable::IsSingleColor(trial_hash)) {
FindEndpointsSingleColor(trial_result, pixels, metrics.avg, (color_count == 3));
} else {
RefineEndpointsLS<M>(sums, trial_result, trial_matrix, trial_hash);
FindSelectors<M>(pixels, trial_result, error_mode);
RefineEndpointsLS<M>(trial_result, sums, trial_matrix, trial_hash);
FindSelectors<M>(trial_result, pixels, error_mode);
}
if (trial_result.error < block.error) { block = trial_result; }
if (trial_result.error < result.error) { result = trial_result; }
if (trial_result.error == 0) break;
}
}
void BC1Encoder::EndpointSearch(Color4x4 &pixels, EncodeResults &block) const {
if (block.solid) return;
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
@ -967,7 +972,7 @@ void BC1Encoder::EndpointSearch(Color4x4 &pixels, EncodeResults &block) const {
if ((int)(i & 31) == forbidden_direction) continue;
Vector4Int delta = Voxels[voxel_index];
EncodeResults trial_result = block;
EncodeResults trial_result = result;
if (i & 16) {
trial_result.low.r = (uint8_t)clamp(trial_result.low.r + delta[0], 0, 31);
@ -979,21 +984,21 @@ void BC1Encoder::EndpointSearch(Color4x4 &pixels, EncodeResults &block) const {
trial_result.high.b = (uint8_t)clamp(trial_result.high.b + delta[2], 0, 31);
}
switch (block.color_mode) {
switch (result.color_mode) {
default:
case ColorMode::FourColor:
FindSelectors<ColorMode::FourColor>(pixels, trial_result, _error_mode);
FindSelectors<ColorMode::FourColor>(trial_result, pixels, _error_mode);
break;
case ColorMode::ThreeColor:
FindSelectors<ColorMode::ThreeColor>(pixels, trial_result, ErrorMode::Full);
FindSelectors<ColorMode::ThreeColor>(trial_result, pixels, ErrorMode::Full);
break;
case ColorMode::ThreeColorBlack:
FindSelectors<ColorMode::ThreeColorBlack>(pixels, trial_result, ErrorMode::Full);
FindSelectors<ColorMode::ThreeColorBlack>(trial_result, pixels, ErrorMode::Full);
break;
}
if (trial_result.error < block.error) {
block = trial_result;
if (trial_result.error < result.error) {
result = trial_result;
forbidden_direction = delta[3] | (int)(i & 16);
prev_improvement_index = i;

View File

@ -27,9 +27,10 @@
#include <tuple>
#include <type_traits>
#include "../../BlockEncoder.h"
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../Color.h"
#include "../../Encoder.h"
#include "../../Texture.h"
#include "../interpolator/Interpolator.h"
#include "BC1Block.h"
#include "SingleColorTable.h"
@ -40,10 +41,11 @@ class Vector4;
namespace quicktex::s3tc {
class BC1Encoder final : public BlockEncoderTemplate<BC1Block, 4, 4> {
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>;
inline static constexpr unsigned min_power_iterations = 4;
inline static constexpr unsigned max_power_iterations = 10;
@ -130,13 +132,13 @@ class BC1Encoder final : public BlockEncoderTemplate<BC1Block, 4, 4> {
void SetPowerIterations(unsigned power_iters);
// Public Methods
void EncodeBlock(Color4x4 pixels, BC1Block *dest) const override;
BC1Block EncodeBlock(const CBlock &pixels) const override;
virtual size_t MTThreshold() const override { return 16; }
private:
using Hash = uint16_t;
using BlockMetrics = Color4x4::BlockMetrics;
using BlockMetrics = CBlock::Metrics;
// Unpacked BC1 block with metadata
struct EncodeResults {
@ -168,23 +170,25 @@ class BC1Encoder final : public BlockEncoderTemplate<BC1Block, 4, 4> {
unsigned _orderings4;
unsigned _orderings3;
void WriteBlockSolid(Color color, BC1Block *dest) const;
void WriteBlock(EncodeResults &block, BC1Block *dest) const;
BC1Block WriteBlockSolid(Color color) const;
BC1Block WriteBlock(EncodeResults &result) const;
void FindEndpoints(Color4x4 pixels, EncodeResults &block, const BlockMetrics &metrics, EndpointMode endpoint_mode, bool ignore_black = false) const;
void FindEndpointsSingleColor(EncodeResults &block, Color color, bool is_3color = false) const;
void FindEndpointsSingleColor(EncodeResults &block, Color4x4 &pixels, Color color, bool is_3color) 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(Color4x4 &pixels, EncodeResults &block, ErrorMode error_mode) const;
template <ColorMode M> void FindSelectors(EncodeResults &result, const CBlock &pixels, ErrorMode error_mode) const;
template <ColorMode M> bool RefineEndpointsLS(Color4x4 pixels, EncodeResults &block, BlockMetrics metrics) const;
template <ColorMode M> bool RefineEndpointsLS(EncodeResults &result, const CBlock &pixels, BlockMetrics metrics) const;
template <ColorMode M> void RefineEndpointsLS(std::array<Vector4, 17> &sums, EncodeResults &block, Vector4 &matrix, Hash hash) const;
template <ColorMode M> void RefineEndpointsLS(EncodeResults &result, std::array<Vector4, 17> &sums, Vector4 &matrix, Hash hash) const;
template <ColorMode M> void RefineBlockLS(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned passes) 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(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const;
template <ColorMode M>
void RefineBlockCF(EncodeResults &result, const CBlock &pixels, const BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const;
void EndpointSearch(Color4x4 &pixels, EncodeResults &block) const;
void EndpointSearch(EncodeResults &result, const CBlock &pixels) const;
};
} // namespace quicktex::s3tc

View File

@ -25,8 +25,8 @@
#include <stdexcept>
#include <string>
#include "../../BlockDecoder.h"
#include "../../BlockEncoder.h"
#include "../../Decoder.h"
#include "../../Encoder.h"
#include "../interpolator/Interpolator.h"
#include "BC1Decoder.h"
#include "BC1Encoder.h"
@ -41,16 +41,12 @@ using InterpolatorPtr = std::shared_ptr<Interpolator>;
void InitBC1(py::module_ &s3tc) {
auto bc1 = s3tc.def_submodule("_bc1", "internal bc1 module");
auto block_encoder = py::type::of<BlockEncoder>();
auto block_decoder = py::type::of<BlockDecoder>();
py::options options;
options.disable_function_signatures();
// BC1Encoder
py::class_<BC1Encoder> bc1_encoder(bc1, "BC1Encoder", block_encoder, R"doc(
Base: :py:class:`~quicktex.BlockEncoder`
py::class_<BC1Encoder> bc1_encoder(bc1, "BC1Encoder" R"doc(
Encodes RGB textures to BC1.
)doc");
@ -133,7 +129,7 @@ void InitBC1(py::module_ &s3tc) {
"Automatically clamped to between :py:const:`BC1Encoder.min_power_iterations` and :py:const:`BC1Encoder.max_power_iterations`");
// BC1Decoder
py::class_<BC1Decoder> bc1_decoder(bc1, "BC1Decoder", block_decoder, R"doc(
py::class_<BC1Decoder> bc1_decoder(bc1, "BC1Decoder", R"doc(
Base: :py:class:`~quicktex.BlockDecoder`
Decodes BC1 textures to RGB

View File

@ -19,13 +19,14 @@
#pragma once
#include "../../Block.h"
#include "../bc1/BC1Block.h"
#include "../bc4/BC4Block.h"
namespace quicktex::s3tc {
namespace quicktex::s3tc {
#pragma pack(push, 1)
class BC3Block {
class BC3Block : public Block<4, 4> {
public:
BC4Block alpha_block;
BC1Block color_block;

View File

@ -21,14 +21,17 @@
#include <type_traits>
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../ndebug.h"
#include "BC3Block.h"
namespace quicktex::s3tc {
namespace quicktex::s3tc {
void BC3Decoder::DecodeBlock(Color4x4 dest, BC3Block *const block) const noexcept(ndebug) {
_bc1_decoder->DecodeBlock(dest, &(block->color_block), false);
_bc4_decoder->DecodeBlock(dest, &(block->alpha_block));
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 quicktex::s3tc

View File

@ -21,8 +21,8 @@
#include <memory>
#include "../../BlockDecoder.h"
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../Decoder.h"
#include "../../ndebug.h"
#include "../bc1/BC1Decoder.h"
#include "../bc4/BC4Decoder.h"
@ -31,7 +31,7 @@
namespace quicktex::s3tc {
class BC3Decoder : public BlockDecoderTemplate<BC3Block, 4, 4> {
class BC3Decoder : public BlockDecoder<BlockTexture<BC3Block>> {
public:
using BC1DecoderPtr = std::shared_ptr<BC1Decoder>;
using BC4DecoderPtr = std::shared_ptr<BC4Decoder>;
@ -41,7 +41,7 @@ class BC3Decoder : public BlockDecoderTemplate<BC3Block, 4, 4> {
BC3Decoder() : BC3Decoder(std::make_shared<Interpolator>()) {}
void DecodeBlock(Color4x4 dest, BC3Block *const block) const noexcept(ndebug) override;
ColorBlock<4, 4> DecodeBlock(const BC3Block &block) const override;
BC1DecoderPtr GetBC1Decoder() const { return _bc1_decoder; }
BC4DecoderPtr GetBC4Decoder() const { return _bc4_decoder; }

View File

@ -19,12 +19,14 @@
#include "BC3Encoder.h"
#include "../../BlockView.h"
#include "../../Block.h"
#include "BC3Block.h"
namespace quicktex::s3tc {
void BC3Encoder::EncodeBlock(Color4x4 pixels, BC3Block *dest) const {
_bc1_encoder->EncodeBlock(pixels, &(dest->color_block));
_bc4_encoder->EncodeBlock(pixels, &(dest->alpha_block));
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

View File

@ -21,8 +21,8 @@
#include <memory>
#include "../../BlockEncoder.h"
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../Encoder.h"
#include "../bc1/BC1Encoder.h"
#include "../bc4/BC4Encoder.h"
#include "../interpolator/Interpolator.h"
@ -30,7 +30,7 @@
namespace quicktex::s3tc {
class BC3Encoder : public BlockEncoderTemplate<BC3Block, 4, 4> {
class BC3Encoder : public BlockEncoder<BlockTexture<BC3Block>> {
public:
using BC1EncoderPtr = std::shared_ptr<BC1Encoder>;
using BC4EncoderPtr = std::shared_ptr<BC4Encoder>;
@ -41,7 +41,7 @@ class BC3Encoder : public BlockEncoderTemplate<BC3Block, 4, 4> {
BC3Encoder(unsigned level = 5) : BC3Encoder(level, std::make_shared<Interpolator>()) {}
void EncodeBlock(Color4x4 pixels, BC3Block *dest) const override;
BC3Block EncodeBlock(const ColorBlock<4, 4>& pixels) const override;
BC1EncoderPtr GetBC1Encoder() const { return _bc1_encoder; }
BC4EncoderPtr GetBC4Encoder() const { return _bc4_encoder; }

View File

@ -25,8 +25,8 @@
#include <stdexcept>
#include <string>
#include "../../BlockDecoder.h"
#include "../../BlockEncoder.h"
#include "../../Decoder.h"
#include "../../Encoder.h"
#include "../interpolator/Interpolator.h"
#include "BC3Decoder.h"
#include "BC3Encoder.h"
@ -42,13 +42,11 @@ using BC1DecoderPtr = std::shared_ptr<BC1Decoder>;
void InitBC3(py::module_ &s3tc) {
auto bc3 = s3tc.def_submodule("_bc3", "internal bc3 module");
auto block_encoder = py::type::of<BlockEncoder>();
auto block_decoder = py::type::of<BlockDecoder>();
py::options options;
options.disable_function_signatures();
// BC3Encoder
py::class_<BC3Encoder> bc3_encoder(bc3, "BC3Encoder", block_encoder,R"doc(
py::class_<BC3Encoder> bc3_encoder(bc3, "BC3Encoder", R"doc(
Base: :py:class:`~quicktex.BlockEncoder`
Encodes RGBA textures to BC3
@ -71,7 +69,7 @@ void InitBC3(py::module_ &s3tc) {
"Internal :py:class:`~quicktex.s3tc.bc4.BC4Encoder` used for alpha data. Readonly.");
// BC3Decoder
py::class_<BC3Decoder> bc3_decoder(bc3, "BC3Decoder", block_decoder, R"doc(
py::class_<BC3Decoder> bc3_decoder(bc3, "BC3Decoder", R"doc(
Base: :py:class:`~quicktex.BlockDecoder`
Decodes BC3 textures to RGBA

View File

@ -28,10 +28,10 @@
#include "../../util.h"
#include "../bc1/BC1Block.h"
namespace quicktex::s3tc {
namespace quicktex::s3tc {
#pragma pack(push, 1)
class BC4Block {
class BC4Block : public Block<4, 4> {
public:
using UnpackedSelectors = std::array<std::array<uint8_t, 4>, 4>;

View File

@ -22,24 +22,32 @@
#include <array> // for array
#include <cassert> // for assert
#include "../../BlockView.h" // for ColorBlock
#include "../../ndebug.h" // for ndebug
#include "BC4Block.h"
namespace quicktex::s3tc {
void BC4Decoder::DecodeBlock(Byte4x4 dest, BC4Block *const block) const noexcept(ndebug) {
auto l = block->GetLowAlpha();
auto h = block->GetHighAlpha();
namespace quicktex::s3tc {
void BC4Decoder::DecodeInto(ColorBlock<4, 4> &dest, const BC4Block &block) const {
auto l = block.GetLowAlpha();
auto h = block.GetHighAlpha();
auto values = BC4Block::GetValues(l, h);
auto selectors = block->UnpackSelectors();
auto selectors = block.UnpackSelectors();
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

View File

@ -23,22 +23,23 @@
#include <cstdint>
#include <stdexcept>
#include "../../BlockDecoder.h"
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../Decoder.h"
#include "../../ndebug.h"
#include "BC4Block.h"
namespace quicktex::s3tc {
class BC4Decoder : public BlockDecoderTemplate<BC4Block, 4, 4> {
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;
}
void DecodeBlock(Color4x4 dest, BC4Block *const block) const noexcept(ndebug) override { DecodeBlock(dest.GetChannel(_channel), block); }
void DecodeBlock(Byte4x4 dest, BC4Block *const block) const noexcept(ndebug);
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; }

View File

@ -22,26 +22,29 @@
#include <algorithm>
#include <array>
#include <cstdint>
#include <utility>
#include "../../BlockView.h"
#include "../../ndebug.h"
#include "../../Block.h"
#include "BC4Block.h"
namespace quicktex::s3tc {
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 {
auto output = BC4Block();
uint8_t min = *minmax.first;
uint8_t max = *minmax.second;
uint8_t min = UINT8_MAX;
uint8_t max = 0;
dest->high_alpha = min;
dest->low_alpha = max;
for (int i = 0; i < 16; i++) {
auto value = pixels.Get(i)[_channel];
min = std::min(min, value);
max = std::max(max, value);
}
output.high_alpha = min;
output.low_alpha = max;
if (max == min) {
dest->SetSelectorBits(0);
return;
output.SetSelectorBits(0);
return output;
}
std::array<uint8_t, 16> selectors = {};
@ -58,16 +61,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 i = 0; i < 16; i++) {
int value = (int)pixels.Get(i)[_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];
selectors[i] = Levels[level];
selectors[(unsigned)i] = Levels[level];
}
dest->PackSelectors(selectors);
output.PackSelectors(selectors);
return output;
}
} // namespace quicktex::s3tc

View File

@ -23,22 +23,21 @@
#include <cstdint>
#include <stdexcept>
#include "../../BlockEncoder.h"
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../Encoder.h"
#include "../../ndebug.h"
#include "BC4Block.h"
namespace quicktex::s3tc {
class BC4Encoder : public BlockEncoderTemplate<BC4Block, 4, 4> {
class BC4Encoder : public BlockEncoder<BlockTexture<BC4Block>> {
public:
BC4Encoder(const uint8_t channel) {
if (channel >= 4) throw std::invalid_argument("Channel out of range");
_channel = channel;
}
void EncodeBlock(Color4x4 pixels, BC4Block *const dest) const override { 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; }

View File

@ -25,8 +25,8 @@
#include <stdexcept>
#include <string>
#include "../../BlockDecoder.h"
#include "../../BlockEncoder.h"
#include "../../Decoder.h"
#include "../../Encoder.h"
#include "BC4Decoder.h"
#include "BC4Encoder.h"
@ -38,13 +38,11 @@ using namespace quicktex::s3tc;
void InitBC4(py::module_ &s3tc) {
auto bc4 = s3tc.def_submodule("_bc4", "internal bc4 module");
auto block_encoder = py::type::of<BlockEncoder>();
auto block_decoder = py::type::of<BlockDecoder>();
py::options options;
options.disable_function_signatures();
// BC4Encoder
py::class_<BC4Encoder> bc4_encoder(bc4, "BC4Encoder", block_encoder, R"doc(
py::class_<BC4Encoder> bc4_encoder(bc4, "BC4Encoder", R"doc(
Base: :py:class:`~quicktex.BlockEncoder`
Encodes single-channel textures to BC4.
@ -60,7 +58,7 @@ void InitBC4(py::module_ &s3tc) {
bc4_encoder.def_property_readonly("channel", &BC4Encoder::GetChannel, "The channel that will be read from. 0 to 3 inclusive. Readonly.");
// BC4Decoder
py::class_<BC4Decoder> bc4_decoder(bc4, "BC4Decoder", block_decoder, R"doc(
py::class_<BC4Decoder> bc4_decoder(bc4, "BC4Decoder", R"doc(
Base: :py:class:`~quicktex.BlockDecoder`
Decodes BC4 textures to a single-channel.

View File

@ -19,12 +19,13 @@
#pragma once
#include "../../Block.h"
#include "../bc4/BC4Block.h"
namespace quicktex::s3tc {
namespace quicktex::s3tc {
#pragma pack(push, 1)
class BC5Block {
class BC5Block : public Block<4, 4> {
public:
BC4Block chan0_block;
BC4Block chan1_block;

View File

@ -19,14 +19,15 @@
#include "BC5Decoder.h"
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../ndebug.h"
#include "BC5Block.h"
namespace quicktex::s3tc {
void BC5Decoder::DecodeBlock(Color4x4 dest, BC5Block *const block) const noexcept(ndebug) {
_chan0_decoder->DecodeBlock(dest, &block->chan0_block);
_chan1_decoder->DecodeBlock(dest, &block->chan1_block);
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 quicktex::s3tc

View File

@ -24,15 +24,16 @@
#include <tuple>
#include <type_traits>
#include "../../BlockDecoder.h"
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../Texture.h"
#include "../../Decoder.h"
#include "../../ndebug.h"
#include "../bc4/BC4Decoder.h"
#include "BC5Block.h"
namespace quicktex::s3tc {
class BC5Decoder : public BlockDecoderTemplate<BC5Block, 4, 4> {
class BC5Decoder : public BlockDecoder<BlockTexture<BC5Block>> {
public:
using ChannelPair = std::tuple<uint8_t, uint8_t>;
using BC4DecoderPtr = std::shared_ptr<BC4Decoder>;
@ -41,7 +42,7 @@ class BC5Decoder : public BlockDecoderTemplate<BC5Block, 4, 4> {
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) {}
void DecodeBlock(Color4x4 dest, BC5Block *const block) const noexcept(ndebug) override;
ColorBlock<4,4> DecodeBlock(const BC5Block &block) const override;
ChannelPair GetChannels() const { return ChannelPair(_chan0_decoder->GetChannel(), _chan1_decoder->GetChannel()); }

View File

@ -19,9 +19,11 @@
#include "BC5Encoder.h"
namespace quicktex::s3tc {
void BC5Encoder::EncodeBlock(Color4x4 pixels, BC5Block *dest) const {
_chan0_encoder->EncodeBlock(pixels, &(dest->chan0_block));
_chan1_encoder->EncodeBlock(pixels, &(dest->chan1_block));
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

View File

@ -24,15 +24,15 @@
#include <tuple>
#include <type_traits>
#include "../../BlockEncoder.h"
#include "../../BlockView.h"
#include "../../Block.h"
#include "../../Encoder.h"
#include "../../ndebug.h"
#include "../bc4/BC4Encoder.h"
#include "BC5Block.h"
namespace quicktex::s3tc {
class BC5Encoder : public BlockEncoderTemplate<BC5Block, 4, 4> {
class BC5Encoder : public BlockEncoder<BlockTexture<BC5Block>> {
public:
using ChannelPair = std::tuple<uint8_t, uint8_t>;
using BC4EncoderPtr = std::shared_ptr<BC4Encoder>;
@ -41,7 +41,7 @@ class BC5Encoder : public BlockEncoderTemplate<BC5Block, 4, 4> {
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) {}
void EncodeBlock(Color4x4 pixels, BC5Block *dest) const override;
BC5Block EncodeBlock(const ColorBlock<4, 4> &pixels) const override;
ChannelPair GetChannels() const { return ChannelPair(_chan0_encoder->GetChannel(), _chan1_encoder->GetChannel()); }

View File

@ -22,8 +22,8 @@
#include <array>
#include <cstdint>
#include "../../BlockDecoder.h"
#include "../../BlockEncoder.h"
#include "../../Decoder.h"
#include "../../Encoder.h"
#include "BC5Decoder.h"
#include "BC5Encoder.h"
@ -35,13 +35,11 @@ using namespace quicktex::s3tc;
void InitBC5(py::module_ &s3tc) {
auto bc5 = s3tc.def_submodule("_bc5", "internal bc5 module");
auto block_encoder = py::type::of<BlockEncoder>();
auto block_decoder = py::type::of<BlockDecoder>();
py::options options;
options.disable_function_signatures();
// BC5Encoder
py::class_<BC5Encoder> bc5_encoder(bc5, "BC5Encoder", block_encoder, R"doc(
py::class_<BC5Encoder> bc5_encoder(bc5, "BC5Encoder", R"doc(
Base: :py:class:`~quicktex.BlockEncoder`
Encodes dual-channel textures to BC5.
@ -61,7 +59,7 @@ void InitBC5(py::module_ &s3tc) {
"2-tuple of internal :py:class:`~quicktex.s3tc.bc4.BC4Encoder` s used for each channel. Readonly.");
// BC5Decoder
py::class_<BC5Decoder> bc5_decoder(bc5, "BC5Decoder", block_decoder, R"doc(
py::class_<BC5Decoder> bc5_decoder(bc5, "BC5Decoder", R"doc(
Base: :py:class:`~quicktex.BlockDecoder`
Decodes BC4 textures to two channels.