Bind BC1Decoder

hotfix/mipmap-alpha-fix
Andrew Cassidy 3 years ago
parent 52d1185dac
commit 04d11112d4

@ -4,8 +4,9 @@ include(tools/CompilerWarnings.cmake)
project(rgbcx)
# Link to Pybind
# Find dependencies
find_package(Python COMPONENTS Interpreter Development REQUIRED)
find_package(OpenMP)
add_subdirectory(extern/pybind11)
# Collect source files
@ -33,6 +34,11 @@ target_link_libraries(test_rgbcx PRIVATE pybind11::embed)
target_compile_definitions(test_rgbcx PRIVATE -DCUSTOM_SYS_PATH="${CMAKE_HOME_DIRECTORY}/env/lib/python3.8/site-packages")
message("\"${CMAKE_HOME_DIRECTORY}/env/lib/python3.8/site-packages\"")
# enable openMP if available
if(OpenMP_CXX_FOUND)
target_link_libraries(_rgbcx PUBLIC OpenMP::OpenMP_CXX)
endif()
# Set module features, like C/C++ standards
target_compile_features(_rgbcx PUBLIC cxx_std_20 c_std_11)
target_compile_features(test_rgbcx PUBLIC cxx_std_20 c_std_11)

@ -41,7 +41,7 @@ void BC1Decoder::DecodeBlock(Color4x4 dest, BC1Block *const block) const noexcep
const auto color = colors[selector];
assert(selector < 4);
assert((color.a == 0 && selector == 3 && l <= h) || color.a == UINT8_MAX);
if (_write_alpha) {
if (write_alpha) {
dest.Get(x, y).SetRGBA(color);
} else {
dest.Get(x, y).SetRGB(color);

@ -28,19 +28,19 @@
#include "BC1Block.h"
namespace rgbcx {
class BC1Decoder final : public BlockDecoder<BC1Block, 4, 4> {
class BC1Decoder final : public BlockDecoderTemplate<BC1Block, 4, 4> {
public:
using InterpolatorPtr = std::shared_ptr<Interpolator>;
BC1Decoder(const InterpolatorPtr interpolator = std::make_shared<Interpolator>(), bool write_alpha = false)
: _interpolator(interpolator), _write_alpha(write_alpha) {}
BC1Decoder(Interpolator::Type type = Interpolator::Type::Ideal, bool write_alpha = false)
: _interpolator(Interpolator::MakeInterpolator(type)), write_alpha(write_alpha) {}
void DecodeBlock(Color4x4 dest, BC1Block *const block) const noexcept(ndebug) override;
InterpolatorPtr GetInterpolator() const { return _interpolator; }
constexpr bool WritesAlpha() const { return _write_alpha; }
Interpolator::Type GetInterpolatorType() const { return _interpolator->GetType(); }
constexpr bool WritesAlpha() const { return write_alpha; }
bool write_alpha;
private:
const InterpolatorPtr _interpolator;
const bool _write_alpha;
};
} // namespace rgbcx

@ -43,40 +43,18 @@ using namespace BC1;
// constructors
BC1Encoder::BC1Encoder(InterpolatorPtr interpolator) : _interpolator(interpolator) {
_flags = Flags::None;
_error_mode = ErrorMode::Check2;
_endpoint_mode = EndpointMode::PCA;
_search_rounds = 0;
_orderings3 = 1;
_orderings4 = 1;
BC1Encoder::BC1Encoder(Interpolator::Type type, unsigned int level, bool allow_3color, bool allow_3color_black)
: _interpolator(Interpolator::MakeInterpolator(type)) {
OrderTable<3>::Generate();
OrderTable<4>::Generate();
assert(OrderTable<3>::generated);
assert(OrderTable<4>::generated);
}
BC1Encoder::BC1Encoder(unsigned int level, bool allow_3color, bool allow_3color_black) : BC1Encoder(Interpolator::MakeInterpolator()) {
SetLevel(level, allow_3color, allow_3color_black);
}
BC1Encoder::BC1Encoder(InterpolatorPtr interpolator, unsigned level, bool allow_3color, bool allow_3color_black) : BC1Encoder(interpolator) {
SetLevel(level, allow_3color, allow_3color_black);
}
BC1Encoder::BC1Encoder(InterpolatorPtr interpolator, Flags flags, ErrorMode error_mode, EndpointMode endpoint_mode, unsigned search_rounds, unsigned orderings4,
unsigned orderings3)
: BC1Encoder(interpolator) {
SetFlags(flags);
SetErrorMode(error_mode);
SetEndpointMode(endpoint_mode);
SetSearchRounds(search_rounds);
SetOrderings4(orderings4);
SetOrderings3(orderings3);
}
// Getters and Setters
void BC1Encoder::SetLevel(unsigned level, bool allow_3color, bool allow_3color_black) {
_flags = Flags::None;

@ -106,16 +106,9 @@ class BC1Encoder final : public BlockEncoderTemplate<BC1Block, 4, 4> {
PCA
};
BC1Encoder(InterpolatorPtr interpolator);
BC1Encoder(Interpolator::Type type = Interpolator::Type::Ideal, unsigned level = 5, bool allow_3color = true, bool allow_3color_black = true);
BC1Encoder(unsigned level = 5, bool allow_3color = true, bool allow_3color_black = true);
BC1Encoder(InterpolatorPtr interpolator, unsigned level, bool allow_3color = true, bool allow_3color_black = true);
BC1Encoder(InterpolatorPtr interpolator, Flags flags, ErrorMode error_mode = ErrorMode::Full, EndpointMode endpoint_mode = EndpointMode::PCA,
unsigned search_rounds = 16, unsigned orderings4 = 32, unsigned orderings3 = 32);
const InterpolatorPtr &GetInterpolator() const;
Interpolator::Type GetInterpolatorType() const { return _interpolator->GetType(); }
void SetLevel(unsigned level, bool allow_3color = true, bool allow_3color_black = true);
@ -138,6 +131,8 @@ class BC1Encoder final : public BlockEncoderTemplate<BC1Block, 4, 4> {
void EncodeBlock(Color4x4 pixels, BC1Block *dest) const override;
virtual size_t MTThreshold() const override { return 16; }
private:
using Hash = uint16_t;
using BlockMetrics = Color4x4::BlockMetrics;

@ -30,13 +30,13 @@
#include "BC3Block.h"
namespace rgbcx {
class BC3Decoder : public BlockDecoder<BC3Block, 4, 4> {
class BC3Decoder : public BlockDecoderTemplate<BC3Block, 4, 4> {
public:
using InterpolatorPtr = std::shared_ptr<Interpolator>;
using BC1DecoderPtr = std::shared_ptr<BC1Decoder>;
using BC4DecoderPtr = std::shared_ptr<BC4Decoder>;
BC3Decoder(InterpolatorPtr interpolator = std::make_shared<Interpolator>()) : BC3Decoder(std::make_shared<BC1Decoder>(interpolator)) {}
BC3Decoder(Interpolator::Type type = Interpolator::Type::Ideal) : BC3Decoder(std::make_shared<BC1Decoder>(type)) {}
BC3Decoder(BC1DecoderPtr bc1_decoder, BC4DecoderPtr bc4_decoder = std::make_shared<BC4Decoder>()) : _bc1_decoder(bc1_decoder), _bc4_decoder(bc4_decoder) {}
void DecodeBlock(Color4x4 dest, BC3Block *const block) const noexcept(ndebug) override;

@ -28,7 +28,7 @@
#include "BC4Block.h"
namespace rgbcx {
class BC4Decoder : public BlockDecoder<BC4Block, 4, 4> {
class BC4Decoder : public BlockDecoderTemplate<BC4Block, 4, 4> {
public:
BC4Decoder(uint8_t channel = 3) : _channel(channel) { assert(channel < 4U); }

@ -31,7 +31,7 @@
#include "BC5Block.h"
namespace rgbcx {
class BC5Decoder : public BlockDecoder<BC5Block, 4, 4> {
class BC5Decoder : public BlockDecoderTemplate<BC5Block, 4, 4> {
public:
using BC4DecoderPtr = std::shared_ptr<BC4Decoder>;

@ -30,17 +30,29 @@
namespace rgbcx {
template <class B, size_t M, size_t N> class BlockDecoder {
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;
BlockDecoder() noexcept = default;
virtual ~BlockDecoder() noexcept = default;
BlockDecoderTemplate() noexcept = default;
virtual ~BlockDecoderTemplate() noexcept = default;
virtual void DecodeBlock(DecodedBlock dest, EncodedBlock *const block) const noexcept(ndebug) = 0;
void DecodeImage(uint8_t *encoded, Color *decoded, unsigned image_width, unsigned image_height) {
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);
@ -69,5 +81,9 @@ template <class B, size_t M, size_t N> class BlockDecoder {
}
}
}
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 rgbcx

@ -1,25 +0,0 @@
/* Python-rgbcx Texture Compression Library
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
and licenced under the public domain
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BlockEncoder.h"
#include "BC1/BC1Encoder.h"
namespace rgbcx {
std::shared_ptr<BlockEncoder> rgbcx::BlockEncoder::MakeEncoder(std::string fourcc) { return std::make_shared<BC1Encoder>(); }
} // namespace rgbcx

@ -37,8 +37,6 @@ class BlockEncoder {
virtual size_t BlockSize() const = 0;
virtual size_t BlockWidth() const = 0;
virtual size_t BlockHeight() const = 0;
static EncoderPtr MakeEncoder(std::string fourcc);
};
template <class B, size_t M, size_t N> class BlockEncoderTemplate : public BlockEncoder {
@ -57,12 +55,16 @@ template <class B, size_t M, size_t N> class BlockEncoderTemplate : public Block
unsigned block_width = image_width / N;
unsigned block_height = image_height / M;
unsigned block_count = block_width * block_height;
auto blocks = reinterpret_cast<B *>(encoded);
// from experimentation, multithreading this using OpenMP actually makes decoding slower
// 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 left as a serial operation despite being embarassingly parallelizable
// 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_count >= MTThreshold())
for (unsigned y = 0; y < block_height; y++) {
for (unsigned x = 0; x < block_width; x++) {
unsigned pixel_x = x * N;
@ -84,5 +86,7 @@ template <class B, size_t M, size_t N> class BlockEncoderTemplate : public Block
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 rgbcx

@ -19,7 +19,9 @@
#include <pybind11/pybind11.h>
#include "../BC1/BC1Decoder.h"
#include "../BC1/BC1Encoder.h"
#include "../BlockDecoder.h"
#include "../BlockEncoder.h"
#include "../bitwiseEnums.h"
@ -29,18 +31,17 @@
namespace py = pybind11;
namespace rgbcx::bindings {
std::unique_ptr<BC1Encoder> MakeBC1Encoder(Interpolator::Type interpolator, unsigned level, bool use_3color, bool use_3color_black) {
auto interpolator_ptr = (std::shared_ptr<Interpolator>)Interpolator::MakeInterpolator(interpolator);
return std::make_unique<BC1Encoder>(interpolator_ptr, level, use_3color, use_3color_black);
}
void InitBC1(py::module_ &m) {
auto block_encoder = py::type::of<BlockEncoder>();
auto block_decoder = py::type::of<BlockDecoder>();
// BC1Encoder
py::class_<BC1Encoder> bc1_encoder(m, "BC1Encoder", block_encoder);
bc1_encoder.def(py::init(&MakeBC1Encoder), py::arg("interpolator") = Interpolator::Type::Ideal, py::arg("level") = 5, py::arg("use_3color") = true,
py::arg("use_3color_black") = true);
bc1_encoder.def(py::init<Interpolator::Type, unsigned, bool, bool>(), py::arg("interpolator") = Interpolator::Type::Ideal, py::arg("level") = 5,
py::arg("use_3color") = true, py::arg("use_3color_black") = true);
bc1_encoder.def("set_level", &BC1Encoder::SetLevel);
bc1_encoder.def_property_readonly("interpolator_type", &BC1Encoder::GetInterpolatorType);
bc1_encoder.def_property("flags", &BC1Encoder::GetFlags, &BC1Encoder::SetFlags);
bc1_encoder.def_property("error_mode", &BC1Encoder::GetErrorMode, &BC1Encoder::SetErrorMode);
bc1_encoder.def_property("endpoint_mode", &BC1Encoder::GetEndpointMode, &BC1Encoder::SetEndpointMode);
@ -77,6 +78,13 @@ void InitBC1(py::module_ &m) {
.value("Faster", BC1Encoder::ErrorMode::Faster)
.value("Check2", BC1Encoder::ErrorMode::Check2)
.value("Full", BC1Encoder::ErrorMode::Full);
// BC1Decoder
py::class_<BC1Decoder> bc1_decoder(m, "BC1Decoder", block_decoder);
bc1_decoder.def(py::init<Interpolator::Type, bool>(), py::arg("interpolator") = Interpolator::Type::Ideal, py::arg("write_alpha") = false);
bc1_decoder.def_property_readonly("interpolator_type", &BC1Decoder::GetInterpolatorType);
bc1_decoder.def_readwrite("write_alpha", &BC1Decoder::write_alpha);
}
} // namespace rgbcx::bindings

@ -0,0 +1,64 @@
/* 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 "../BlockDecoder.h"
#include <pybind11/pybind11.h>
#include <stdexcept>
#include "../bitwiseEnums.h"
#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)
namespace py = pybind11;
namespace rgbcx::bindings {
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 == 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 color_size = size * sizeof(Color);
std::string encoded_str = (std::string)encoded; // encoded data is copied here, unfortunately
std::string decoded_str = std::string(color_size, 0);
if (decoded_str.size() != color_size) throw std::invalid_argument("Incompatible data: image width and height do not match the size of the decoded image");
self.DecodeImage(reinterpret_cast<uint8_t *>(encoded_str.data()), reinterpret_cast<Color *>(decoded_str.data()), image_width, image_height);
auto bytes = py::bytes(decoded_str); // decoded data is copied here, unfortunately
return bytes;
}
void InitBlockDecoder(py::module_ &m) {
py::class_<BlockDecoder> block_decoder(m, "BlockDecoder");
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);
}
} // namespace rgbcx::bindings

@ -19,16 +19,13 @@
#include <pybind11/pybind11.h>
#include "../BlockEncoder.h"
#include "../Interpolator.h"
#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)
namespace py = pybind11;
namespace rgbcx::bindings {
void InitBlockEncoder(py::module_ &m);
void InitBlockDecoder(py::module_ &m);
void InitBC1(py::module_ &m);
PYBIND11_MODULE(_rgbcx, m) {
@ -42,7 +39,8 @@ PYBIND11_MODULE(_rgbcx, m) {
.value("AMD", IType::AMD);
InitBlockEncoder(m);
InitBlockDecoder(m);
InitBC1(m);
}
} // namespace python_rgbcx::py
} // namespace rgbcx::bindings

@ -0,0 +1,30 @@
from s3tc import BC1Block
from color import Color
from PIL import Image
import os
image_path = os.path.dirname(os.path.realpath(__file__)) + "/images"
# A block that should always encode greyscale, where every row of pixels is identical, and the left side is lighter than the right side
greyscale = Image.open(image_path + "/blocks/greyscale.png").tobytes("raw", "RGBX")
# A block that should always encode 3-color when available.
# from left to right: red, yellow, yellow, green
three_color = Image.open(image_path + "/blocks/3color.png").tobytes("raw", "RGBX")
# A block that should always encode 3-color with black when available
# from left to right: black, red, yellow, green
three_color_black = Image.open(image_path + "/blocks/3color black.png").tobytes("raw", "RGBX")
bc1_test_blocks = [
# A block that should always encode greyscale, where every row of pixels is identical, and the left side is lighter than the right side
{"name": "greyscale",
"image": Image.open(image_path + "/blocks/greyscale.png").tobytes("raw", "RGBX"),
"expected": BC1Block(Color(0xFF, 0xFF, 0xFF), Color(0x44, 0x44, 0x44), [[0, 2, 3, 1]] * 4)},
# A block that should always encode 3-color when available.
# from left to right: red, yellow, yellow, green
{"name": "3color",
"image": Image.open(image_path + "/blocks/3color.png").tobytes("raw", "RGBX"),
"expected": BC1Block(Color(0x00, 0xFF, 0x00), Color(0xFF, 0x00, 0x00), [[0, 2, 3, 1]] * 4)}
]

@ -1,5 +1,5 @@
class Color:
def __init__(self, r=0, g=0, b=0, a=1):
def __init__(self, r=0, g=0, b=0, a=0xFF):
self.r = r
self.g = g
self.b = b
@ -23,6 +23,10 @@ class Color:
def __str__(self):
return self.to_hex()
def error(self, other):
assert isinstance(other, Color)
return ((self.r - other.r) ** 2) + ((self.g - other.g) ** 2) + ((self.b - other.b) ** 2)
@classmethod
def from_565(cls, int_value):
r = float((int_value & 0xF800) >> 11) / 0x1F

@ -28,16 +28,16 @@ def triple_merge(rows):
class BC1Block:
size = 8
def __init__(self):
self.color0 = Color()
self.color1 = Color()
self.selectors = [[0] * 4] * 4
def __init__(self, color0=Color(), color1=Color(), selectors=[[0] * 4] * 4):
self.color0 = color0
self.color1 = color1
self.selectors = selectors
def __repr__(self):
return repr(self.__dict__)
def __str__(self):
return f'color0: {str(self.color0)} color1: {str(self.color1)}, indices:{self.selectors}'
return f'color0: {str(self.color0)} color1: {str(self.color1)}, selectors:{self.selectors}'
@staticmethod
def frombytes(data):

@ -1,13 +1,12 @@
import rgbcx
import unittest
import nose
import parameterized
import s3tc
import rgbcx
from parameterized import parameterized_class
from s3tc import BC1Block
from images import Blocks
def get_class_name_bc1encoder(cls, num, params_dict):
def get_class_name(cls, num, params_dict):
# By default the generated class named includes either the "name"
# parameter (if present), or the first string value. This example shows
# multiple parameters being included in the generated class name:
@ -17,11 +16,11 @@ def get_class_name_bc1encoder(cls, num, params_dict):
)
@parameterized.parameterized_class([
@parameterized_class([
{"use_3color": False, "use_3color_black": False, "suffix": "4Color"},
{"use_3color": True, "use_3color_black": False, "suffix": "3Color"},
{"use_3color": True, "use_3color_black": True, "suffix": "3ColorBlack"},
], class_name_func=get_class_name_bc1encoder)
], class_name_func=get_class_name)
class TestBC1EncoderBlocks(unittest.TestCase):
"""Test BC1 encoder with a variety of inputs with 3 color blocks disabled."""
@ -39,7 +38,7 @@ class TestBC1EncoderBlocks(unittest.TestCase):
def test_block_4color(self):
"""Test encoder output with 4 color greyscale testblock."""
out = s3tc.BC1Block.frombytes(self.bc1_encoder.encode_image(Blocks.greyscale, 4, 4))
out = BC1Block.frombytes(self.bc1_encoder.encode_image(Blocks.greyscale, 4, 4))
selectors = [[0, 2, 3, 1]] * 4
self.assertFalse(out.is_3color(), "returned block color mode for greyscale test block")
@ -47,7 +46,7 @@ class TestBC1EncoderBlocks(unittest.TestCase):
def test_block_3color(self):
"""Test encoder output with 3 color test block."""
out = s3tc.BC1Block.frombytes(self.bc1_encoder.encode_image(Blocks.three_color, 4, 4))
out = BC1Block.frombytes(self.bc1_encoder.encode_image(Blocks.three_color, 4, 4))
selectors = [[1, 2, 2, 0]] * 4
self.assertEqual(out.is_3color(), self.use_3color, "returned incorrect block color mode for 3 color test block")
@ -56,7 +55,7 @@ class TestBC1EncoderBlocks(unittest.TestCase):
def test_block_3color_black(self):
"""Test encoder output with 3 color test block with black pixels."""
out = s3tc.BC1Block.frombytes(self.bc1_encoder.encode_image(Blocks.three_color_black, 4, 4))
out = BC1Block.frombytes(self.bc1_encoder.encode_image(Blocks.three_color_black, 4, 4))
selectors = [[3, 1, 2, 0]] * 4
self.assertEqual(out.is_3color_black(), self.use_3color_black, "returned incorrect block color mode for 3 color with black test block")

Loading…
Cancel
Save