More unit tests and bindings

hotfix/mipmap-alpha-fix
Andrew Cassidy 3 years ago
parent e58871167e
commit b63c26a45a

@ -29,11 +29,24 @@
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>();
py::class_<BC1Encoder> bc1_encoder(m, "BC1Encoder", block_encoder);
bc1_encoder.def(py::init<>());
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("set_level", &BC1Encoder::SetLevel);
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);
bc1_encoder.def_property("search_rounds", &BC1Encoder::GetSearchRounds, &BC1Encoder::SetSearchRounds);
bc1_encoder.def_property("orderings_4", &BC1Encoder::GetOrderings4, &BC1Encoder::SetOrderings4);
bc1_encoder.def_property("orderings_3", &BC1Encoder::GetOrderings3, &BC1Encoder::SetOrderings3);
using Flags = BC1Encoder::Flags;
py::enum_<Flags>(bc1_encoder, "Flags", py::arithmetic())

@ -19,6 +19,7 @@
#include <pybind11/pybind11.h>
#include "../src/BlockEncoder.h"
#include "../src/Interpolator.h"
#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)
@ -30,7 +31,16 @@ void InitBlockEncoder(py::module_ &m);
void InitBC1(py::module_ &m);
PYBIND11_MODULE(python_rgbcx, m) {
m.doc() = "More Stuff";
using IType = Interpolator::Type;
py::enum_<IType>(m, "InterpolatorType")
.value("Ideal", IType::Ideal)
.value("IdealRound", IType::IdealRound)
.value("Nvidia", IType::Nvidia)
.value("AMD", IType::AMD);
InitBlockEncoder(m);
InitBC1(m);
}

