2022-05-23 01:40:13 +00:00
|
|
|
import math
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
from PIL import Image, ImageChops
|
|
|
|
|
2021-04-07 08:08:45 +00:00
|
|
|
from quicktex.s3tc.bc4 import BC4Block, BC4Texture, BC4Encoder, BC4Decoder
|
2022-04-10 07:04:08 +00:00
|
|
|
from .images import BC4Blocks
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
block_bytes = b'\xF0\x10\x88\x86\x68\xAC\xCF\xFA'
|
|
|
|
selectors = [[0, 1, 2, 3]] * 2 + [[4, 5, 6, 7]] * 2
|
|
|
|
endpoints = (240, 16)
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-04-19 02:53:26 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
class TestBC4Block:
|
|
|
|
"""Tests for the BC1Block class"""
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
def test_size(self):
|
|
|
|
"""Test the size and dimensions of BC4Block"""
|
2022-05-23 01:40:13 +00:00
|
|
|
assert BC4Block.nbytes == 8
|
|
|
|
assert BC4Block.width == 4
|
|
|
|
assert BC4Block.height == 4
|
|
|
|
assert BC4Block.size == (4, 4)
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
def test_buffer(self):
|
|
|
|
"""Test the buffer protocol of BC4Block"""
|
|
|
|
block = BC4Block()
|
|
|
|
mv = memoryview(block)
|
2022-05-23 01:40:13 +00:00
|
|
|
mv[:] = block_bytes
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
assert not mv.readonly
|
|
|
|
assert mv.c_contiguous
|
|
|
|
assert mv.ndim == 1
|
|
|
|
assert mv.nbytes == 8
|
|
|
|
assert mv.format == 'B'
|
|
|
|
assert mv.tobytes() == block_bytes
|
|
|
|
assert mv.tobytes() == block.tobytes()
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
def test_constructor(self):
|
|
|
|
"""Test constructing a block out of endpoints and selectors"""
|
2022-05-23 01:40:13 +00:00
|
|
|
block = BC4Block(*endpoints, selectors)
|
|
|
|
assert block.tobytes() == block_bytes
|
|
|
|
assert block.selectors == selectors
|
|
|
|
assert block.endpoints == endpoints
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
def test_frombytes(self):
|
|
|
|
"""Test constructing a block out of raw data"""
|
2022-05-23 01:40:13 +00:00
|
|
|
block = BC4Block.frombytes(block_bytes)
|
|
|
|
assert block.tobytes() == block_bytes
|
|
|
|
assert block.selectors == selectors
|
|
|
|
assert block.endpoints == endpoints
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
def test_eq(self):
|
2021-04-08 00:07:03 +00:00
|
|
|
"""Test equality between two identical blocks"""
|
2022-05-23 01:40:13 +00:00
|
|
|
block1 = BC4Block.frombytes(block_bytes)
|
|
|
|
block2 = BC4Block.frombytes(block_bytes)
|
|
|
|
assert block1 == block2
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
def test_values_6(self):
|
2021-04-08 00:07:03 +00:00
|
|
|
"""Test values of a 6-value block"""
|
2021-04-07 08:08:45 +00:00
|
|
|
block = BC4Block(8, 248, [[0] * 4] * 4)
|
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
assert block.values == [8, 248, 56, 104, 152, 200, 0, 255]
|
|
|
|
assert block.is_6value
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
def test_values_8(self):
|
2021-04-08 00:07:03 +00:00
|
|
|
"""Test values of an 8-value block"""
|
2021-04-07 08:08:45 +00:00
|
|
|
block = BC4Block(240, 16, [[0] * 4] * 4)
|
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
assert block.values == [240, 16, 208, 176, 144, 112, 80, 48]
|
|
|
|
assert not block.is_6value
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
# noinspection PyMethodMayBeStatic
|
|
|
|
@pytest.mark.parametrize('w', [7, 8, 9])
|
|
|
|
@pytest.mark.parametrize('h', [7, 8, 9])
|
|
|
|
class TestBC4Texture:
|
|
|
|
def test_dimensions(self, w, h):
|
|
|
|
"""Test dimensions of BC4Texture in pixels, blocks, and bytes"""
|
|
|
|
tex = BC4Texture(w, h)
|
|
|
|
wb = math.ceil(w / 4)
|
|
|
|
hb = math.ceil(h / 4)
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
assert tex.nbytes == BC4Block.nbytes * wb * hb # block width x block height
|
|
|
|
assert len(tex.tobytes()) == tex.nbytes
|
|
|
|
|
|
|
|
assert tex.width == w
|
|
|
|
assert tex.height == h
|
|
|
|
assert tex.size == (w, h)
|
|
|
|
|
|
|
|
assert tex.width_blocks == wb
|
|
|
|
assert tex.height_blocks == hb
|
|
|
|
assert tex.size_blocks == (wb, hb)
|
|
|
|
|
|
|
|
def test_blocks(self, w, h):
|
2021-04-07 08:08:45 +00:00
|
|
|
"""Test getting and setting blocks to BC4Texture"""
|
2022-05-23 01:40:13 +00:00
|
|
|
tex = BC4Texture(w, h)
|
|
|
|
|
|
|
|
# generate garbage blocks with the x and y index in the first 2 bytes
|
|
|
|
blocks = [
|
|
|
|
[BC4Block.frombytes(bytes([x, y] + [0] * 6)) for x in range(tex.width_blocks)]
|
|
|
|
for y in range(tex.height_blocks)
|
|
|
|
]
|
|
|
|
# assign those blocks to the texture
|
|
|
|
for x in range(tex.width_blocks):
|
|
|
|
for y in range(tex.height_blocks):
|
|
|
|
tex[x, y] = blocks[y][x]
|
|
|
|
|
|
|
|
# get the blocks and analyze
|
|
|
|
b = tex.tobytes()
|
|
|
|
for x in range(tex.width_blocks):
|
|
|
|
for y in range(tex.height_blocks):
|
|
|
|
index = (x + (y * tex.width_blocks)) * BC4Block.nbytes
|
|
|
|
tb = tex[x, y]
|
2021-04-08 23:04:21 +00:00
|
|
|
fb = BC4Block.frombytes(b[index : index + BC4Block.nbytes])
|
2022-05-23 01:40:13 +00:00
|
|
|
assert tb == blocks[y][x]
|
|
|
|
assert fb == blocks[y][x]
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
def text_subscript(self, w, h):
|
|
|
|
"""Test BC4Texture subscripting for blocks"""
|
|
|
|
tex = BC4Texture(w, h)
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
# ensure negative wraparound works
|
|
|
|
assert tex[-1, -1] == tex[tex.width_blocks - 1, tex.height_blocks - 1]
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
with pytest.raises(IndexError):
|
|
|
|
_ = tex[tex.width_blocks, tex.height_blocks]
|
|
|
|
with pytest.raises(IndexError):
|
|
|
|
_ = tex[-1 - tex.width_blocks, -1 - tex.height_blocks]
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
def test_buffer(self, w, h):
|
|
|
|
"""Test the buffer protocol of BC1Texture"""
|
|
|
|
tex = BC4Texture(w, h)
|
|
|
|
mv = memoryview(tex)
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
data = block_bytes * tex.width_blocks * tex.height_blocks
|
2021-04-07 08:08:45 +00:00
|
|
|
mv[:] = data
|
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
assert not mv.readonly
|
|
|
|
assert mv.c_contiguous
|
|
|
|
assert mv.nbytes == tex.nbytes
|
|
|
|
assert mv.format == 'B'
|
|
|
|
assert mv.tobytes() == data
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2021-04-08 06:21:54 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
class TestBC4Encoder:
|
|
|
|
"""Test BC4Encoder"""
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
def test_block(self):
|
|
|
|
"""Test encoder output with 8 value test block"""
|
2022-05-23 01:40:13 +00:00
|
|
|
encoder = BC4Encoder(0)
|
|
|
|
out_tex = encoder.encode(BC4Blocks.eight_value.texture)
|
2021-04-07 08:08:45 +00:00
|
|
|
out_block = out_tex[0, 0]
|
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
assert out_tex.size_blocks == (1, 1)
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
assert not out_block.is_6value
|
|
|
|
assert out_block == BC4Blocks.eight_value.block
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
@pytest.mark.parametrize('texture', [BC4Blocks.eight_value, BC4Blocks.six_value])
|
|
|
|
class TestBC4Decoder:
|
|
|
|
"""Test BC4Decoder"""
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
def test_block(self, texture):
|
2021-04-07 08:08:45 +00:00
|
|
|
"""Test decoder output for a single block"""
|
2022-05-23 01:40:13 +00:00
|
|
|
block = texture.block
|
|
|
|
image = texture.image
|
|
|
|
decoder = BC4Decoder(0)
|
2021-04-07 08:08:45 +00:00
|
|
|
in_tex = BC4Texture(4, 4)
|
|
|
|
in_tex[0, 0] = block
|
2022-05-23 01:40:13 +00:00
|
|
|
out_tex = decoder.decode(in_tex)
|
2021-04-07 08:08:45 +00:00
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
assert out_tex.size == (4, 4)
|
2021-04-07 08:08:45 +00:00
|
|
|
|
|
|
|
out_img = Image.frombytes('RGBA', (4, 4), out_tex.tobytes())
|
|
|
|
img_diff = ImageChops.difference(out_img, image).convert('L')
|
|
|
|
img_hist = img_diff.histogram()
|
|
|
|
|
2022-05-23 01:40:13 +00:00
|
|
|
assert img_hist[0] == 16
|