mirror of
https://github.com/drewcassidy/quicktex.git
synced 2024-09-13 06:37:34 +00:00
Improved subscripting
This commit is contained in:
parent
bd454d9d20
commit
28b541d49a
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
@ -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__':
|
||||
|
Loading…
Reference in New Issue
Block a user