More unit tests and bindings
parent
e58871167e
commit
b63c26a45a
@ -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…
Reference in New Issue