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 BlocksX() const { return _width / B::Width; }
constexpr int BlocksY() const { return _height / B::Height; } 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 { virtual B GetBlock(int x, int y) const {
if (x < 0 || x >= BlocksX()) throw std::invalid_argument("x 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::invalid_argument("y value out of range."); if (y < 0 || y >= BlocksY()) throw std::range_error("y value out of range.");
return _blocks[x + (y * _width)]; return _blocks[x + (y * _width)];
} }
virtual void SetBlock(int x, int y, const B &val) { 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 (x < 0 || x >= BlocksX()) throw std::range_error("x value out of range.");
if (y < 0 || y >= BlocksY()) throw std::invalid_argument("y value out of range."); if (y < 0 || y >= BlocksY()) throw std::range_error("y value out of range.");
_blocks[x + (y * _width)] = val; _blocks[x + (y * _width)] = val;
} }

View File

@ -88,6 +88,8 @@ PYBIND11_MODULE(_quicktex, m) {
py::options options; py::options options;
// Texture
py::class_<Texture> texture(m, "Texture", py::buffer_protocol()); py::class_<Texture> texture(m, "Texture", py::buffer_protocol());
texture.def_property_readonly("size", &Texture::Size); 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_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()); }); 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); py::class_<RawTexture> raw_texture(m, "RawTexture", texture);
raw_texture.def(py::init<int, int>(), "width"_a, "height"_a); 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; DefSubscript2D(raw_texture, &RawTexture::GetPixel, &RawTexture::SetPixel, &RawTexture::Dimensions);
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);
});
raw_texture.def_static("frombytes", &BufferToTexture<RawTexture>, "data"_a, "width"_a, "height"_a); raw_texture.def_static("frombytes", &BufferToTexture<RawTexture>, "data"_a, "width"_a, "height"_a);

View File

@ -25,6 +25,7 @@
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#include <type_traits>
#include "Block.h" #include "Block.h"
#include "Color.h" #include "Color.h"
@ -60,16 +61,41 @@ template <typename T> T BufferToTexture(py::buffer buf, int width, int height) {
return output; return output;
} }
inline int PyIndex(int val, int size) { inline int PyIndex(int val, int size, std::string name = "index") {
if (val < -size) throw std::range_error("index out of range"); if (val < -size || val >= size) throw std::out_of_range(name + " value out of range");
if (val >= size) throw std::range_error("index out of range");
if (val < 0) return size + val; if (val < 0) return size + val;
return val; return val;
} }
inline void PyIndex2D(std::tuple<int, int> pnt, std::tuple<int, int> size, int& x, int& y) { template <typename T, typename Getter, typename Setter, typename Extent> void DefSubscript(py::class_<T> t, Getter&& get, Setter&& set, Extent&& ext) {
x = PyIndex(std::get<0>(pnt), std::get<0>(size)); using V = typename std::invoke_result<Getter, T*, int>::type;
y = PyIndex(std::get<1>(pnt), std::get<1>(size)); 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) { 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_x", &BTex::BlocksX);
block_texture.def_property_readonly("blocks_y", &BTex::BlocksY); 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 } // namespace quicktex::bindings

View File

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