From 1cef1429c73b70ffc3fe927133103741f39a65b4 Mon Sep 17 00:00:00 2001 From: Kota Yamaguchi Date: Mon, 20 Apr 2020 18:58:53 +0900 Subject: [PATCH 1/2] Add PathEffect binding --- setup.py | 1 + src/skia/Paint.cpp | 82 +++++++-- src/skia/Path.cpp | 17 ++ src/skia/PathEffect.cpp | 365 +++++++++++++++++++++++++++++++++++++++ src/skia/main.cpp | 18 ++ tests/test_patheffect.py | 125 ++++++++++++++ 6 files changed, 590 insertions(+), 18 deletions(-) create mode 100644 src/skia/PathEffect.cpp create mode 100644 tests/test_patheffect.py diff --git a/setup.py b/setup.py index b6aa058bb..ec52fa0c5 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ os.path.join('src', 'skia', 'Matrix.cpp'), os.path.join('src', 'skia', 'Paint.cpp'), os.path.join('src', 'skia', 'Path.cpp'), + os.path.join('src', 'skia', 'PathEffect.cpp'), os.path.join('src', 'skia', 'Picture.cpp'), os.path.join('src', 'skia', 'Pixmap.cpp'), os.path.join('src', 'skia', 'Point.cpp'), diff --git a/src/skia/Paint.cpp b/src/skia/Paint.cpp index 2cb379aae..0022227fa 100644 --- a/src/skia/Paint.cpp +++ b/src/skia/Paint.cpp @@ -6,6 +6,25 @@ const int SkPaint::kStyleCount; const int SkPaint::kCapCount; const int SkPaint::kJoinCount; + +class PyFlattanable : public SkFlattenable { + using SkFlattenable::SkFlattenable; + // Factory getFactory() const override { + // PYBIND11_OVERLOAD_PURE( + // Factory, + // SkFlattenable, + // getFactory, + // ); + // } + const char* getTypeName() const override { + PYBIND11_OVERLOAD_PURE(const char*, SkFlattenable, getTypeName); + } + Type getFlattenableType() const override { + PYBIND11_OVERLOAD_PURE(Type, SkFlattenable, getFlattenableType); + } +}; + + void initPaint(py::module &m) { // Paint py::class_ paint(m, "Paint", R"docstring( @@ -189,9 +208,53 @@ paint "Compares a and b, and returns true if a and b are not equivalent.") ; +py::class_> flattanable( + m, "Flattanable", + R"docstring( + :py:class:`Flattenable` is the base class for objects that need to be + flattened into a data stream for either transport or as part of the key to + the font cache. + )docstring"); + +py::enum_(flattanable, "Type") + .value("kSkColorFilter", SkShader::Type::kSkColorFilter_Type) + .value("kSkDrawable", SkShader::Type::kSkDrawable_Type) + .value("kSkDrawLooper", SkShader::Type::kSkDrawLooper_Type) + .value("kSkImageFilter", SkShader::Type::kSkImageFilter_Type) + .value("kSkMaskFilter", SkShader::Type::kSkMaskFilter_Type) + .value("kSkPathEffect", SkShader::Type::kSkPathEffect_Type) + .value("kSkPixelRef", SkShader::Type::kSkPixelRef_Type) + .value("kSkUnused4", SkShader::Type::kSkUnused_Type4) + .value("kSkShaderBase", SkShader::Type::kSkShaderBase_Type) + .value("kSkUnused", SkShader::Type::kSkUnused_Type) + .value("kSkUnused2", SkShader::Type::kSkUnused_Type2) + .value("kSkUnused3", SkShader::Type::kSkUnused_Type3) + .export_values(); + +flattanable + // .def("getFactory", &SkFlattenable::getFactory) + .def("getTypeName", &SkFlattenable::getTypeName, + R"docstring( + Returns the name of the object's class. + + Implemented in :py:class:`~skia.Drawable`. + )docstring") + // .def("flatten", &SkFlattenable::flatten) + .def("getFlattenableType", &SkFlattenable::getFlattenableType) + // .def("serialize", &SkFlattenable::serialize) + .def("unique", &SkFlattenable::unique) + .def("ref", &SkFlattenable::ref) + .def("unref", &SkFlattenable::unref) + // .def_static("NameToFactory", &SkFlattenable::NameToFactory) + // .def_static("FactoryToName", &SkFlattenable::FactoryToName) + // .def_static("Register", &SkFlattenable::Register) + // .def_static("Deserialize", &SkFlattenable::Deserialize) + ; + // Shader // TODO: Need a wrapper class for pure virtual functions. -py::class_> shader(m, "Shader", R"docstring( +py::class_, SkFlattenable> shader( + m, "Shader", R"docstring( Shaders specify the source color(s) for what is being drawn. If a paint has no shader, then the paint's color is used. If the paint has a @@ -224,21 +287,6 @@ py::enum_(shader, "GradientType", R"docstring( .value("kLast", SkShader::GradientType::kLast_GradientType) .export_values(); -py::enum_(shader, "Type") - .value("kSkColorFilter", SkShader::Type::kSkColorFilter_Type) - .value("kSkDrawable", SkShader::Type::kSkDrawable_Type) - .value("kSkDrawLooper", SkShader::Type::kSkDrawLooper_Type) - .value("kSkImageFilter", SkShader::Type::kSkImageFilter_Type) - .value("kSkMaskFilter", SkShader::Type::kSkMaskFilter_Type) - .value("kSkPathEffect", SkShader::Type::kSkPathEffect_Type) - .value("kSkPixelRef", SkShader::Type::kSkPixelRef_Type) - .value("kSkUnused4", SkShader::Type::kSkUnused_Type4) - .value("kSkShaderBase", SkShader::Type::kSkShaderBase_Type) - .value("kSkUnused", SkShader::Type::kSkUnused_Type) - .value("kSkUnused2", SkShader::Type::kSkUnused_Type2) - // .value("kSkUnused3", SkShader::Type::kSkUnused_Type3) - .export_values(); - shader .def("isOpaque", &SkShader::isOpaque, "Returns true if the shader is guaranteed to produce only opaque " @@ -282,8 +330,6 @@ shader ; // ColorFilter py::class_>(m, "ColorFilter"); -// PathEffect -py::class_>(m, "PathEffect"); // MaskFilter py::class_>(m, "MaskFilter"); // ImageFilter diff --git a/src/skia/Path.cpp b/src/skia/Path.cpp index 97b456247..9ebc801bc 100644 --- a/src/skia/Path.cpp +++ b/src/skia/Path.cpp @@ -81,6 +81,23 @@ m.def("PathFillType_IsEvenOdd", &SkPathFillType_IsEvenOdd); m.def("PathFillType_IsInverse", &SkPathFillType_IsInverse); m.def("PathFillType_ConvertToNonInverse", &SkPathFillType_ConvertToNonInverse); + +py::enum_(m, "PathOp", R"docstring( + The logical operations that can be performed when combining two paths. + )docstring") + .value("kDifference", SkPathOp::kDifference_SkPathOp, + "subtract the op path from the first path") + .value("kIntersect", SkPathOp::kIntersect_SkPathOp, + "intersect the two paths") + .value("kUnion", SkPathOp::kUnion_SkPathOp, + "union (inclusive-or) the two paths") + .value("kXOR", SkPathOp::kXOR_SkPathOp, + "exclusive-or the two paths") + .value("kReverseDifference", SkPathOp::kReverseDifference_SkPathOp, + "subtract the first path from the op path") + .export_values(); + + // Path py::class_ path(m, "Path", R"docstring( :py:class:`Path` contain geometry. diff --git a/src/skia/PathEffect.cpp b/src/skia/PathEffect.cpp new file mode 100644 index 000000000..f984a40de --- /dev/null +++ b/src/skia/PathEffect.cpp @@ -0,0 +1,365 @@ +#include "common.h" +#include +#include "include/effects/SkOpPathEffect.h" +#include "include/effects/SkTrimPathEffect.h" + + +// Wrap DashInfo for bare pointer used for fIntervals. +struct PyDashInfo : public SkPathEffect::DashInfo { + std::vector intervals_; +}; + +// Wrap PointData for bare pointer used for fPoints. +class PyPointData : public SkPathEffect::PointData { +public: + PyPointData() : SkPathEffect::PointData::PointData() { + this->fPoints = &this->points_[0]; + } + + std::vector points_; +}; + + +void initPathEffect(py::module &m) { +py::class_, SkFlattenable> patheffect( + m, "PathEffect", + R"docstring( + :py:class:`PathEffect` is the base class for objects in the + :py:class:`Paint` that affect the geometry of a drawing primitive before it + is transformed by the canvas' matrix and drawn. + + Dashing is implemented as a subclass of :py:class:`PathEffect`. + + .. rubric:: Classes + + .. autosummary:: + :nosignatures: + + ~PathEffect.DashInfo + ~PathEffect.PointData + )docstring"); + +py::class_(patheffect, "DashInfo") + .def(py::init<>()) + .def(py::init([] (const std::vector& intervals, SkScalar phase) { + auto info = PyDashInfo(); + info.intervals_.assign(intervals.begin(), intervals.end()); + info.fIntervals = (info.intervals_.empty()) ? + nullptr : &info.intervals_[0]; + info.fCount = info.intervals_.size(); + info.fPhase = phase; + return info; + })) + .def_property("fIntervals", + [] (const PyDashInfo& info) { return info.intervals_; }, + [] (PyDashInfo& info, const std::vector& intervals) { + info.intervals_.assign(intervals.begin(), intervals.end()); + info.fIntervals = (info.intervals_.empty()) ? + nullptr : &info.intervals_[0]; + info.fCount = info.intervals_.size(); + }, + R"docstring( + Length of on/off intervals for dashed lines. + )docstring") + .def_readonly("fCount", &PyDashInfo::fCount, + R"docstring( + Number of intervals in the dash. Should be even number. + )docstring") + .def_readwrite("fPhase", &PyDashInfo::fPhase, + R"docstring( + Offset into the dashed interval pattern. + )docstring") + ; + +py::class_ pointdata(patheffect, "PointData", R"docstring( + :py:class:`PointData` aggregates all the information needed to draw the + point primitives returned by an asPoints call. + + .. rubric:: Classes + + .. autosummary:: + :nosignatures: + + ~skia.PathEffect.PointData.PointFlags + )docstring"); + +py::enum_( + pointdata, "PointFlags", py::arithmetic()) + .value("kCircles", SkPathEffect::PointData::PointFlags::kCircles_PointFlag) + .value("kUsePath", SkPathEffect::PointData::PointFlags::kUsePath_PointFlag) + .value("kUseClip", SkPathEffect::PointData::PointFlags::kUseClip_PointFlag) + .export_values(); + +pointdata + .def(py::init<>()) + .def_readwrite("fFlags", &PyPointData::fFlags) + .def_property("fPoints", + [] (const PyPointData& data) { return data.points_; }, + [] (PyPointData* data, const std::vector& points) { + data->points_.assign(points.begin(), points.end()); + data->fNumPoints = data->points_.size(); + }) + .def_readonly("fNumPoints", &PyPointData::fNumPoints) + .def_readwrite("fSize", &PyPointData::fSize) + .def_readwrite("fClipRect", &PyPointData::fClipRect) + .def_readwrite("fPath", &PyPointData::fPath) + .def_readwrite("fFirst", &PyPointData::fFirst) + .def_readwrite("fLast", &PyPointData::fLast) + ; + +py::enum_(patheffect, "DashType", + R"docstring( + If the :py:class:`PathEffect` can be represented as a dash pattern, asADash + will return kDash_DashType and None otherwise. + + If a non NULL info is passed in, the various + :py:class:`~PathEffect.DashInfo` will be filled in if the + :py:class:`PathEffect` can be a dash pattern. If passed in info has an + fCount equal or greater to that of the effect, it will memcpy the values of + the dash intervals into the info. Thus the general approach will be call + asADash once with default info to get DashType and fCount. If effect can be + represented as a dash pattern, allocate space for the intervals in info, + then call asADash again with the same info and the intervals will get copied + in. + )docstring") + .value("kNone", SkPathEffect::DashType::kNone_DashType, + "ignores the info parameter") + .value("kDash", SkPathEffect::DashType::kDash_DashType, + "fills in all of the info parameter") + .export_values(); + +patheffect + .def("filterPath", &SkPathEffect::filterPath, + R"docstring( + Given a src path (input) and a stroke-rec (input and output), apply this + effect to the src path, returning the new path in dst, and return true. + + If this effect cannot be applied, return false and ignore dst and + stroke-rec. + + The stroke-rec specifies the initial request for stroking (if any). The + effect can treat this as input only, or it can choose to change the rec + as well. For example, the effect can decide to change the stroke's width + or join, or the effect can change the rec from stroke to fill (or fill + to stroke) in addition to returning a new (dst) path. + + If this method returns true, the caller will apply (as needed) the + resulting stroke-rec to dst and then draw. + )docstring", + py::arg("dst"), py::arg("dst"), py::arg("stroke_rec"), py::arg("cullR")) + .def("computeFastBounds", &SkPathEffect::computeFastBounds, + R"docstring( + Compute a conservative bounds for its effect, given the src bounds. + + The baseline implementation just assigns src to dst. + )docstring", + py::arg("dst"), py::arg("src")) + .def("asPoints", &SkPathEffect::asPoints, + R"docstring( + Does applying this path effect to 'src' yield a set of points? If so, + optionally return the points in 'results'. + )docstring", + py::arg("results"), py::arg("src"), py::arg("stroke_rec"), + py::arg("matrix"), py::arg("cullR")) + .def("asADash", &SkPathEffect::asADash, py::arg("info")) + .def_static("MakeSum", + [] (const SkPathEffect& first, const SkPathEffect& second) { + auto first_ = first.serialize(); + auto second_ = second.serialize(); + return SkPathEffect::MakeSum( + SkPathEffect::Deserialize(first_->data(), first_->size()), + SkPathEffect::Deserialize(second_->data(), second_->size())); + }, + R"docstring( + Returns a patheffect that apples each effect (first and second) to the + original path, and returns a path with the sum of these. + + result = first(path) + second(path) + )docstring") + .def_static("MakeCompose", + [] (const SkPathEffect& outer, const SkPathEffect& inner) { + auto outer_ = outer.serialize(); + auto inner_ = inner.serialize(); + return SkPathEffect::MakeCompose( + SkPathEffect::Deserialize(outer_->data(), outer_->size()), + SkPathEffect::Deserialize(inner_->data(), inner_->size())); + }, + R"docstring( + Returns a patheffect that applies the inner effect to the path, and then + applies the outer effect to the result of the inner's. + + result = outer(inner(path)) + )docstring") + .def_static("RegisterFlattenables", &SkPathEffect::RegisterFlattenables) + .def_static("GetFlattenableType", &SkPathEffect::GetFlattenableType) + .def_static("Deserialize", &SkPathEffect::Deserialize) + ; + +py::class_>( + m, "DiscretePathEffect") + .def_static("Make", &SkDiscretePathEffect::Make, + R"docstring( + Break the path into segments of segLength length, and randomly move the + endpoints away from the original path by a maximum of deviation. + + Note: works on filled or framed paths + + :param segLength: Segment length + :param dev: Deviation + :param seedAssist: This is a caller-supplied seedAssist that modifies + the seed value that is used to randomize the path segments' + endpoints. If not supplied it defaults to 0, in which case filtering + a path multiple times will result in the same set of segments (this + is useful for testing). If a caller does not want this behaviour + they can pass in a different seedAssist to get a different set of + path segments. + )docstring", + py::arg("segLength"), py::arg("dev"), py::arg("seedAssist") = 0) + ; + +py::class_(m, "DashPathEffect") + .def_static("Make", + [] (const std::vector& intervals, SkScalar phase) { + return SkDashPathEffect::Make( + &intervals[0], intervals.size(), phase); + }, + R"docstring( + For example: if intervals[] = {10, 20}, count = 2, and phase = 25, this + will set up a dashed path like so: 5 pixels off 10 pixels on 20 pixels + off 10 pixels on 20 pixels off ... A phase of -5, 25, 55, 85, etc. would + all result in the same path, because the sum of all the intervals is 30. + + Note: only affects stroked paths. + + :param List[skia.Point] intervals: array containing an even number of + entries (>=2), with the even indices specifying the length of "on" + intervals, and the odd indices specifying the length of "off" + intervals. + :param float phase: offset into the intervals array (mod the sum of all + of the intervals). + )docstring", + py::arg("intervals"), py::arg("phase")) + ; + +py::class_>( + m, "CornerPathEffect", + R"docstring( + :py:class:`CornerPathEffect` is a subclass of :py:class:`PathEffect` that + can turn sharp corners into various treatments (e.g. rounded corners) + )docstring") + .def_static("Make", &SkCornerPathEffect::Make, + R"docstring( + radius must be > 0 to have an effect. + + It specifies the distance from each corner that should be "rounded". + )docstring", + py::arg("radius")) + ; + +py::class_> + path1dpatheffect(m, "Path1DPathEffect", + R"docstring( + .. rubric:: Classes + + .. autosummary:: + :nosignatures: + + ~Path1DPathEffect.Style + )docstring"); + +py::enum_(path1dpatheffect, "Style") + .value("kTranslate", SkPath1DPathEffect::Style::kTranslate_Style) + .value("kRotate", SkPath1DPathEffect::Style::kRotate_Style) + .value("kMorph", SkPath1DPathEffect::Style::kMorph_Style) + .value("kLastEnum", SkPath1DPathEffect::Style::kLastEnum_Style) + .export_values(); + +path1dpatheffect + .def_static("Make", + &SkPath1DPathEffect::Make, + R"docstring( + Dash by replicating the specified path. + + :param skia.Path path: The path to replicate (dash) + :param float advance: The space between instances of path + :param float phase: distance (mod advance) along path for its initial + position + :param skia.Path1DPathEffect.Style style: how to transform path at each + point (based on the current position and tangent) + )docstring", + py::arg("path"), py::arg("advance"), py::arg("phase"), py::arg("style")) + ; + +py::class_>( + m, "Path2DPathEffect") + .def_static("Make", &SkPath2DPathEffect::Make, + R"docstring( + Stamp the specified path to fill the shape, using the matrix to define + the latice. + )docstring", + py::arg("matrix"), py::arg("path")) + ; + +py::class_(m, "MergePathEffect") + .def_static("Make", + [] (const SkPathEffect& one, const SkPathEffect& two, SkPathOp op) { + auto one_ = one.serialize(); + auto two_ = two.serialize(); + return SkMergePathEffect::Make( + SkPathEffect::Deserialize(one_->data(), one_->size()), + SkPathEffect::Deserialize(two_->data(), two_->size()), + op); + }, + py::arg("one"), py::arg("two"), py::arg("op")) + ; + +py::class_(m, "MatrixPathEffect") + .def_static("Make", &SkMatrixPathEffect::Make, py::arg("matrix")) + .def_static("MakeTranslate", &SkMatrixPathEffect::MakeTranslate, + py::arg("dx"), py::arg("dy")) + ; + +py::class_(m, "StrokePathEffect") + .def_static("Make", &SkStrokePathEffect::Make, + py::arg("width"), py::arg("join"), py::arg("cap"), py::arg("miter") = 4) + ; + +py::class_ trimpatheffect(m, "TrimPathEffect", + R"docstring( + .. rubric:: Classes + + .. autosummary:: + :nosignatures: + + ~TrimPathEffect.Mode + )docstring"); + +py::enum_(trimpatheffect, "Mode") + .value("kNormal", SkTrimPathEffect::Mode::kNormal) + .value("kInverted", SkTrimPathEffect::Mode::kInverted) + .export_values(); + +trimpatheffect + .def_static("Make", &SkTrimPathEffect::Make, + R"docstring( + Take start and stop "t" values (values between 0...1), and return a path + that is that subset of the original path. + + e.g. Make(0.5, 1.0) –> return the 2nd half of the path Make(0.33333, + 0.66667) –> return the middle third of the path + + The trim values apply to the entire path, so if it contains several + contours, all of them are including in the calculation. + + startT and stopT must be 0..1 inclusive. If they are outside of that + interval, they will be pinned to the nearest legal value. If either is + NaN, null will be returned. + + Note: for Mode::kNormal, this will return one (logical) segment (even if + it is spread across multiple contours). For Mode::kInverted, this will + return 2 logical segments: stopT..1 and 0...startT, in this order. + )docstring", + py::arg("startT"), py::arg("stopT"), + py::arg("mode") = SkTrimPathEffect::Mode::kNormal) + ; +} diff --git a/src/skia/main.cpp b/src/skia/main.cpp index 892b61fd1..e8c974379 100644 --- a/src/skia/main.cpp +++ b/src/skia/main.cpp @@ -16,6 +16,7 @@ void initImageInfo(py::module &); void initMatrix(py::module &); void initPaint(py::module &); void initPath(py::module &); +void initPathEffect(py::module &); void initPicture(py::module &); void initPixmap(py::module &); void initPoint(py::module &); @@ -64,10 +65,14 @@ PYBIND11_MODULE(skia, m) { ColorInfo ColorSpace ColorType + CornerPathEffect + DashPathEffect Data DeserialProcs + DiscretePathEffect EncodedImageFormat FilterQuality + Flattanable Font FontArguments FontHinting @@ -96,6 +101,8 @@ PYBIND11_MODULE(skia, m) { M44 MaskFilter Matrix + MatrixPathEffect + MergePathEffect Paint Path Path.Iter @@ -107,6 +114,13 @@ PYBIND11_MODULE(skia, m) { PathConvexityType PathDirection PathEffect + PathEffect.DashInfo + PathEffect.DashType + PathEffect.PointData + PathEffect.PointData.PointFlags + Path1DPathEffect + Path1DPathEffect.Style + Path2DPathEffect PathFillType PathSegmentMask PathVerb @@ -122,6 +136,7 @@ PYBIND11_MODULE(skia, m) { Region Shader Size + StrokePathEffect Surface Surface.AsyncReadResult Surface.ContentChangeMode @@ -137,6 +152,8 @@ PYBIND11_MODULE(skia, m) { TextBlobBuilder TextEncoding TileMode + TrimPathEffect + TrimPathEffect.Mode Typeface Vertices YUVColorSpace @@ -159,6 +176,7 @@ PYBIND11_MODULE(skia, m) { initImageInfo(m); initPaint(m); initPath(m); + initPathEffect(m); initPicture(m); initPixmap(m); initTextBlob(m); diff --git a/tests/test_patheffect.py b/tests/test_patheffect.py new file mode 100644 index 000000000..375f317b9 --- /dev/null +++ b/tests/test_patheffect.py @@ -0,0 +1,125 @@ +import skia +import pytest + + +@pytest.mark.parametrize('args', [ + tuple(), + ([1.0, 2.0,], 1.0), +]) +def test_PathEffect_DashInfo_init(args): + assert isinstance(skia.PathEffect.DashInfo(*args), skia.PathEffect.DashInfo) + + +@pytest.fixture +def patheffect_dashinfo(): + return skia.PathEffect.DashInfo() + + +def test_PathEffect_DashInfo_fIntervals(patheffect_dashinfo): + patheffect_dashinfo.fIntervals = [1.0, 2.0] + assert isinstance(patheffect_dashinfo.fIntervals, list) + assert patheffect_dashinfo.fCount == 2 + + +def test_PathEffect_DashInfo_fPhase(patheffect_dashinfo): + patheffect_dashinfo.fPhase = 2.0 + assert patheffect_dashinfo.fPhase == 2.0 + + +def test_PathEffect_PointData_init(): + assert isinstance(skia.PathEffect.PointData(), skia.PathEffect.PointData) + + +@pytest.fixture +def patheffect_pointdata(): + return skia.PathEffect.PointData() + + +def test_PathEffect_PointData_fFlags(patheffect_pointdata): + patheffect_pointdata.fFlags = skia.PathEffect.PointData.PointFlags.kCircles + assert (patheffect_pointdata.fFlags == + skia.PathEffect.PointData.PointFlags.kCircles) + + +def test_PathEffect_PointData_fPoints(patheffect_pointdata): + patheffect_pointdata.fPoints = [skia.Point(0, 0)] + assert isinstance(patheffect_pointdata.fPoints, list) + assert patheffect_pointdata.fPoints[0] == skia.Point(0, 0) + assert patheffect_pointdata.fNumPoints == 1 + + +def test_PathEffect_PointData_fSize(patheffect_pointdata): + assert isinstance(patheffect_pointdata.fSize, skia.Point) + + +def test_PathEffect_PointData_fClipRect(patheffect_pointdata): + assert isinstance(patheffect_pointdata.fClipRect, skia.Rect) + + +def test_PathEffect_PointData_fPath(patheffect_pointdata): + assert isinstance(patheffect_pointdata.fPath, skia.Path) + + +def test_PathEffect_PointData_fFirst(patheffect_pointdata): + assert isinstance(patheffect_pointdata.fFirst, skia.Path) + + +def test_PathEffect_PointData_fLast(patheffect_pointdata): + assert isinstance(patheffect_pointdata.fLast, skia.Path) + + +def test_DiscretePathEffect_Make(): + assert isinstance(skia.DiscretePathEffect.Make(4.0, 1.0), skia.PathEffect) + + +def test_DashPathEffect_Make(): + assert isinstance(skia.DashPathEffect.Make([2., 1.], 0), skia.PathEffect) + + +def test_CornerPathEffect_Make(): + assert isinstance(skia.CornerPathEffect.Make(4.0), skia.PathEffect) + + +def test_Path1DPathEffect_Make(): + path = skia.Path() + path.addCircle(10, 10, 5) + assert isinstance( + skia.Path1DPathEffect.Make( + path, 1., 0., skia.Path1DPathEffect.Style.kTranslate), + skia.PathEffect) + + +def test_Path2DPathEffect_Make(): + path = skia.Path() + path.addCircle(10, 10, 5) + assert isinstance( + skia.Path2DPathEffect.Make(skia.Matrix(), path), skia.PathEffect) + + +def test_MergePathEffect_Make(): + assert isinstance( + skia.MergePathEffect.Make( + skia.CornerPathEffect.Make(4.0), + skia.DiscretePathEffect.Make(4.0, 1.0), + skia.PathOp.kUnion), skia.PathEffect) + + +def test_MatrixPathEffect_Make(): + assert isinstance( + skia.MatrixPathEffect.Make(skia.Matrix()), skia.PathEffect) + + +def test_MatrixPathEffect_MakeTranslate(): + assert isinstance( + skia.MatrixPathEffect.MakeTranslate(1., 1.), skia.PathEffect) + + +def test_StrokePathEffect_Make(): + assert isinstance( + skia.StrokePathEffect.Make( + 3, skia.Paint.Join.kMiter, skia.Paint.Cap.kButt), skia.PathEffect) + + +def test_TrimPathEffect_Make(): + assert isinstance( + skia.TrimPathEffect.Make(0.5, 1.0), skia.PathEffect) From 70d9d2b0617ecfb98a8d7f9dd4f1ac0d4a1bd9da Mon Sep 17 00:00:00 2001 From: Kota Yamaguchi Date: Tue, 21 Apr 2020 11:43:50 +0900 Subject: [PATCH 2/2] Add PathEffect and StrokeRec bindings --- docs/index.rst | 2 +- docs/reference.rst | 130 ++++++++++++++++++++++++++- src/skia/Paint.cpp | 50 ++++++----- src/skia/PathEffect.cpp | 186 +++++++++++++++++++++++++++++---------- src/skia/main.cpp | 128 +-------------------------- tests/test_patheffect.py | 167 +++++++++++++++++++++++++++++++---- 6 files changed, 446 insertions(+), 217 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index f21072c28..b6dd7eea6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -31,7 +31,7 @@ Documentation ------------- .. toctree:: - :maxdepth: 3 + :maxdepth: 2 build usage diff --git a/docs/reference.rst b/docs/reference.rst index 9069b4380..fe6719928 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -1,4 +1,132 @@ Reference ---------- +========= .. automodule:: skia + +.. currentmodule:: skia + +.. rubric:: Classes + +.. autosummary:: + :toctree: _generate + :nosignatures: + :template: class.rst + + .. rubric:: Classes + + AlphaType + ApplyPerspectiveClip + AutoCanvasRestore + BBoxHierarchy + BackingFit + Bitmap + BlendMode + BlendModeCoeff + Budgeted + Canvas + Canvas.SaveLayerFlags + Canvas.SrcRectConstraint + Canvas.PointMode + Canvas.QuadAAFlags + Canvas.SaveLayerRec + Canvas.Lattice + Canvas.Lattice.RectType + ClipOp + Color4f + ColorFilter + ColorInfo + ColorSpace + ColorType + CornerPathEffect + DashPathEffect + Data + DeserialProcs + DiscretePathEffect + EncodedImageFormat + FilterQuality + Flattanable + Font + FontArguments + FontHinting + FontMetrics + FontMgr + FontStyle + FontStyleSet + GrBackendApi + GrBackendSemaphore + GrBackendTexture + GrContext + GrFlushFlags + GrGLBackendState + GrGLInterface + GrMipMapped + GrProtected + GrRenderable + GrSemaphoresSubmitted + GrSurfaceOrigin + IPoint + IRect + ISize + Image + ImageFilter + ImageInfo + M44 + MaskFilter + Matrix + MatrixPathEffect + MergePathEffect + Paint + Path + Path.Iter + Path.RawIter + Path.ArcSize + Path.AddPathMode + Path.SegmentMask + Path.Verb + PathConvexityType + PathDirection + PathEffect + PathEffect.DashInfo + PathEffect.DashType + PathEffect.PointData + PathEffect.PointData.PointFlags + Path1DPathEffect + Path1DPathEffect.Style + Path2DPathEffect + PathFillType + PathSegmentMask + PathVerb + Picture + PictureRecorder + PixelGeometry + Pixmap + Point + Point3 + RRect + RSXform + Rect + Region + Shader + Size + StrokePathEffect + StrokeRec + Surface + Surface.AsyncReadResult + Surface.ContentChangeMode + Surface.BackendHandleAccess + Surface.RescaleGamma + Surface.BackendSurfaceAccess + Surface.FlushFlags + SurfaceCharacterization + SurfaceProps + SurfaceProps.Flags + SurfaceProps.InitType + TextBlob + TextBlobBuilder + TextEncoding + TileMode + TrimPathEffect + TrimPathEffect.Mode + Typeface + Vertices + YUVColorSpace diff --git a/src/skia/Paint.cpp b/src/skia/Paint.cpp index 0022227fa..b481ed97d 100644 --- a/src/skia/Paint.cpp +++ b/src/skia/Paint.cpp @@ -9,13 +9,13 @@ const int SkPaint::kJoinCount; class PyFlattanable : public SkFlattenable { using SkFlattenable::SkFlattenable; - // Factory getFactory() const override { - // PYBIND11_OVERLOAD_PURE( - // Factory, - // SkFlattenable, - // getFactory, - // ); - // } + Factory getFactory() const override { + PYBIND11_OVERLOAD_PURE( + Factory, + SkFlattenable, + getFactory, + ); + } const char* getTypeName() const override { PYBIND11_OVERLOAD_PURE(const char*, SkFlattenable, getTypeName); } @@ -217,18 +217,18 @@ py::class_> flattanable( )docstring"); py::enum_(flattanable, "Type") - .value("kSkColorFilter", SkShader::Type::kSkColorFilter_Type) - .value("kSkDrawable", SkShader::Type::kSkDrawable_Type) - .value("kSkDrawLooper", SkShader::Type::kSkDrawLooper_Type) - .value("kSkImageFilter", SkShader::Type::kSkImageFilter_Type) - .value("kSkMaskFilter", SkShader::Type::kSkMaskFilter_Type) - .value("kSkPathEffect", SkShader::Type::kSkPathEffect_Type) - .value("kSkPixelRef", SkShader::Type::kSkPixelRef_Type) - .value("kSkUnused4", SkShader::Type::kSkUnused_Type4) - .value("kSkShaderBase", SkShader::Type::kSkShaderBase_Type) - .value("kSkUnused", SkShader::Type::kSkUnused_Type) - .value("kSkUnused2", SkShader::Type::kSkUnused_Type2) - .value("kSkUnused3", SkShader::Type::kSkUnused_Type3) + .value("kColorFilter", SkFlattenable::Type::kSkColorFilter_Type) + .value("kDrawable", SkFlattenable::Type::kSkDrawable_Type) + .value("kDrawLooper", SkFlattenable::Type::kSkDrawLooper_Type) + .value("kImageFilter", SkFlattenable::Type::kSkImageFilter_Type) + .value("kMaskFilter", SkFlattenable::Type::kSkMaskFilter_Type) + .value("kPathEffect", SkFlattenable::Type::kSkPathEffect_Type) + .value("kPixelRef", SkFlattenable::Type::kSkPixelRef_Type) + .value("kUnused4", SkFlattenable::Type::kSkUnused_Type4) + .value("kShaderBase", SkFlattenable::Type::kSkShaderBase_Type) + .value("kUnused", SkFlattenable::Type::kSkUnused_Type) + .value("kUnused2", SkFlattenable::Type::kSkUnused_Type2) + .value("kUnused3", SkFlattenable::Type::kSkUnused_Type3) .export_values(); flattanable @@ -241,14 +241,22 @@ flattanable )docstring") // .def("flatten", &SkFlattenable::flatten) .def("getFlattenableType", &SkFlattenable::getFlattenableType) - // .def("serialize", &SkFlattenable::serialize) + .def("serialize", + [] (const SkFlattenable& flattanable) { + return flattanable.serialize(); + }) .def("unique", &SkFlattenable::unique) .def("ref", &SkFlattenable::ref) .def("unref", &SkFlattenable::unref) // .def_static("NameToFactory", &SkFlattenable::NameToFactory) // .def_static("FactoryToName", &SkFlattenable::FactoryToName) // .def_static("Register", &SkFlattenable::Register) - // .def_static("Deserialize", &SkFlattenable::Deserialize) + .def_static("Deserialize", + [] (SkFlattenable::Type type, py::buffer b) { + auto info = b.request(); + size_t size = (info.ndim) ? info.shape[0] * info.strides[0] : 0; + return SkFlattenable::Deserialize(type, info.ptr, size); + }) ; // Shader diff --git a/src/skia/PathEffect.cpp b/src/skia/PathEffect.cpp index f984a40de..d751f7a65 100644 --- a/src/skia/PathEffect.cpp +++ b/src/skia/PathEffect.cpp @@ -4,25 +4,121 @@ #include "include/effects/SkTrimPathEffect.h" -// Wrap DashInfo for bare pointer used for fIntervals. -struct PyDashInfo : public SkPathEffect::DashInfo { - std::vector intervals_; -}; +const int SkStrokeRec::kStyleCount; -// Wrap PointData for bare pointer used for fPoints. -class PyPointData : public SkPathEffect::PointData { -public: - PyPointData() : SkPathEffect::PointData::PointData() { - this->fPoints = &this->points_[0]; - } - std::vector points_; -}; +void initPathEffect(py::module &m) { +// StrokeRec +py::class_ strokerec(m, "StrokeRec", + R"docstring( + .. rubric:: Classes -void initPathEffect(py::module &m) { -py::class_, SkFlattenable> patheffect( - m, "PathEffect", + .. autosummary:: + :nosignatures: + + ~skia.StrokeRec.InitStyle + ~skia.StrokeRec.Style + )docstring"); + +py::enum_(strokerec, "InitStyle") + .value("kHairline", SkStrokeRec::InitStyle::kHairline_InitStyle) + .value("kFill", SkStrokeRec::InitStyle::kFill_InitStyle) + .export_values(); + +py::enum_(strokerec, "Style") + .value("kHairline", SkStrokeRec::Style::kHairline_Style) + .value("kFill", SkStrokeRec::Style::kFill_Style) + .value("kStroke", SkStrokeRec::Style::kStroke_Style) + .value("kStrokeAndFill", SkStrokeRec::Style::kStrokeAndFill_Style) + .export_values(); + +strokerec + .def(py::init(), py::arg("style")) + .def(py::init(), + py::arg("paint"), py::arg("style"), py::arg("resScale") = 1) + .def(py::init(), + py::arg("paint"), py::arg("resScale") = 1) + .def("getStyle", &SkStrokeRec::getStyle) + .def("getWidth", &SkStrokeRec::getWidth) + .def("getMiter", &SkStrokeRec::getMiter) + .def("getCap", &SkStrokeRec::getCap) + .def("getJoin", &SkStrokeRec::getJoin) + .def("isHairlineStyle", &SkStrokeRec::isHairlineStyle) + .def("isFillStyle", &SkStrokeRec::isFillStyle) + .def("setFillStyle", &SkStrokeRec::setFillStyle) + .def("setHairlineStyle", &SkStrokeRec::setHairlineStyle) + .def("setStrokeStyle", &SkStrokeRec::setStrokeStyle, + R"docstring( + Specify the strokewidth, and optionally if you want stroke + fill. + + Note, if width==0, then this request is taken to mean: + strokeAndFill==true -> new style will be Fill strokeAndFill==false -> + new style will be Hairline + )docstring", + py::arg("width"), py::arg("strokeAndFill") = false) + .def("setStrokeParams", &SkStrokeRec::setStrokeParams, + py::arg("cap"), py::arg("join"), py::arg("miterLimit")) + .def("getResScale", &SkStrokeRec::getResScale) + .def("setResScale", &SkStrokeRec::setResScale, py::arg("rs")) + .def("needToApply", &SkStrokeRec::needToApply, + R"docstring( + Returns true if this specifes any thick stroking, i.e. + + :py:meth:`applyToPath` will return true. + )docstring") + .def("applyToPath", &SkStrokeRec::applyToPath, + R"docstring( + Apply these stroke parameters to the src path, returning the result in + dst. + + If there was no change (i.e. style == hairline or fill) this returns + false and dst is unchanged. Otherwise returns true and the result is + stored in dst. + + src and dst may be the same path. + )docstring", + py::arg("dst"), py::arg("src")) + .def("applyToPaint", &SkStrokeRec::applyToPaint, + R"docstring( + Apply these stroke parameters to a paint. + )docstring", + py::arg("paint")) + .def("getInflationRadius", &SkStrokeRec::getInflationRadius, + R"docstring( + Gives a conservative value for the outset that should applied to a + geometries bounds to account for any inflation due to applying this + strokeRec to the geometry. + )docstring") + .def("hasEqualEffect", &SkStrokeRec::hasEqualEffect, + R"docstring( + Compare if two SkStrokeRecs have an equal effect on a path. + + Equal SkStrokeRecs produce equal paths. Equality of produced paths does + not take the ResScale parameter into account. + )docstring", + py::arg("other")) + .def_static("GetInflationRadius", + py::overload_cast( + &SkStrokeRec::GetInflationRadius), + R"docstring( + Equivalent to: :py:class:`StrokeRec` rec(paint, style); + rec.getInflationRadius(); This does not account for other effects on the + paint (i.e. path effect). + )docstring", + py::arg("paint"), py::arg("style")) + .def_static("GetInflationRadius", + py::overload_cast( + &SkStrokeRec::GetInflationRadius), + py::arg("join"), py::arg("miterLimit"), py::arg("cap"), + py::arg("strokeWidth")) + .def_readonly_static("kStyleCount", &SkStrokeRec::kStyleCount) + ; + + +// PathEffect +py::class_, SkFlattenable> + patheffect(m, "PathEffect", R"docstring( :py:class:`PathEffect` is the base class for objects in the :py:class:`Paint` that affect the geometry of a drawing primitive before it @@ -39,39 +135,28 @@ py::class_, SkFlattenable> patheffect( ~PathEffect.PointData )docstring"); -py::class_(patheffect, "DashInfo") +py::class_(patheffect, "DashInfo") .def(py::init<>()) - .def(py::init([] (const std::vector& intervals, SkScalar phase) { - auto info = PyDashInfo(); - info.intervals_.assign(intervals.begin(), intervals.end()); - info.fIntervals = (info.intervals_.empty()) ? - nullptr : &info.intervals_[0]; - info.fCount = info.intervals_.size(); - info.fPhase = phase; - return info; - })) - .def_property("fIntervals", - [] (const PyDashInfo& info) { return info.intervals_; }, - [] (PyDashInfo& info, const std::vector& intervals) { - info.intervals_.assign(intervals.begin(), intervals.end()); - info.fIntervals = (info.intervals_.empty()) ? - nullptr : &info.intervals_[0]; - info.fCount = info.intervals_.size(); + .def_property_readonly("fIntervals", + [] (const SkPathEffect::DashInfo& info) { + return std::vector( + info.fIntervals, info.fIntervals + info.fCount); }, R"docstring( Length of on/off intervals for dashed lines. )docstring") - .def_readonly("fCount", &PyDashInfo::fCount, + .def_readonly("fCount", &SkPathEffect::DashInfo::fCount, R"docstring( Number of intervals in the dash. Should be even number. )docstring") - .def_readwrite("fPhase", &PyDashInfo::fPhase, + .def_readonly("fPhase", &SkPathEffect::DashInfo::fPhase, R"docstring( Offset into the dashed interval pattern. )docstring") ; -py::class_ pointdata(patheffect, "PointData", R"docstring( +py::class_ pointdata(patheffect, "PointData", + R"docstring( :py:class:`PointData` aggregates all the information needed to draw the point primitives returned by an asPoints call. @@ -92,19 +177,18 @@ py::enum_( pointdata .def(py::init<>()) - .def_readwrite("fFlags", &PyPointData::fFlags) - .def_property("fPoints", - [] (const PyPointData& data) { return data.points_; }, - [] (PyPointData* data, const std::vector& points) { - data->points_.assign(points.begin(), points.end()); - data->fNumPoints = data->points_.size(); + .def_readonly("fFlags", &SkPathEffect::PointData::fFlags) + .def_property_readonly("fPoints", + [] (const SkPathEffect::PointData& data) { + return std::vector( + data.fPoints, data.fPoints + data.fNumPoints); }) - .def_readonly("fNumPoints", &PyPointData::fNumPoints) - .def_readwrite("fSize", &PyPointData::fSize) - .def_readwrite("fClipRect", &PyPointData::fClipRect) - .def_readwrite("fPath", &PyPointData::fPath) - .def_readwrite("fFirst", &PyPointData::fFirst) - .def_readwrite("fLast", &PyPointData::fLast) + .def_readonly("fNumPoints", &SkPathEffect::PointData::fNumPoints) + .def_readonly("fSize", &SkPathEffect::PointData::fSize) + .def_readonly("fClipRect", &SkPathEffect::PointData::fClipRect) + .def_readonly("fPath", &SkPathEffect::PointData::fPath) + .def_readonly("fFirst", &SkPathEffect::PointData::fFirst) + .def_readonly("fLast", &SkPathEffect::PointData::fLast) ; py::enum_(patheffect, "DashType", @@ -192,7 +276,13 @@ patheffect )docstring") .def_static("RegisterFlattenables", &SkPathEffect::RegisterFlattenables) .def_static("GetFlattenableType", &SkPathEffect::GetFlattenableType) - .def_static("Deserialize", &SkPathEffect::Deserialize) + .def_static("Deserialize", + [] (py::buffer b) { + auto info = b.request(); + size_t size = (info.ndim) ? info.shape[0] * info.strides[0] : 0; + return SkPathEffect::Deserialize(info.ptr, size); + }, + py::arg("data")) ; py::class_>( diff --git a/src/skia/main.cpp b/src/skia/main.cpp index e8c974379..424d82559 100644 --- a/src/skia/main.cpp +++ b/src/skia/main.cpp @@ -30,133 +30,7 @@ void initVertices(py::module &); // Main entry point. PYBIND11_MODULE(skia, m) { m.doc() = R"docstring( - skia - ---- - - Python Skia binding. - - .. currentmodule:: skia - - .. autosummary:: - :toctree: _generate - :nosignatures: - :template: class.rst - - AlphaType - ApplyPerspectiveClip - AutoCanvasRestore - BBoxHierarchy - BackingFit - Bitmap - BlendMode - BlendModeCoeff - Budgeted - Canvas - Canvas.SaveLayerFlags - Canvas.SrcRectConstraint - Canvas.PointMode - Canvas.QuadAAFlags - Canvas.SaveLayerRec - Canvas.Lattice - Canvas.Lattice.RectType - ClipOp - Color4f - ColorFilter - ColorInfo - ColorSpace - ColorType - CornerPathEffect - DashPathEffect - Data - DeserialProcs - DiscretePathEffect - EncodedImageFormat - FilterQuality - Flattanable - Font - FontArguments - FontHinting - FontMetrics - FontMgr - FontStyle - FontStyleSet - GrBackendApi - GrBackendSemaphore - GrBackendTexture - GrContext - GrFlushFlags - GrGLBackendState - GrGLInterface - GrMipMapped - GrProtected - GrRenderable - GrSemaphoresSubmitted - GrSurfaceOrigin - IPoint - IRect - ISize - Image - ImageFilter - ImageInfo - M44 - MaskFilter - Matrix - MatrixPathEffect - MergePathEffect - Paint - Path - Path.Iter - Path.RawIter - Path.ArcSize - Path.AddPathMode - Path.SegmentMask - Path.Verb - PathConvexityType - PathDirection - PathEffect - PathEffect.DashInfo - PathEffect.DashType - PathEffect.PointData - PathEffect.PointData.PointFlags - Path1DPathEffect - Path1DPathEffect.Style - Path2DPathEffect - PathFillType - PathSegmentMask - PathVerb - Picture - PictureRecorder - PixelGeometry - Pixmap - Point - Point3 - RRect - RSXform - Rect - Region - Shader - Size - StrokePathEffect - Surface - Surface.AsyncReadResult - Surface.ContentChangeMode - Surface.BackendHandleAccess - Surface.RescaleGamma - Surface.BackendSurfaceAccess - Surface.FlushFlags - SurfaceCharacterization - SurfaceProps - SurfaceProps.Flags - SurfaceProps.InitType - TextBlob - TextBlobBuilder - TextEncoding - TileMode - TrimPathEffect - TrimPathEffect.Mode - Typeface - Vertices - YUVColorSpace + Python Skia binding module. )docstring"; initBlendMode(m); diff --git a/tests/test_patheffect.py b/tests/test_patheffect.py index 375f317b9..7a2a1377f 100644 --- a/tests/test_patheffect.py +++ b/tests/test_patheffect.py @@ -2,12 +2,101 @@ import pytest +@pytest.fixture +def strokerec(): + return skia.StrokeRec(skia.StrokeRec.InitStyle.kHairline) + + +@pytest.mark.parametrize('args', [ + (skia.StrokeRec.InitStyle.kHairline,), + (skia.Paint(), skia.Paint.Style.kStroke), + (skia.Paint(), skia.Paint.Style.kStroke, 1), + (skia.Paint(),), + (skia.Paint(), 1), +]) +def test_StrokeRec_init(args): + assert isinstance(skia.StrokeRec(*args), skia.StrokeRec) + + +def test_StrokeRec_getStyle(strokerec): + assert isinstance(strokerec.getStyle(), skia.StrokeRec.Style) + + +def test_StrokeRec_getWidth(strokerec): + assert isinstance(strokerec.getWidth(), float) + + +def test_StrokeRec_getMiter(strokerec): + assert isinstance(strokerec.getMiter(), float) + + +def test_StrokeRec_getCap(strokerec): + assert isinstance(strokerec.getCap(), skia.Paint.Cap) + + +def test_StrokeRec_getJoin(strokerec): + assert isinstance(strokerec.getJoin(), skia.Paint.Join) + + +def test_StrokeRec_isHairlineStyle(strokerec): + assert isinstance(strokerec.isHairlineStyle(), bool) + + +def test_StrokeRec_isFillStyle(strokerec): + assert isinstance(strokerec.isFillStyle(), bool) + + +def test_StrokeRec_setFillStyle(strokerec): + strokerec.setFillStyle() + + +def test_StrokeRec_setHairlineStyle(strokerec): + strokerec.setHairlineStyle() + + +def test_StrokeRec_setStrokeStyle(strokerec): + strokerec.setStrokeStyle(1., True) + + +def test_StrokeRec_setStrokeParams(strokerec): + strokerec.setStrokeParams(skia.Paint.Cap.kButt, skia.Paint.Join.kMiter, 1) + + +def test_StrokeRec_getResScale(strokerec): + assert isinstance(strokerec.getResScale(), float) + + +def test_StrokeRec_setResScale(strokerec): + strokerec.setResScale(1.) + + +def test_StrokeRec_needToApply(strokerec): + assert isinstance(strokerec.needToApply(), bool) + + +def test_StrokeRec_applyToPath(strokerec): + assert isinstance(strokerec.applyToPath(skia.Path(), skia.Path()), bool) + + +def test_StrokeRec_applyToPaint(strokerec): + strokerec.applyToPaint(skia.Paint()) + + +def test_StrokeRec_getInflationRadius(strokerec): + assert isinstance(strokerec.getInflationRadius(), float) + + +def test_StrokeRec_hasEqualEffect(strokerec): + assert isinstance(strokerec.hasEqualEffect( + skia.StrokeRec(skia.StrokeRec.InitStyle.kHairline)), bool) + + @pytest.mark.parametrize('args', [ - tuple(), - ([1.0, 2.0,], 1.0), + (skia.Paint(), skia.Paint.Style.kStroke,), + (skia.Paint.Join.kMiter, 1, skia.Paint.Cap.kButt, 1), ]) -def test_PathEffect_DashInfo_init(args): - assert isinstance(skia.PathEffect.DashInfo(*args), skia.PathEffect.DashInfo) +def test_StrokeRec_GetInflationRadius(args): + assert isinstance(skia.StrokeRec.GetInflationRadius(*args), float) @pytest.fixture @@ -16,18 +105,15 @@ def patheffect_dashinfo(): def test_PathEffect_DashInfo_fIntervals(patheffect_dashinfo): - patheffect_dashinfo.fIntervals = [1.0, 2.0] assert isinstance(patheffect_dashinfo.fIntervals, list) - assert patheffect_dashinfo.fCount == 2 -def test_PathEffect_DashInfo_fPhase(patheffect_dashinfo): - patheffect_dashinfo.fPhase = 2.0 - assert patheffect_dashinfo.fPhase == 2.0 +def test_PathEffect_DashInfo_fCount(patheffect_dashinfo): + assert isinstance(patheffect_dashinfo.fCount, int) -def test_PathEffect_PointData_init(): - assert isinstance(skia.PathEffect.PointData(), skia.PathEffect.PointData) +def test_PathEffect_DashInfo_fPhase(patheffect_dashinfo): + assert isinstance(patheffect_dashinfo.fPhase, float) @pytest.fixture @@ -36,16 +122,15 @@ def patheffect_pointdata(): def test_PathEffect_PointData_fFlags(patheffect_pointdata): - patheffect_pointdata.fFlags = skia.PathEffect.PointData.PointFlags.kCircles - assert (patheffect_pointdata.fFlags == - skia.PathEffect.PointData.PointFlags.kCircles) + assert isinstance(patheffect_pointdata.fFlags, int) def test_PathEffect_PointData_fPoints(patheffect_pointdata): - patheffect_pointdata.fPoints = [skia.Point(0, 0)] assert isinstance(patheffect_pointdata.fPoints, list) - assert patheffect_pointdata.fPoints[0] == skia.Point(0, 0) - assert patheffect_pointdata.fNumPoints == 1 + + +def test_PathEffect_PointData_fNumPoints(patheffect_pointdata): + assert isinstance(patheffect_pointdata.fNumPoints, int) def test_PathEffect_PointData_fSize(patheffect_pointdata): @@ -68,14 +153,58 @@ def test_PathEffect_PointData_fLast(patheffect_pointdata): assert isinstance(patheffect_pointdata.fLast, skia.Path) -def test_DiscretePathEffect_Make(): - assert isinstance(skia.DiscretePathEffect.Make(4.0, 1.0), skia.PathEffect) +@pytest.fixture +def patheffect(): + return skia.DashPathEffect.Make([2., 1.], 0) + + +def test_PathEffect_filterPath(patheffect): + dst = skia.Path() + src = skia.Path() + src.addCircle(10, 10, 5) + rec = skia.StrokeRec(skia.StrokeRec.InitStyle.kHairline) + assert isinstance(patheffect.filterPath(dst, src, rec, None), bool) + + +def test_PathEffect_computeFastBounds(patheffect): + patheffect.computeFastBounds(skia.Rect(100, 100), skia.Rect(100, 100)) + + +def test_PathEffect_asPoints(patheffect): + results = skia.PathEffect.PointData() + path = skia.Path() + path.addCircle(10, 10, 5) + rec = skia.StrokeRec(skia.StrokeRec.InitStyle.kHairline) + matrix = skia.Matrix() + assert isinstance( + patheffect.asPoints(results, path, rec, matrix, None), bool) + + +def test_PathEffect_asADash(patheffect): + dashinfo = skia.PathEffect.DashInfo() + assert isinstance(patheffect.asADash(dashinfo), skia.PathEffect.DashType) + + +def test_PathEffect_Deserialize(patheffect): + data = patheffect.serialize() + assert isinstance(data, skia.Data) + effect = skia.PathEffect.Deserialize(data) + assert isinstance(effect, skia.PathEffect) + + +def test_PathEffect_GetFlattenableType(): + assert isinstance( + skia.PathEffect.GetFlattenableType(), skia.Flattanable.Type) def test_DashPathEffect_Make(): assert isinstance(skia.DashPathEffect.Make([2., 1.], 0), skia.PathEffect) +def test_DiscretePathEffect_Make(): + assert isinstance(skia.DiscretePathEffect.Make(4.0, 1.0), skia.PathEffect) + + def test_CornerPathEffect_Make(): assert isinstance(skia.CornerPathEffect.Make(4.0), skia.PathEffect)