Add BC1 tests

This commit is contained in:
Andrew Cassidy 2021-04-05 02:44:56 -07:00
parent 8ab324a661
commit 901dcc45df
16 changed files with 191 additions and 385 deletions

View File

@ -1,61 +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 "Texture.h"
#include <stdexcept>
#include <type_traits>
#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

View File

@ -22,11 +22,13 @@
#include <array>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <vector>
#include "Color.h"
#include "ColorBlock.h"
@ -69,40 +71,25 @@ class RawTexture : public Texture {
* @param width width of the texture in pixels
* @param height height of the texture in pixels
*/
RawTexture(int width, int height);
RawTexture(int width, int height) : Base(width, height), _pixels(_width * _height) {}
/**
* Move constructor
* @param other object to move
*/
RawTexture(RawTexture &&other);
Color 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.at(x + (y * _width));
}
/**
* Copy constructor
* @param other object to copy
*/
RawTexture(const RawTexture &other);
void 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.at(x + (y * _width)) = val;
}
/**
* 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); }
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.");
if (block_x < 0 || (block_x + 1) * N > _width) throw std::out_of_range("x value out of range.");
if (block_y < 0 || (block_y + 1) * M > _height) throw std::out_of_range("y value out of range.");
// coordinates in the image of the top-left pixel of the selected block
ColorBlock<N, M> block;
@ -126,8 +113,8 @@ class RawTexture : public Texture {
}
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.");
if (block_x < 0) throw std::out_of_range("x value out of range.");
if (block_y < 0) throw std::out_of_range("y value out of range.");
// coordinates in the image of the top-left pixel of the selected block
int pixel_x = block_x * N;
@ -147,14 +134,19 @@ class RawTexture : public Texture {
}
}
virtual const uint8_t *Data() const noexcept override { return reinterpret_cast<const uint8_t *>(_pixels); }
virtual uint8_t *Data() noexcept override { return reinterpret_cast<uint8_t *>(_pixels); }
virtual const uint8_t *Data() const noexcept override { return reinterpret_cast<const uint8_t *>(_pixels.data()); }
virtual uint8_t *Data() noexcept override { return reinterpret_cast<uint8_t *>(_pixels.data()); }
protected:
Color *_pixels;
std::vector<Color> _pixels;
};
template <typename B> class BlockTexture final: public Texture {
template <typename B> class BlockTexture final : public Texture {
private:
std::vector<B> _blocks;
int _width_b;
int _height_b;
public:
using BlockType = B;
using Base = Texture;
@ -164,65 +156,32 @@ template <typename B> class BlockTexture final: public Texture {
* @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;
BlockTexture(int width, int height) : Base(width, height) {
_width_b = (_width + B::Width - 1) / B::Width;
_height_b = (_height + B::Height - 1) / B::Height;
_blocks = std::vector<B>(_width_b * _height_b);
}
/**
* 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 - 1) / B::Width; }
constexpr int BlocksY() const { return (_height + B::Height - 1) / B::Height; }
constexpr std::tuple<int, int> BlocksXY() const { return std::tuple<int, int>(BlocksX(), BlocksY()); }
constexpr int BlocksX() const { return _width_b; }
constexpr int BlocksY() const { return _height_b; }
constexpr std::tuple<int, int> BlocksXY() const { return std::tuple<int, int>(_width_b, _height_b); }
B GetBlock(int x, int y) const {
if (x < 0 || x >= BlocksX()) throw std::out_of_range("x value out of range.");
if (y < 0 || y >= BlocksY()) throw std::out_of_range("y value out of range.");
return _blocks[x + (y * _width)];
if (x < 0 || x >= _width_b) throw std::out_of_range("x value out of range.");
if (y < 0 || y >= _height_b) throw std::out_of_range("y value out of range.");
return _blocks.at(x + (y * _width_b));
}
void SetBlock(int x, int y, const B &val) {
if (x < 0 || x >= BlocksX()) throw std::out_of_range("x value out of range.");
if (y < 0 || y >= BlocksY()) throw std::out_of_range("y value out of range.");
_blocks[x + (y * _width)] = val;
if (x < 0 || x >= _width_b) throw std::out_of_range("x value out of range.");
if (y < 0 || y >= _height_b) throw std::out_of_range("y value out of range.");
_blocks.at(x + (y * _width_b)) = val;
}
size_t Size() const noexcept override { return (size_t)(BlocksX() * BlocksY()) * sizeof(B); }
size_t Size() const noexcept override { return _blocks.size() * sizeof(B); }
const uint8_t *Data() const noexcept override { return reinterpret_cast<const uint8_t *>(_blocks); }
uint8_t *Data() noexcept override { return reinterpret_cast<uint8_t *>(_blocks); }
protected:
B *_blocks;
const uint8_t *Data() const noexcept override { return reinterpret_cast<const uint8_t *>(_blocks.data()); }
uint8_t *Data() noexcept override{ return reinterpret_cast<uint8_t *>(_blocks.data()); }
};
} // namespace quicktex

View File

@ -52,7 +52,7 @@ PYBIND11_MODULE(_quicktex, m) {
// RawTexture
py::class_<RawTexture> raw_texture(m, "RawTexture", texture);
py::class_<RawTexture, Texture> raw_texture(m, "RawTexture");
raw_texture.def(py::init<int, int>(), "width"_a, "height"_a);
raw_texture.def_static("frombytes", &BufferToTexture<RawTexture>, "data"_a, "width"_a, "height"_a);

View File

@ -19,6 +19,7 @@
#pragma once
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include <cstdint>
@ -140,7 +141,8 @@ template <typename T, typename Getter, typename Setter, typename Extent> void De
"__setitem__", [set, ext](T& self, int index, V val) { (self.*set)(PyIndex(index, (self.*ext)()), val); }, "key"_a, "value"_a);
}
template <typename T, typename Getter, typename Setter, typename Extent> void DefSubscript2D(py::class_<T> t, Getter&& get, Setter&& set, Extent&& ext) {
template <typename Tpy, typename Getter, typename Setter, typename Extent> void DefSubscript2D(Tpy t, Getter&& get, Setter&& set, Extent&& ext) {
using T = typename Tpy::type;
using V = typename std::invoke_result<Getter, T*, int, int>::type;
using Coords = std::tuple<int, int>;
t.def(
@ -154,7 +156,7 @@ template <typename T, typename Getter, typename Setter, typename Extent> void De
"key"_a);
t.def(
"__setitem__",
[set, ext](T& self, Coords pnt, V val) {
[set, ext](T& self, Coords pnt, const V& val) {
Coords s = (self.*ext)();
int x = PyIndex(std::get<0>(pnt), std::get<0>(s), "x");
int y = PyIndex(std::get<1>(pnt), std::get<1>(s), "y");
@ -186,6 +188,8 @@ template <typename B> py::class_<B> BindBlock(py::module_& m, const char* name)
block.def_property_readonly_static(
"size", [](py::object) { return sizeof(B); }, "The size of the block in bytes.");
block.def(py::self == py::self);
block.def_buffer([](B& b) { return py::buffer_info(reinterpret_cast<uint8_t*>(&b), sizeof(B)); });
block.def(
"tobytes", [](const B& b) { return py::bytes(reinterpret_cast<const char*>(&b), sizeof(B)); },
@ -216,7 +220,7 @@ template <typename B> py::class_<BlockTexture<B>> BindBlockTexture(py::module_&
using BTex = BlockTexture<B>;
py::class_<BTex> block_texture(m, name, py::type::of<Texture>(), py::is_final());
py::class_<BTex, Texture> block_texture(m, name);
block_texture.def(py::init<int, int>(), "width"_a, "height"_a, Format(constructor_str, name).c_str());
block_texture.def_static("from_bytes", &BufferToTexture<BTex>, "data"_a, "width"_a, "height"_a, Format(from_bytes_str, name).c_str());

View File

@ -38,8 +38,8 @@ void InitS3TC(py::module_ &m) {
InitInterpolator(s3tc);
InitBC1(s3tc);
InitBC4(s3tc);
InitBC3(s3tc);
InitBC5(s3tc);
// InitBC4(s3tc);
// InitBC3(s3tc);
// InitBC5(s3tc);
}
} // namespace quicktex::bindings

View File

@ -122,5 +122,8 @@ class alignas(8) BC1Block {
void SetSelectors(const SelectorArray& unpacked);
bool Is3Color() const { return GetColor0Raw() <= GetColor1Raw(); }
bool operator==(const BC1Block& other) const = default;
bool operator!=(const BC1Block& other) const = default;
};
} // namespace quicktex::s3tc

View File

@ -55,5 +55,8 @@ class alignas(8) BC3Block {
alpha_block = blocks.first;
color_block = blocks.second;
}
bool operator==(const BC3Block& other) const = default;
bool operator!=(const BC3Block& other) const = default;
};
} // namespace quicktex::s3tc

View File

@ -99,6 +99,9 @@ class alignas(8) BC4Block {
/// The interpolated values of this block as an array of 8 integers.
std::array<uint8_t, 8> GetValues() const { return Is6Value() ? GetValues6() : GetValues8(); }
bool operator==(const BC4Block& other) const = default;
bool operator!=(const BC4Block& other) const = default;
private:
std::array<uint8_t, 8> GetValues6() const;
std::array<uint8_t, 8> GetValues8() const;

View File

@ -53,5 +53,8 @@ class alignas(8) BC5Block {
chan0_block = pair.first;
chan1_block = pair.second;
}
bool operator==(const BC5Block& other) const = default;
bool operator!=(const BC5Block& other) const = default;
};
} // namespace quicktex::s3tc

0
tests/__init__.py Normal file
View File

View File

@ -1,30 +0,0 @@
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)}
]

View File

@ -1,81 +0,0 @@
class Color:
def __init__(self, r=0, g=0, b=0, a=0xFF):
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()
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
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())

View File

@ -25,6 +25,7 @@
#include <string>
namespace py = pybind11;
using namespace pybind11::literals;
#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)
@ -37,6 +38,7 @@ int main() {
site.attr("addsitedir")(CUSTOM_SYS_PATH);
py::module_ nose = py::module_::import("nose");
py::module_ tests = py::module_::import("test_texture");
nose.attr("main")("test_texture");
py::module_ tests = py::module_::import("tests");
py::list argv(1);
nose.attr("runmodule")("name"_a = "tests.test_bc1", "exit"_a = false);
}

View File

@ -1,89 +0,0 @@
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, 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)}, selectors:{self.selectors}'
@staticmethod
def frombytes(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 tobytes(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()
def is_3color_black(self):
return self.is_3color() and any(3 in row for row in self.selectors)
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 frombytes(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 tobytes(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]))

View File

@ -1,32 +1,122 @@
import unittest
import nose
from parameterized import parameterized_class
from s3tc import BC1Block
from images import Blocks
# import quicktex.s3tc.bc1 as bc1
# import quicktex.s3tc.interpolator as interpolator
#
# ColorMode = bc1.BC1Encoder.ColorMode
from parameterized import parameterized, parameterized_class
import quicktex.s3tc.bc1 as bc1
in_endpoints = ((253, 254, 255), (65, 70, 67)) # has some small changes that should encode the same
out_endpoints = ((255, 255, 255, 255), (66, 69, 66, 255))
selectors = [[0, 2, 3, 1]] * 4
block_bytes = b'\xff\xff\x28\x42\x78\x78\x78\x78'
class TestBC1Block(unittest.TestCase):
"""Tests for the BC1Block class"""
def test_size(self):
"""Test the size and dimensions of BC1Block"""
self.assertEqual(bc1.BC1Block.size, 8, 'incorrect block size')
self.assertEqual(bc1.BC1Block.width, 4, 'incorrect block width')
self.assertEqual(bc1.BC1Block.height, 4, 'incorrect block width')
self.assertEqual(bc1.BC1Block.dimensions, (4, 4), 'incorrect block dimensions')
def test_buffer(self):
"""Test the buffer protocol of BC1Block"""
block = bc1.BC1Block()
mv = memoryview(block)
self.assertFalse(mv.readonly, 'buffer is readonly')
self.assertTrue(mv.c_contiguous, 'buffer is not contiguous')
self.assertEqual(mv.ndim, 1, 'buffer is multidimensional')
self.assertEqual(mv.nbytes, bc1.BC1Block.size, 'buffer is the wrong size')
self.assertEqual(mv.format, 'B', 'buffer has the wrong format')
mv[:] = block_bytes
self.assertEqual(mv.tobytes(), block_bytes, 'incorrect buffer data')
def test_constructor(self):
"""Test constructing a block out of endpoints and selectors"""
block = bc1.BC1Block(*in_endpoints, selectors)
self.assertEqual(block.tobytes(), block_bytes, 'incorrect block bytes')
self.assertEqual(block.selectors, selectors, 'incorrect selectors')
self.assertEqual(block.endpoints, out_endpoints, 'incorrect endpoints')
self.assertFalse(block.is_3color, 'incorrect color mode')
def test_frombytes(self):
"""Test constructing a block out of raw data"""
block = bc1.BC1Block.frombytes(block_bytes)
self.assertEqual(block.tobytes(), block_bytes, 'incorrect block bytes')
self.assertEqual(block.selectors, selectors, 'incorrect selectors')
self.assertEqual(block.endpoints, out_endpoints, 'incorrect endpoints')
self.assertFalse(block.is_3color, 'incorrect color mode')
def test_eq(self):
block1 = bc1.BC1Block.frombytes(block_bytes)
block2 = bc1.BC1Block.frombytes(block_bytes)
self.assertEqual(block1, block2, 'identical blocks not equal')
@parameterized_class(
("name", "w", "h", "wb", "hb"), [
("8x8", 8, 8, 2, 2),
("9x9", 9, 9, 3, 3),
("7x7", 7, 7, 2, 2),
("7x9", 7, 9, 2, 3)
])
class TestBC1Texture(unittest.TestCase):
def setUp(self):
self.tex = bc1.BC1Texture(self.w, self.h)
self.size = self.wb * self.hb * bc1.BC1Block.size
def test_size(self):
"""Test size and dimensions of BC1Texture"""
self.assertEqual(self.tex.size, self.size, 'incorrect texture size')
self.assertEqual(len(self.tex.tobytes()), self.size, 'incorrect texture size from tobytes')
self.assertEqual(self.tex.width, self.w, 'incorrect texture width')
self.assertEqual(self.tex.height, self.h, 'incorrect texture height')
self.assertEqual(self.tex.dimensions, (self.w, self.h), 'incorrect texture dimensions')
self.assertEqual(self.tex.width_blocks, self.wb, 'incorrect texture width_blocks')
self.assertEqual(self.tex.height_blocks, self.hb, 'incorrect texture width_blocks')
self.assertEqual(self.tex.dimensions_blocks, (self.wb, self.hb), 'incorrect texture dimensions_blocks')
def test_blocks(self):
"""Test getting and setting blocks to BC1Texture"""
blocks = [[bc1.BC1Block.frombytes(bytes([x, y] + [0] * 6)) for x in range(self.wb)] for y in range(self.hb)]
for x in range(self.wb):
for y in range(self.hb):
self.tex[x, y] = blocks[y][x]
b = self.tex.tobytes()
for x in range(self.wb):
for y in range(self.hb):
index = (x + (y * self.wb)) * bc1.BC1Block.size
tb = self.tex[x, y]
fb = bc1.BC1Block.frombytes(b[index:index + bc1.BC1Block.size])
self.assertEqual(tb, blocks[y][x], 'incorrect block read from texture')
self.assertEqual(fb, blocks[y][x], 'incorrect block read from texture bytes')
self.assertEqual(self.tex[-1, -1], self.tex[self.wb - 1, self.hb - 1], 'incorrect negative subscripting')
def test_buffer(self):
"""Test the buffer protocol of BC1Texture"""
mv = memoryview(self.tex)
self.assertFalse(mv.readonly, 'buffer is readonly')
self.assertTrue(mv.c_contiguous, 'buffer is not contiguous')
self.assertEqual(mv.ndim, 1, 'buffer is multidimensional')
self.assertEqual(mv.nbytes, self.size, 'buffer is the wrong size')
self.assertEqual(mv.format, 'B', 'buffer has the wrong format')
data = block_bytes * self.wb * self.hb
mv[:] = data
self.assertEqual(mv.tobytes(), data, 'incorrect buffer data')
def get_class_name_blocks(cls, num, params_dict):
return "%s%s" % (cls.__name__, params_dict['color_mode'].name,)
#
# class TestBC1Encoder(unittest.TestCase):
# """Test BC1 Encoder"""
#
# def setUp(self):
# self.bc1_encoder = bc1.BC1Encoder(5)
#
# def test_block_size(self):
# """Ensure encoded block size is 8 bytes."""
# out = self.bc1_encoder.encode_image(Blocks.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 get_class_name_blocks(cls, num, params_dict):
# return "%s%s" % (cls.__name__, params_dict['color_mode'].name,)
#
#
# @parameterized_class([
@ -69,7 +159,7 @@ from images import Blocks
# self.assertEqual(selectors, out.selectors, "block has incorrect selectors for 3 color with black test block")
# else:
# self.assertFalse(out.is_3color_black(), "returned incorrect block color mode for 3 color with black test block")
#
#
# if __name__ == '__main__':
# nose.main()
if __name__ == '__main__':
nose.main()

View File

@ -1,7 +1,7 @@
import unittest
import nose
import quicktex
import images
import tests.images as images
import tempfile
from PIL import Image