diff --git a/quicktex/Texture.cpp b/quicktex/Texture.cpp deleted file mode 100644 index 2508cb2..0000000 --- a/quicktex/Texture.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* Python-rgbcx Texture Compression Library - Copyright (C) 2021 Andrew Cassidy - Partially derived from rgbcx.h written by Richard Geldreich - 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 . - */ -#include "Texture.h" - -#include -#include - -#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 diff --git a/quicktex/Texture.h b/quicktex/Texture.h index e21ddf9..2f2a6f5 100644 --- a/quicktex/Texture.h +++ b/quicktex/Texture.h @@ -22,11 +22,13 @@ #include #include #include +#include #include #include #include #include #include +#include #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(Width() * Height()) * sizeof(Color); } + size_t Size() const noexcept override { return static_cast(Width() * Height()) * sizeof(Color); } template ColorBlock 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 block; @@ -126,8 +113,8 @@ class RawTexture : public Texture { } template void SetBlock(int block_x, int block_y, const ColorBlock &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(_pixels); } - virtual uint8_t *Data() noexcept override { return reinterpret_cast(_pixels); } + virtual const uint8_t *Data() const noexcept override { return reinterpret_cast(_pixels.data()); } + virtual uint8_t *Data() noexcept override { return reinterpret_cast(_pixels.data()); } protected: - Color *_pixels; + std::vector _pixels; }; -template class BlockTexture final: public Texture { +template class BlockTexture final : public Texture { + private: + std::vector _blocks; + int _width_b; + int _height_b; + public: using BlockType = B; using Base = Texture; @@ -164,65 +156,32 @@ template 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 &&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(_width_b * _height_b); } - /** - * Copy constructor - * @param other object to copy - */ - BlockTexture(const BlockTexture &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 other) { - swap(*this, other); - return *this; - } - - friend void swap(BlockTexture &first, BlockTexture &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 BlocksXY() const { return std::tuple(BlocksX(), BlocksY()); } + constexpr int BlocksX() const { return _width_b; } + constexpr int BlocksY() const { return _height_b; } + constexpr std::tuple BlocksXY() const { return std::tuple(_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(_blocks); } - uint8_t *Data() noexcept override { return reinterpret_cast(_blocks); } - - protected: - B *_blocks; + const uint8_t *Data() const noexcept override { return reinterpret_cast(_blocks.data()); } + uint8_t *Data() noexcept override{ return reinterpret_cast(_blocks.data()); } }; } // namespace quicktex \ No newline at end of file diff --git a/quicktex/_bindings.cpp b/quicktex/_bindings.cpp index df0d8b4..bee195e 100644 --- a/quicktex/_bindings.cpp +++ b/quicktex/_bindings.cpp @@ -52,7 +52,7 @@ PYBIND11_MODULE(_quicktex, m) { // RawTexture - py::class_ raw_texture(m, "RawTexture", texture); + py::class_ raw_texture(m, "RawTexture"); raw_texture.def(py::init(), "width"_a, "height"_a); raw_texture.def_static("frombytes", &BufferToTexture, "data"_a, "width"_a, "height"_a); diff --git a/quicktex/_bindings.h b/quicktex/_bindings.h index 8addeb9..74cce49 100644 --- a/quicktex/_bindings.h +++ b/quicktex/_bindings.h @@ -19,6 +19,7 @@ #pragma once +#include #include #include @@ -140,7 +141,8 @@ template void De "__setitem__", [set, ext](T& self, int index, V val) { (self.*set)(PyIndex(index, (self.*ext)()), val); }, "key"_a, "value"_a); } -template void DefSubscript2D(py::class_ t, Getter&& get, Setter&& set, Extent&& ext) { +template void DefSubscript2D(Tpy t, Getter&& get, Setter&& set, Extent&& ext) { + using T = typename Tpy::type; using V = typename std::invoke_result::type; using Coords = std::tuple; t.def( @@ -154,7 +156,7 @@ template 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 py::class_ 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(&b), sizeof(B)); }); block.def( "tobytes", [](const B& b) { return py::bytes(reinterpret_cast(&b), sizeof(B)); }, @@ -216,7 +220,7 @@ template py::class_> BindBlockTexture(py::module_& using BTex = BlockTexture; - py::class_ block_texture(m, name, py::type::of(), py::is_final()); + py::class_ block_texture(m, name); block_texture.def(py::init(), "width"_a, "height"_a, Format(constructor_str, name).c_str()); block_texture.def_static("from_bytes", &BufferToTexture, "data"_a, "width"_a, "height"_a, Format(from_bytes_str, name).c_str()); diff --git a/quicktex/s3tc/_bindings.cpp b/quicktex/s3tc/_bindings.cpp index bb7e689..7386479 100644 --- a/quicktex/s3tc/_bindings.cpp +++ b/quicktex/s3tc/_bindings.cpp @@ -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 diff --git a/quicktex/s3tc/bc1/BC1Block.h b/quicktex/s3tc/bc1/BC1Block.h index 7389830..2b48640 100644 --- a/quicktex/s3tc/bc1/BC1Block.h +++ b/quicktex/s3tc/bc1/BC1Block.h @@ -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 \ No newline at end of file diff --git a/quicktex/s3tc/bc3/BC3Block.h b/quicktex/s3tc/bc3/BC3Block.h index b7b1a07..ed35d92 100644 --- a/quicktex/s3tc/bc3/BC3Block.h +++ b/quicktex/s3tc/bc3/BC3Block.h @@ -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 \ No newline at end of file diff --git a/quicktex/s3tc/bc4/BC4Block.h b/quicktex/s3tc/bc4/BC4Block.h index e041027..2ead4c0 100644 --- a/quicktex/s3tc/bc4/BC4Block.h +++ b/quicktex/s3tc/bc4/BC4Block.h @@ -99,6 +99,9 @@ class alignas(8) BC4Block { /// The interpolated values of this block as an array of 8 integers. std::array GetValues() const { return Is6Value() ? GetValues6() : GetValues8(); } + bool operator==(const BC4Block& other) const = default; + bool operator!=(const BC4Block& other) const = default; + private: std::array GetValues6() const; std::array GetValues8() const; diff --git a/quicktex/s3tc/bc5/BC5Block.h b/quicktex/s3tc/bc5/BC5Block.h index 6b2b58e..52487cf 100644 --- a/quicktex/s3tc/bc5/BC5Block.h +++ b/quicktex/s3tc/bc5/BC5Block.h @@ -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 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/blocks.py b/tests/blocks.py deleted file mode 100644 index 7589094..0000000 --- a/tests/blocks.py +++ /dev/null @@ -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)} -] diff --git a/tests/color.py b/tests/color.py deleted file mode 100644 index 1b4b186..0000000 --- a/tests/color.py +++ /dev/null @@ -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()) diff --git a/tests/run_tests.cpp b/tests/run_tests.cpp index 91b3e9f..bd0dc7c 100644 --- a/tests/run_tests.cpp +++ b/tests/run_tests.cpp @@ -25,6 +25,7 @@ #include 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); } \ No newline at end of file diff --git a/tests/s3tc.py b/tests/s3tc.py deleted file mode 100644 index 034d571..0000000 --- a/tests/s3tc.py +++ /dev/null @@ -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])) diff --git a/tests/test_bc1.py b/tests/test_bc1.py index b73a32c..e5451e7 100644 --- a/tests/test_bc1.py +++ b/tests/test_bc1.py @@ -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() diff --git a/tests/test_texture.py b/tests/test_texture.py index 25481d8..dfaa1f4 100644 --- a/tests/test_texture.py +++ b/tests/test_texture.py @@ -1,7 +1,7 @@ import unittest import nose import quicktex -import images +import tests.images as images import tempfile from PIL import Image