mirror of
https://github.com/drewcassidy/quicktex.git
synced 2024-09-13 06:37:34 +00:00
Refactor to add Texture classes
This commit is contained in:
parent
c843871ac1
commit
fb56c6af04
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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) {
|
||||
|
@ -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
68
quicktex/Decoder.h
Normal 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
72
quicktex/Encoder.h
Normal 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
61
quicktex/Texture.cpp
Normal 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
219
quicktex/Texture.h
Normal 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
|
@ -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
54
quicktex/_bindings.h
Normal 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
|
@ -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)); }
|
||||
|
@ -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
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
@ -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; }
|
||||
|
@ -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
|
@ -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; }
|
||||
|
@ -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
|
||||
|
@ -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>;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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
|
@ -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; }
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
|
@ -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()); }
|
||||
|
||||
|
@ -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
|
@ -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()); }
|
||||
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user