diff --git a/src/skia/Picture.cpp b/src/skia/Picture.cpp index cdfe43a70..3111939dc 100644 --- a/src/skia/Picture.cpp +++ b/src/skia/Picture.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace { @@ -27,11 +28,32 @@ class PyPicture : public SkPicture { class PyBBoxHierarchy : public SkBBoxHierarchy { public: using SkBBoxHierarchy::SkBBoxHierarchy; + + // https://pybind11.readthedocs.io/en/stable/advanced/classes.html#different-method-signatures void insert(const SkRect rects[], int N) override { - PYBIND11_OVERRIDE_PURE(void, SkBBoxHierarchy, insert, rects, N); + pybind11::gil_scoped_acquire gil; + pybind11::function override = pybind11::get_override(this, "insert"); + if (override) { + override(std::vector(rects, rects + N)); + } + } + void insert(const SkRect rects[], const Metadata metadata[], int N) override { + pybind11::gil_scoped_acquire gil; + pybind11::function override = pybind11::get_override(this, "insert"); + if (override) { + override(std::vector(rects, rects + N), + std::vector(metadata, metadata + N)); + } } void search(const SkRect& query, std::vector *results) const override { - PYBIND11_OVERRIDE_PURE(void, SkBBoxHierarchy, search, query, results); + pybind11::gil_scoped_acquire gil; + pybind11::function override = pybind11::get_override(this, "search"); + if (override) { + auto obj = override(query); + if (py::isinstance(obj)) { + *results = obj.cast>(); + } + } } size_t bytesUsed() const override { PYBIND11_OVERRIDE_PURE(size_t, SkBBoxHierarchy, bytesUsed); @@ -289,21 +311,30 @@ py::class_(bboxhierarchy, "Metadata") bboxhierarchy .def(py::init()) .def("insert", - py::overload_cast(&SkBBoxHierarchy::insert), + [] (SkBBoxHierarchy& bbh, const std::vector& rects) { + return bbh.insert(rects.data(), rects.size()); + }, R"docstring( Insert N bounding boxes into the hierarchy. )docstring", - py::arg("rects"), py::arg("N")) + py::arg("rects")) .def("insert", - py::overload_cast(&SkBBoxHierarchy::insert), - py::arg("rects"), py::arg("metadata"), py::arg("N")) - .def("search", &SkBBoxHierarchy::search, + [] (SkBBoxHierarchy& bbh, const std::vector& rects, + const std::vector& metadata) { + return bbh.insert(rects.data(), metadata.data(), rects.size()); + }, + py::arg("rects"), py::arg("metadata")) + .def("search", + [] (SkBBoxHierarchy& bbh, const SkRect& query) { + std::vector results; + bbh.search(query, &results); + return results; + }, R"docstring( Populate results with the indices of bounding boxes intersecting that query. )docstring", - py::arg("query"), py::arg("results")) + py::arg("query")) .def("bytesUsed", &SkBBoxHierarchy::bytesUsed, R"docstring( Return approximate size in memory of this. diff --git a/src/skia/Point.cpp b/src/skia/Point.cpp index 9048f54fa..11b313203 100644 --- a/src/skia/Point.cpp +++ b/src/skia/Point.cpp @@ -1,6 +1,7 @@ #include "common.h" #include #include +#include void initPoint(py::module &m) { // IPoint diff --git a/tests/test_picture.py b/tests/test_picture.py index d1bc88340..b99cb422e 100644 --- a/tests/test_picture.py +++ b/tests/test_picture.py @@ -62,6 +62,58 @@ def test_Picture_MakePlaceholder(): skia.Picture.MakePlaceholder(skia.Rect(100, 100)), skia.Picture) +def test_RTreeFactory_init(): + assert isinstance(skia.RTreeFactory(), skia.RTreeFactory) + + +@pytest.fixture +def factory(): + return skia.RTreeFactory() + + +def test_RTreeFactory_call(factory): + bbh = factory() + assert isinstance(bbh, skia.BBoxHierarchy) + + +def test_BBoxHierarchy_init(): + assert isinstance(skia.BBoxHierarchy(), skia.BBoxHierarchy) + + +def test_BBoxHierarchy_insert(factory): + bbh = factory() + bbh.insert([skia.Rect(100, 100)]) + + +def test_BBoxHierarchy_search(factory): + bbh = factory() + bbh.insert([skia.Rect(100, 100)]) + results = bbh.search(skia.Rect(100, 100)) + assert results[0] == 0 + + +def test_inherit_BBoxHierarchy(): + class TestBBH(skia.BBoxHierarchy): + def __init__(self): + self.rects = [] + super().__init__() + + def search(self, query): + return [i for i, rect in enumerate(self.rects) if rect.intersects(query)] + + def insert(self, rects, metadata=[]): + self.rects.extend(rects) + + def bytesUsed(self): + return 0 + + bbh = TestBBH() + bbh.insert([skia.Rect(100, 100)]) + assert len(bbh.rects) == 1 + results = bbh.search(skia.Rect(100, 100)) + assert results[0] == 0 + + def test_PictureRecorder_init(): assert isinstance(skia.PictureRecorder(), skia.PictureRecorder) @@ -69,6 +121,7 @@ def test_PictureRecorder_init(): @pytest.mark.parametrize('args', [ (skia.Rect(100, 100),), (100, 100), + (skia.Rect(100, 100), skia.RTreeFactory()()), ]) def test_PictureRecorder_beginRecording(recorder, args): canvas = recorder.beginRecording(*args) @@ -126,3 +179,82 @@ def test_Drawable_getBounds(drawable): def test_Drawable_notifyDrawingChanged(drawable): drawable.notifyDrawingChanged() + + +# https://github.com/google/skia/blob/ad08229fd0163a784c60a8bac2c0c5a6a13877c9/tests/PictureTest.cpp#L869-L893 +def test_Picture_fillsBBH(factory, recorder): + rects = [ + skia.Rect(0, 0, 20, 20), + skia.Rect(20, 20, 40, 40), + ] + + for n in range(len(rects) + 1): + bbh = factory() + + canvas = recorder.beginRecording(skia.Rect(0, 0, 100, 100), bbh) + for i in range(n): + canvas.drawRect(rects[i], skia.Paint()) + recorder.finishRecordingAsPicture() + + results = bbh.search(skia.Rect(0, 0, 100, 100)) + assert len(results) == n + + +# https://github.com/google/skia/blob/64148dd7cfe0a3f104d93f58ec42592a0252d378/tests/PictureBBHTest.cpp#L99-L127 +def test_PictureNegativeSpace(factory, recorder): + cull = skia.Rect(-200, -200, 200, 200) + + bbh = factory() + canvas = recorder.beginRecording(cull, bbh) + canvas.save() + canvas.clipRect(cull) + canvas.drawRect(skia.Rect(-20, -20, -10, -10), skia.Paint()) + canvas.drawRect(skia.Rect(-20, -20, -10, -10), skia.Paint()) + canvas.restore() + picture = recorder.finishRecordingAsPicture() + assert picture.approximateOpCount() == 5 + assert picture.cullRect() == skia.Rect(-20, -20, -10, -10) + + bbh = factory() + canvas = recorder.beginRecording(cull, bbh) + canvas.clipRect(cull) + canvas.drawRect(skia.Rect(-20, -20, -10, -10), skia.Paint()) + canvas.drawRect(skia.Rect(-20, -20, -10, -10), skia.Paint()) + picture = recorder.finishRecordingAsPicture() + assert picture.approximateOpCount() == 3 + assert picture.cullRect() == skia.Rect(-20, -20, -10, -10) + + +# https://github.com/google/skia/blob/64148dd7cfe0a3f104d93f58ec42592a0252d378/tests/PictureTest.cpp#L613-L657 +def test_Picture_SkipBBH(recorder): + class CountingBBH(skia.BBoxHierarchy): + def __init__(self): + self.search_calls: int = 0 + super().__init__() + + def search(self, query): + self.search_calls += 1 + + def insert(self, rects, metadata=[]): + pass + + def bytesUsed(self): + return 0 + + bound = skia.Rect(320, 240) + + bbh = CountingBBH() + + canvas = recorder.beginRecording(bound, bbh) + canvas.drawRect(bound, skia.Paint()) + canvas.drawRect(bound, skia.Paint()) + picture = recorder.finishRecordingAsPicture() + + big = skia.Canvas(640, 480) + small = skia.Canvas(300, 200) + + picture.playback(big) + assert bbh.search_calls == 0 + + picture.playback(small) + assert bbh.search_calls == 1 diff --git a/tests/test_point.py b/tests/test_point.py index e1f010eb0..6efcab49b 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -105,6 +105,13 @@ def test_Point_init(args): assert isinstance(skia.Point(*args), skia.Point) +def test_Point_Offset(): + points = [skia.Point(1, 2), skia.Point(3, 4)] + points = skia.Point.Offset(points, 1, 1) + assert points[0].equals(2, 3) + assert points[1].equals(4, 5) + + def test_Point_x(point): assert point.x() == 4