@ -41,8 +41,6 @@
namespace rgbcx {
using namespace BC1;
using ColorMode = BC1Encoder::ColorMode;
// constructors
BC1Encoder::BC1Encoder(InterpolatorPtr interpolator) : _interpolator(interpolator) {
@ -75,7 +73,8 @@ BC1Encoder::BC1Encoder(InterpolatorPtr interpolator, Flags flags, ErrorMode erro
SetErrorMode(error_mode);
SetEndpointMode(endpoint_mode);
SetSearchRounds(search_rounds);
SetOrderings(orderings4, orderings3);
SetOrderings4(orderings4);
SetOrderings3(orderings3);
}
// Getters and Setters
@ -213,10 +212,8 @@ void BC1Encoder::SetLevel(unsigned level, bool allow_3color, bool allow_3color_b
_orderings3 = clamp(_orderings3, 1U, OrderTable<3>::BestOrderCount);
}
void BC1Encoder::SetOrderings(unsigned orderings4, unsigned orderings3) {
_orderings4 = clamp(orderings4, 1U, OrderTable<4>::BestOrderCount);
_orderings3 = clamp(orderings3, 1U, OrderTable<3>::BestOrderCount);
}
void BC1Encoder::SetOrderings4(unsigned orderings4) { _orderings4 = clamp(orderings4, 1U, OrderTable<4>::BestOrderCount); }
void BC1Encoder::SetOrderings3(unsigned orderings3) { _orderings3 = clamp(orderings3, 1U, OrderTable<3>::BestOrderCount); }
// Public methods
void BC1Encoder::EncodeBlock(Color4x4 pixels, BC1Block *dest) const {
@ -643,7 +640,7 @@ void BC1Encoder::FindEndpoints(Color4x4 pixels, EncodeResults &block, const Bloc
block.color_mode = ColorMode::Incomplete;
}
template <ColorMode M> void BC1Encoder::FindSelectors(Color4x4 &pixels, EncodeResults &block, ErrorMode error_mode) const {
template <BC1Encoder::ColorMode M> void BC1Encoder::FindSelectors(Color4x4 &pixels, EncodeResults &block, ErrorMode error_mode) const {
assert(!((error_mode != ErrorMode::Full) && (bool)(M & ColorMode::ThreeColor)));
assert(!(bool)(M & ColorMode::Solid));
@ -746,7 +743,7 @@ template <ColorMode M> void BC1Encoder::FindSelectors(Color4x4 &pixels, EncodeRe
block.color_mode = M;
}
template <ColorMode M> bool BC1Encoder::RefineEndpointsLS(Color4x4 pixels, EncodeResults &block, BlockMetrics metrics) const {
template <BC1Encoder::ColorMode M> bool BC1Encoder::RefineEndpointsLS(Color4x4 pixels, EncodeResults &block, BlockMetrics metrics) const {
const int color_count = (unsigned)M & 0x0F;
static_assert(color_count == 3 || color_count == 4);
static_assert(!(bool)(M & ColorMode::Solid));
@ -793,7 +790,7 @@ template <ColorMode M> bool BC1Encoder::RefineEndpointsLS(Color4x4 pixels, Encod
return true;
}
template <ColorMode M> void BC1Encoder::RefineEndpointsLS(std::array<Vector4, 17> &sums, EncodeResults &block, Vector4 &matrix, Hash hash) const {
template <BC1Encoder::ColorMode M> void BC1Encoder::RefineEndpointsLS(std::array<Vector4, 17> &sums, EncodeResults &block, Vector4 &matrix, Hash hash) const {
const int color_count = (unsigned)M & 0x0F;
static_assert(color_count == 3 || color_count == 4);
static_assert(!(bool)(M & ColorMode::Solid));
@ -819,7 +816,7 @@ template <ColorMode M> void BC1Encoder::RefineEndpointsLS(std::array<Vector4, 17
block.high = Color::PreciseRound565(high);
}
template <ColorMode M>
template <BC1Encoder::ColorMode M>
void BC1Encoder::RefineBlockLS(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned passes) const {
assert(error_mode != ErrorMode::None || passes == 1);
@ -844,7 +841,7 @@ void BC1Encoder::RefineBlockLS(Color4x4 &pixels, EncodeResults &block, BlockMetr
}
}
template <ColorMode M>
template <BC1Encoder::ColorMode M>
void BC1Encoder::RefineBlockCF(Color4x4 &pixels, EncodeResults &block, BlockMetrics &metrics, ErrorMode error_mode, unsigned orderings) const {
const int color_count = (unsigned)M & 0x0F;
static_assert(color_count == 3 || color_count == 4);

@ -76,17 +76,6 @@ class BC1Encoder final : public BlockEncoderTemplate<BC1Block, 4, 4> {
TryAllInitialEndpoints = 128,
};
enum class ColorMode {
Incomplete = 0x00,
ThreeColor = 0x03,
FourColor = 0x04,
UseBlack = 0x10,
Solid = 0x20,
ThreeColorBlack = ThreeColor | UseBlack,
ThreeColorSolid = ThreeColor | Solid,
FourColorSolid = FourColor | Solid,
};
enum class ErrorMode {
// Perform no error checking at all.
None,
@ -144,7 +133,8 @@ class BC1Encoder final : public BlockEncoderTemplate<BC1Block, 4, 4> {
unsigned int GetOrderings4() const { return _orderings4; }
unsigned int GetOrderings3() const { return _orderings3; }
void SetOrderings(unsigned orderings4, unsigned orderings3);
void SetOrderings4(unsigned orderings4);
void SetOrderings3(unsigned orderings3);
void EncodeBlock(Color4x4 pixels, BC1Block *dest) const override;
@ -152,6 +142,17 @@ class BC1Encoder final : public BlockEncoderTemplate<BC1Block, 4, 4> {
using Hash = uint16_t;
using BlockMetrics = Color4x4::BlockMetrics;
enum class ColorMode {
Incomplete = 0x00,
ThreeColor = 0x03,
FourColor = 0x04,
UseBlack = 0x10,
Solid = 0x20,
ThreeColorBlack = ThreeColor | UseBlack,
ThreeColorSolid = ThreeColor | Solid,
FourColorSolid = FourColor | Solid,
};
// Unpacked BC1 block with metadata
struct EncodeResults {
Color low;

@ -0,0 +1,77 @@
class Color:
def __init__(self, r=0, g=0, b=0, a=1):
self.r = r
self.g = g
self.b = b
self.a = a
def __add__(self, a):
return Color(self.r + a.r, self.g + a.g, self.b + a.b, self.a + a.a)
def __mul__(self, c):
return Color(self.r * c, self.g * c, self.b * c, self.a * c)
def __rmul__(self, c):
return Color(self.r * c, self.g * c, self.b * c, self.a * c)
def __iter__(self):
return iter([self.r, self.g, self.b, self.a])
def __repr__(self):
return f'r: {self.r} g: {self.g} b: {self.b} a: {self.a}'
def __str__(self):
return self.to_hex()
@classmethod
def from_565(cls, int_value):
r = float((int_value & 0xF800) >> 11) / 0x1F
g = float((int_value & 0x07E0) >> 5) / 0x3F
b = float(int_value & 0x001F) / 0x1F
return cls(r, g, b)
def to_565(self):
r = int(self.r * 0x1F)
g = int(self.g * 0x3F)
b = int(self.b * 0x1F)
return (r << 11) | (g << 5) | b
@classmethod
def from_rgb24(cls, int_value):
r = float((int_value & 0xFF0000) >> 16) / 0xFF
g = float((int_value & 0x00FF00) >> 8) / 0xFF
b = float(int_value & 0x0000FF) / 0xFF
return cls(r, g, b)
def to_rgb24(self):
r = int(self.r * 0xFF)
g = int(self.g * 0xFF)
b = int(self.b * 0xFF)
return (r << 16) | (g << 8) | b
@classmethod
def from_rgba32(cls, int_value):
r = float((int_value & 0xFF000000) >> 24) / 0xFF
g = float((int_value & 0x00FF0000) >> 16) / 0xFF
b = float((int_value & 0x0000FF00) >> 8) / 0xFF
a = float(int_value & 0x000000FF) / 0xFF
return cls(r, g, b, a)
def to_rgba32(self):
r = int(self.r * 0xFF)
g = int(self.g * 0xFF)
b = int(self.b * 0xFF)
a = int(self.a * 0xFF)
return (r << 24) | (g << 16) | (b << 8) | a
def to_hex(self):
if self.a < 1:
return hex(self.to_rgba32())
else:
return hex(self.to_rgb24())

@ -0,0 +1,86 @@
import struct
import math
import operator
from functools import reduce
from color import Color
def bit_slice(value, size, count):
mask = (2 ** size) - 1
return [(value >> offset) & mask for offset in range(0, size * count, size)]
def bit_merge(values, size):
offsets = range(0, len(values) * size, size)
return reduce(operator.__or__, map(operator.lshift, values, offsets))
def triple_slice(triplet):
values = bit_slice(bit_merge(triplet, 8), 3, 8)
return [values[0:4], values[4:8]]
def triple_merge(rows):
values = rows[0] + rows[1]
return bit_slice(bit_merge(values, 3), 8, 3)
class BC1Block:
size = 8
def __init__(self):
self.color0 = Color()
self.color1 = Color()
self.selectors = [[0] * 4] * 4
def __repr__(self):
return repr(self.__dict__)
def __str__(self):
return f'color0: {str(self.color0)} color1: {str(self.color1)}, indices:{self.selectors}'
@staticmethod
def from_bytes(data):
block = struct.unpack_from('<2H4B', data)
result = BC1Block()
result.color0 = Color.from_565(block[0])
result.color1 = Color.from_565(block[1])
result.selectors = [bit_slice(row, 2, 4) for row in block[2:6]]
return result
def to_bytes(self):
return struct.pack('<2H4B',
self.color0.to_565(), self.color1.to_565(),
*(bit_merge(row, 2) for row in self.selectors))
def is_3color(self):
return self.color0.to_565() <= self.color1.to_565()
class BC4Block:
size = 8
def __init__(self):
self.alpha0 = 1
self.alpha1 = 1
self.selectors = [[0] * 4] * 4
def __repr__(self):
return repr(self.__dict__)
@staticmethod
def from_bytes(data):
block = struct.unpack_from('<2B6B', data)
result = BC4Block()
result.alpha0 = block[0] / 0xFF
result.alpha1 = block[1] / 0xFF
result.selectors = triple_slice(block[2:5]) + triple_slice(block[5:8])
return result
def to_bytes(self):
return struct.pack('<2B6B',
int(self.alpha0 * 0xFF), int(self.alpha1 * 0xFF),
*triple_merge(self.selectors[0:2]),
*triple_merge(self.selectors[2:4]))

@ -1,20 +1,51 @@
import unittest
import python_rgbcx
import color
import s3tc
class MyTestCase(unittest.TestCase):
class TestBC1Encoder(unittest.TestCase):
def setUp(self):
self.bc1_encoder = python_rgbcx.BC1Encoder()
self.image = b'\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x55\x55\x55\xFF\xAA\xAA\xAA\xFF' \
b'\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x55\x55\x55\xFF\xAA\xAA\xAA\xFF' \
b'\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x55\x55\x55\xFF\xAA\xAA\xAA\xFF' \
b'\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x55\x55\x55\xFF\xAA\xAA\xAA\xFF'
self.bc1_encoder = python_rgbcx.BC1Encoder(python_rgbcx.InterpolatorType.Ideal, 5)
self.bc1_encoder_no3color = python_rgbcx.BC1Encoder(python_rgbcx.InterpolatorType.Ideal, 5, False, False)
self.bc1_encoder_noblack = python_rgbcx.BC1Encoder(python_rgbcx.InterpolatorType.Ideal, 5, True, False)
# A block that should always encode greyscale
self.greyscale = b'\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x55\x55\x55\xFF\xAA\xAA\xAA\xFF' \
b'\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x55\x55\x55\xFF\xAA\xAA\xAA\xFF' \
b'\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x55\x55\x55\xFF\xAA\xAA\xAA\xFF' \
b'\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\x55\x55\x55\xFF\xAA\xAA\xAA\xFF'
# A block that should always encode 3-color with black when available
self.chroma_black = b'\x00\x00\x00\xFF\xFF\x00\x00\xFF\x88\x88\x00\xFF\x00\xFF\x00\xFF' \
b'\x00\x00\x00\xFF\xFF\x00\x00\xFF\x88\x88\x00\xFF\x00\xFF\x00\xFF' \
b'\x00\x00\x00\xFF\xFF\x00\x00\xFF\x88\x88\x00\xFF\x00\xFF\x00\xFF' \
b'\x00\x00\x00\xFF\xFF\x00\x00\xFF\x88\x88\x00\xFF\x00\xFF\x00\xFF'
def test_block_size(self):
out = self.bc1_encoder.encode_image(self.image, 4, 4)
out = self.bc1_encoder.encode_image(self.greyscale, 4, 4)
self.assertEqual(self.bc1_encoder.block_width, 4, 'incorrect reported block width')
self.assertEqual(self.bc1_encoder.block_height, 4, 'incorrect reported block height')
self.assertEqual(self.bc1_encoder.block_size, 8, 'incorrect reported block size')
self.assertEqual(len(out), 8, 'incorrect returned block size')
def test_block_3color(self):
out = s3tc.BC1Block.from_bytes(self.bc1_encoder.encode_image(self.chroma_black, 4, 4))
out_no_3color = s3tc.BC1Block.from_bytes(self.bc1_encoder_no3color.encode_image(self.chroma_black, 4, 4))
self.assertTrue(out.is_3color(), "incorrect color mode with use_3color enabled")
self.assertFalse(out_no_3color.is_3color(), "incorrect color mode with use_3color disabled")
def test_block_black(self):
out = s3tc.BC1Block.from_bytes(self.bc1_encoder.encode_image(self.chroma_black, 4, 4))
out_no_black = s3tc.BC1Block.from_bytes(self.bc1_encoder_noblack.encode_image(self.chroma_black, 4, 4))
self.assertTrue(any(3 in row for row in out.selectors), "use_3color_black enabled but not used")
self.assertFalse(out_no_black.is_3color()
and any(3 in row for row in out_no_black.selectors),
"use_3color_black disabled but 3 color block has black selectors")
if __name__ == '__main__':
unittest.main()

Loading…
Cancel
Save