Improved subscripting

This commit is contained in:
Andrew Cassidy 2021-03-30 02:36:27 -07:00
parent bd454d9d20
commit 28b541d49a
4 changed files with 64 additions and 32 deletions

View File

@ -203,16 +203,17 @@ template <typename B> class BlockTexture : public Texture {
constexpr int BlocksX() const { return _width / B::Width; }
constexpr int BlocksY() const { return _height / B::Height; }
constexpr std::tuple<int, int> BlocksXY() const { return std::tuple<int, int>(BlocksX(), BlocksY()); }
virtual B GetBlock(int x, int y) const {
if (x < 0 || x >= BlocksX()) throw std::invalid_argument("x value out of range.");
if (y < 0 || y >= BlocksY()) throw std::invalid_argument("y value out of range.");
if (x < 0 || x >= BlocksX()) throw std::range_error("x value out of range.");
if (y < 0 || y >= BlocksY()) throw std::range_error("y value out of range.");
return _blocks[x + (y * _width)];
}
virtual void SetBlock(int x, int y, const B &val) {
if (x < 0 || x >= BlocksX()) throw std::invalid_argument("x value out of range.");
if (y < 0 || y >= BlocksY()) throw std::invalid_argument("y value out of range.");
if (x < 0 || x >= BlocksX()) throw std::range_error("x value out of range.");
if (y < 0 || y >= BlocksY()) throw std::range_error("y value out of range.");
_blocks[x + (y * _width)] = val;
}

View File

@ -88,6 +88,8 @@ PYBIND11_MODULE(_quicktex, m) {
py::options options;
// Texture
py::class_<Texture> texture(m, "Texture", py::buffer_protocol());
texture.def_property_readonly("size", &Texture::Size);
@ -98,19 +100,13 @@ PYBIND11_MODULE(_quicktex, m) {
texture.def_buffer([](Texture &t) { return py::buffer_info(t.Data(), t.Size()); });
texture.def("tobytes", [](const Texture &t) { return py::bytes(reinterpret_cast<const char *>(t.Data()), t.Size()); });
//RawTexture
py::class_<RawTexture> raw_texture(m, "RawTexture", texture);
raw_texture.def(py::init<int, int>(), "width"_a, "height"_a);
raw_texture.def("__getitem__", [](RawTexture &t, std::tuple<int, int> pnt) {
int x, y;
PyIndex2D(pnt, t.Dimensions(), x, y);
return t.GetPixel(x, y);
});
raw_texture.def("__setitem__", [](RawTexture &t, std::tuple<int, int> pnt, Color val) {
int x, y;
PyIndex2D(pnt, t.Dimensions(), x, y);
t.SetPixel(x, y, val);
});
DefSubscript2D(raw_texture, &RawTexture::GetPixel, &RawTexture::SetPixel, &RawTexture::Dimensions);
raw_texture.def_static("frombytes", &BufferToTexture<RawTexture>, "data"_a, "width"_a, "height"_a);

View File

@ -25,6 +25,7 @@
#include <cstring>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include "Block.h"
#include "Color.h"
@ -60,16 +61,41 @@ template <typename T> T BufferToTexture(py::buffer buf, int width, int height) {
return output;
}
inline int PyIndex(int val, int size) {
if (val < -size) throw std::range_error("index out of range");
if (val >= size) throw std::range_error("index out of range");
inline int PyIndex(int val, int size, std::string name = "index") {
if (val < -size || val >= size) throw std::out_of_range(name + " value out of range");
if (val < 0) return size + val;
return val;
}
inline void PyIndex2D(std::tuple<int, int> pnt, std::tuple<int, int> size, int& x, int& y) {
x = PyIndex(std::get<0>(pnt), std::get<0>(size));
y = PyIndex(std::get<1>(pnt), std::get<1>(size));
template <typename T, typename Getter, typename Setter, typename Extent> void DefSubscript(py::class_<T> t, Getter&& get, Setter&& set, Extent&& ext) {
using V = typename std::invoke_result<Getter, T*, int>::type;
t.def(
"__getitem__", [get, ext](T& self, int index) { return (self.*get)(PyIndex(index, (self.*ext)())); }, "key"_a);
t.def(
"__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) {
using V = typename std::invoke_result<Getter, T*, int, int>::type;
using Coords = std::tuple<int, int>;
t.def(
"__getitem__",
[get, ext](T& self, Coords pnt) {
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");
return (self.*get)(x, y);
},
"key"_a);
t.def(
"__setitem__",
[set, ext](T& self, Coords pnt, 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");
(self.*set)(x, y, val);
},
"key"_a, "value"_a);
}
template <typename B> py::class_<BlockTexture<B>> BindBlockTexture(py::module_& m, const char* name) {
@ -87,5 +113,8 @@ template <typename B> py::class_<BlockTexture<B>> BindBlockTexture(py::module_&
block_texture.def_property_readonly("blocks_x", &BTex::BlocksX);
block_texture.def_property_readonly("blocks_y", &BTex::BlocksY);
block_texture.def_property_readonly("blocks_xy", &BTex::BlocksXY);
DefSubscript2D(block_texture, &BTex::GetPixel, &BTex::SetPixel, &BTex::BlocksXY);
}
} // namespace quicktex::bindings

View File

@ -7,21 +7,23 @@ from PIL import Image
class TestRawTexture(unittest.TestCase):
boilerplate = Image.open(images.image_path + '/Boilerplate.png')
bp_bytes = boilerplate.tobytes('raw', 'RGBX')
width = boilerplate.width
height = boilerplate.height
def setUp(self):
self.width = 1024
self.height = 1024
self.texture = quicktex.RawTexture(self.width, self.height)
self.boilerplate = Image.open(images.image_path + '/Boilerplate.png')
def test_size(self):
"""test byte size and image dimensions"""
"""Test byte size and image dimensions"""
self.assertEqual(self.texture.size, self.width * self.height * 4, "incorrect texture byte size")
self.assertEqual(self.texture.width, self.width, "incorrect texture width")
self.assertEqual(self.texture.height, self.height, "incorrect texture height")
self.assertEqual(self.texture.dimensions, (self.width, self.height), "incorrect texture dimension tuple")
def test_pixels(self):
"""Test get_ and set_pixel methods for RawTexture"""
"""Test getting and setting pixel values"""
color1 = (69, 13, 12, 0) # totally random color
color2 = (19, 142, 93, 44)
@ -33,22 +35,26 @@ class TestRawTexture(unittest.TestCase):
self.assertEqual(self.texture[-1, -1], color2)
self.assertEqual(tuple(data[0:4]), color1)
self.assertEqual(tuple(data[-4:]), color2)
with self.assertRaises(IndexError):
thing = self.texture[self.width, self.height]
with self.assertRaises(IndexError):
thing = self.texture[-1 - self.width, -1 - self.height]
def test_buffer(self):
"""Test the Buffer protocol implementation for RawTexture"""
with tempfile.TemporaryFile('r+b') as fp:
fp.write(self.boilerplate.tobytes('raw', 'RGBX'))
fp.write(self.bp_bytes)
fp.seek(0)
bytes_read = fp.readinto(self.texture)
self.assertEqual(bytes_read, self.texture.size, 'buffer over/underrun')
self.assertEqual(self.boilerplate.tobytes('raw', 'RGBX'), self.texture.tobytes(), 'Incorrect bytes after writing to buffer')
self.assertEqual(bytes(self.texture), self.texture.tobytes(), "Incorrect bytes reading from buffer")
self.assertEqual(bytes_read, self.texture.size, 'buffer over/underrun')
self.assertEqual(self.bp_bytes, self.texture.tobytes(), 'Incorrect bytes after writing to buffer')
self.assertEqual(self.bp_bytes, bytes(self.texture), "Incorrect bytes after reading from buffer")
def test_factory(self):
def test_frombytes(self):
"""Test the frombytes factory function"""
bytetex = quicktex.RawTexture.frombytes(self.boilerplate.tobytes('raw', 'RGBX'), *self.boilerplate.size)
self.assertEqual(self.boilerplate.tobytes('raw', 'RGBX'), bytetex.tobytes(), 'Incorrect bytes after writing to buffer')
bytetex = quicktex.RawTexture.frombytes(self.bp_bytes, *self.boilerplate.size)
self.assertEqual(self.bp_bytes, bytetex.tobytes(), 'Incorrect bytes after writing to buffer')
if __name__ == '__main__':