From 6274c41a8005c585c12258bf054d84338e8ebf6e Mon Sep 17 00:00:00 2001 From: jmsgrogan Date: Tue, 24 Jan 2023 11:04:47 +0000 Subject: [PATCH] Add path rendering and svg line output. --- src/base/geometry/path/Arc.cpp | 143 +++++++++++------- src/base/geometry/path/Arc.h | 12 ++ src/base/geometry/path/CubicBezierCurve.cpp | 10 ++ src/base/geometry/path/CubicBezierCurve.h | 4 + .../geometry/path/PathPostScriptConverter.cpp | 4 +- .../geometry/path/QuadraticBezierCurve.cpp | 5 + src/base/geometry/path/QuadraticBezierCurve.h | 2 + .../graphics/directx/DirectX2dPainter.cpp | 22 ++- .../visual_elements/svg/SvgPainter.cpp | 29 ++++ .../visual_elements/svg/SvgPainter.h | 4 + .../visual_elements/svg/SvgShapeElement.cpp | 5 +- .../visual_elements/svg/SvgShapeElement.h | 5 +- .../svg/elements/SvgShapeElements.cpp | 40 +++++ .../svg/elements/SvgShapeElements.h | 8 + test/graphics/TestD2dOffScreenRendering.cpp | 67 ++++++-- 15 files changed, 281 insertions(+), 79 deletions(-) diff --git a/src/base/geometry/path/Arc.cpp b/src/base/geometry/path/Arc.cpp index c74c659..bc96db9 100644 --- a/src/base/geometry/path/Arc.cpp +++ b/src/base/geometry/path/Arc.cpp @@ -3,78 +3,105 @@ #include "PointParser.h" #include -Arc::Arc(const Point& startPoint, const Point& endPoint, double rX, double rY, double rotation, bool largeArc, bool sweep) - : mStartPoint(startPoint), - mEndPoint(endPoint), - mRx(rX), - mRy(rY), - mRotation(rotation), - mLargeArc(largeArc), - mSweep(sweep) -{ - -} - -Point Arc::getFirstPoint() const -{ - return mStartPoint; -} - -Point Arc::getEndPoint() const -{ - return mEndPoint; -} - -std::string Arc::toPostScriptString(std::size_t precision) const -{ - const auto large = mLargeArc ? "1" : "0"; - const auto sweep = mSweep ? "1" : "0"; - std::stringstream sstr; - if (precision > 0) +namespace ntk { + Arc::Arc(const Point& startPoint, const Point& endPoint, double rX, double rY, double rotation, bool largeArc, bool sweep) + : mStartPoint(startPoint), + mEndPoint(endPoint), + mRx(rX), + mRy(rY), + mRotation(rotation), + mLargeArc(largeArc), + mSweep(sweep) { - sstr.precision(precision); + } - if (mPostscriptPositioning == PostscriptPositioning::RELATIVE_TO) + + Point Arc::getFirstPoint() const { - sstr << "a"; + return mStartPoint; } - else + + Point Arc::getEndPoint() const { - sstr << "A"; + return mEndPoint; } - sstr << mRx << " " << mRy << " " << mRotation << " " << large << " " << sweep << " "; - if (mPostscriptPositioning == PostscriptPositioning::RELATIVE_TO) + + std::string Arc::toPostScriptString(std::size_t precision) const { - sstr << PointParser::toStringRelative(mEndPoint, mStartPoint, 2, " ", precision); + const auto large = mLargeArc ? "1" : "0"; + const auto sweep = mSweep ? "1" : "0"; + std::stringstream sstr; + if (precision > 0) + { + sstr.precision(precision); + } + if (mPostscriptPositioning == PostscriptPositioning::RELATIVE_TO) + { + sstr << "a"; + } + else + { + sstr << "A"; + } + sstr << mRx << " " << mRy << " " << mRotation << " " << large << " " << sweep << " "; + if (mPostscriptPositioning == PostscriptPositioning::RELATIVE_TO) + { + sstr << PointParser::toStringRelative(mEndPoint, mStartPoint, 2, " ", precision); + } + else + { + sstr << PointParser::toString(mEndPoint, 2, " ", precision); + } + return sstr.str(); } - else + + Bounds Arc::getBounds() const { - sstr << PointParser::toString(mEndPoint, 2, " ", precision); + return {}; } - return sstr.str(); -} -Bounds Arc::getBounds() const -{ - return {}; -} + const Point& Arc::getLocation() const + { + return mStartPoint; + } -const Point& Arc::getLocation() const -{ - return mStartPoint; -} + void Arc::sample(SparseGrid*) const + { -void Arc::sample(SparseGrid*) const -{ + } -} + Arc::Type Arc::getType() const + { + return Type::CURVE; + } -Arc::Type Arc::getType() const -{ - return Type::CURVE; -} + Arc::CurveType Arc::getCurveType() const + { + return CurveType::ARC; + } -Arc::CurveType Arc::getCurveType() const -{ - return CurveType::ARC; + double Arc::getRx() const + { + return mRx; + } + + double Arc::getRy() const + { + return mRy; + } + + double Arc::getRotation() const + { + return mRotation; + } + + bool Arc::getUseLargeArc() const + { + return mLargeArc; + } + + bool Arc::getSweepParam() const + { + return mSweep; + } } diff --git a/src/base/geometry/path/Arc.h b/src/base/geometry/path/Arc.h index f2b93ac..eeb0831 100644 --- a/src/base/geometry/path/Arc.h +++ b/src/base/geometry/path/Arc.h @@ -3,6 +3,7 @@ #include "Curve.h" #include "Point.h" +namespace ntk{ class Arc : public Curve { public: @@ -20,6 +21,16 @@ public: CurveType getCurveType() const override; + double getRx() const; + + double getRy() const; + + double getRotation() const; + + bool getUseLargeArc() const; + + bool getSweepParam() const; + void sample(SparseGrid*) const override; std::string toPostScriptString(std::size_t precision = 0) const override; @@ -33,3 +44,4 @@ private: bool mLargeArc{ false }; bool mSweep{ false }; }; +} diff --git a/src/base/geometry/path/CubicBezierCurve.cpp b/src/base/geometry/path/CubicBezierCurve.cpp index 793ff8c..dce9db0 100644 --- a/src/base/geometry/path/CubicBezierCurve.cpp +++ b/src/base/geometry/path/CubicBezierCurve.cpp @@ -21,6 +21,16 @@ Point CubicBezierCurve::getEndPoint() const return mEndPoint; } +const Point& CubicBezierCurve::getStartControlPoint() const +{ + return mStartControlPoint; +} + +const Point& CubicBezierCurve::getEndControlPoint() const +{ + return mEndControlPoint; +} + std::string CubicBezierCurve::toPostScriptString(std::size_t precision) const { if (mPostscriptPositioning == PostscriptPositioning::RELATIVE_TO) diff --git a/src/base/geometry/path/CubicBezierCurve.h b/src/base/geometry/path/CubicBezierCurve.h index b3d4043..76cc3e3 100644 --- a/src/base/geometry/path/CubicBezierCurve.h +++ b/src/base/geometry/path/CubicBezierCurve.h @@ -12,6 +12,10 @@ public: Point getEndPoint() const override; + const Point& getStartControlPoint() const; + + const Point& getEndControlPoint() const; + Bounds getBounds() const override; const Point& getLocation() const override; diff --git a/src/base/geometry/path/PathPostScriptConverter.cpp b/src/base/geometry/path/PathPostScriptConverter.cpp index acbbf58..82b77b9 100644 --- a/src/base/geometry/path/PathPostScriptConverter.cpp +++ b/src/base/geometry/path/PathPostScriptConverter.cpp @@ -249,12 +249,12 @@ PathElementPtr PathPostScriptConverter::onArc() if (mPositionState == PositionState::RELATIVE) { const auto end_point = Point(mCurrentPoint.getX() + mPointBuffer[5], mCurrentPoint.getY() + mPointBuffer[6]); - element = std::make_unique(mCurrentPoint, end_point, rx, ry, rotation, large_arc, sweep); + element = std::make_unique(mCurrentPoint, end_point, rx, ry, rotation, large_arc, sweep); } else { const auto end_point = Point(mPointBuffer[5], mPointBuffer[6]); - element = std::make_unique(mCurrentPoint, end_point, rx, ry, rotation, large_arc, sweep); + element = std::make_unique(mCurrentPoint, end_point, rx, ry, rotation, large_arc, sweep); } } return element; diff --git a/src/base/geometry/path/QuadraticBezierCurve.cpp b/src/base/geometry/path/QuadraticBezierCurve.cpp index dba241e..fff1072 100644 --- a/src/base/geometry/path/QuadraticBezierCurve.cpp +++ b/src/base/geometry/path/QuadraticBezierCurve.cpp @@ -20,6 +20,11 @@ Point QuadraticBezierCurve::getEndPoint() const return mEndPoint; } +Point QuadraticBezierCurve::getControlPoint() const +{ + return mControlPoint; +} + std::string QuadraticBezierCurve::toPostScriptString(std::size_t precision) const { if (mPostscriptPositioning == PostscriptPositioning::RELATIVE_TO) diff --git a/src/base/geometry/path/QuadraticBezierCurve.h b/src/base/geometry/path/QuadraticBezierCurve.h index 6b68f5c..ce85d93 100644 --- a/src/base/geometry/path/QuadraticBezierCurve.h +++ b/src/base/geometry/path/QuadraticBezierCurve.h @@ -10,6 +10,8 @@ public: Point getFirstPoint() const override; + Point getControlPoint() const; + Point getEndPoint() const override; Bounds getBounds() const override; diff --git a/src/rendering/graphics/directx/DirectX2dPainter.cpp b/src/rendering/graphics/directx/DirectX2dPainter.cpp index f1b4c06..aff62bd 100644 --- a/src/rendering/graphics/directx/DirectX2dPainter.cpp +++ b/src/rendering/graphics/directx/DirectX2dPainter.cpp @@ -195,7 +195,6 @@ void DirectX2dPainter::paintPath(SceneModel* model) for (const auto& feature : path_item->getFeatures()) { const auto loc = feature->getLocation(); - MLOG_INFO("Starting feature at: " << loc.getX() << " " << loc.getY()); path_sink->BeginFigure(toD2dPoint(loc), D2D1_FIGURE_BEGIN_FILLED); for (const auto& element : feature->getElements()) @@ -246,39 +245,48 @@ void DirectX2dPainter::paintPath(SceneModel* model) mSolidBrush->SetColor(toD2dColor(material.getStrokeColor())); rt->DrawGeometry(path_geom.Get(), mSolidBrush.Get(), 1.0f); } - rt->SetTransform(D2D1::Matrix3x2F::Identity()); } void DirectX2dPainter::onArc(Curve* element, ID2D1GeometrySink* sink) { + auto arc = dynamic_cast(element); + const auto end = toD2dPoint(arc->getEndPoint()); + D2D1_SIZE_F size{static_cast(arc->getRx()), static_cast(arc->getRx()) }; + const auto angle = static_cast(arc->getRotation()); + D2D1_SWEEP_DIRECTION direction = arc->getSweepParam() ? D2D1_SWEEP_DIRECTION_CLOCKWISE : D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; + D2D1_ARC_SIZE arc_size = arc->getUseLargeArc() ? D2D1_ARC_SIZE_LARGE : D2D1_ARC_SIZE_SMALL; + + D2D1_ARC_SEGMENT segment{ end , size , angle , direction , arc_size }; + sink->AddArc(segment); } void DirectX2dPainter::onQuadraticBezier(Curve* element, ID2D1GeometrySink* sink) { - + auto bezier = dynamic_cast(element); + D2D1_QUADRATIC_BEZIER_SEGMENT segment{ toD2dPoint(bezier->getControlPoint()) , toD2dPoint(bezier->getEndPoint()) }; + sink->AddQuadraticBezier(segment); } void DirectX2dPainter::onCubicBezier(Curve* element, ID2D1GeometrySink* sink) { - + auto bezier = dynamic_cast(element); + D2D1_BEZIER_SEGMENT segment{ toD2dPoint(bezier->getStartControlPoint()), toD2dPoint(bezier->getEndControlPoint()), toD2dPoint(bezier->getEndPoint()) }; + sink->AddBezier(segment); } void DirectX2dPainter::onLine(PathElement* element, ID2D1GeometrySink* sink) { for (const auto& point : dynamic_cast(element)->getPoints().getPoints()) { - MLOG_INFO("Adding line entry at: " << point.getX() << " " << point.getY()); sink->AddLine(toD2dPoint(point)); } - MLOG_INFO("Finished line"); } void DirectX2dPainter::onLineSegment(PathElement* element, ID2D1GeometrySink* sink) { const auto loc = element->getEndPoint(); - MLOG_INFO("Adding segment entry at: " << loc.getX() << " " << loc.getY()); sink->AddLine(toD2dPoint(loc)); } diff --git a/src/rendering/visual_elements/svg/SvgPainter.cpp b/src/rendering/visual_elements/svg/SvgPainter.cpp index 6ef4c0e..54a552f 100644 --- a/src/rendering/visual_elements/svg/SvgPainter.cpp +++ b/src/rendering/visual_elements/svg/SvgPainter.cpp @@ -15,6 +15,8 @@ #include "Circle.h" #include "Rectangle.h" #include "Path.h" +#include "Line.h" +#include "LineSegment.h" #include "SvgShapeElements.h" #include "SvgTextElement.h" @@ -139,6 +141,14 @@ void SvgPainter::paintPrimitive(SvgDocument* document, SceneModel* model) const { paintPath(document, model); } + else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::LINE) + { + paintLine(document, model); + } + else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::LINE_SEGMENT) + { + paintLineSegment(document, model); + } } void SvgPainter::paintRect(SvgDocument* document, SceneModel* model) const @@ -173,6 +183,25 @@ void SvgPainter::paintCircle(SvgDocument* document, SceneModel* model) const document->getRoot()->addChild(std::move(circle)); } +void SvgPainter::paintLine(SvgDocument* document, SceneModel* model) const +{ + auto model_line = dynamic_cast(model->getGeometry()); + auto svg_line = std::make_unique(); + svg_line->setPoints(model_line->getFirstPoint(), model_line->getPoints().getPoints()); + + setStyle(model, svg_line.get()); + document->getRoot()->addChild(std::move(svg_line)); +} + +void SvgPainter::paintLineSegment(SvgDocument* document, SceneModel* model) const +{ + auto model_line = dynamic_cast(model->getGeometry()); + auto svg_line = std::make_unique(model_line->getFirstPoint(), model_line->getEndPoint()); + + setStyle(model, svg_line.get()); + document->getRoot()->addChild(std::move(svg_line)); +} + void SvgPainter::paintPath(SvgDocument* document, SceneModel* model) const { auto model_path = dynamic_cast(model->getGeometry()); diff --git a/src/rendering/visual_elements/svg/SvgPainter.h b/src/rendering/visual_elements/svg/SvgPainter.h index 3649dbd..7daf021 100644 --- a/src/rendering/visual_elements/svg/SvgPainter.h +++ b/src/rendering/visual_elements/svg/SvgPainter.h @@ -26,6 +26,10 @@ private: void paintCircle(SvgDocument* document, SceneModel* model) const; + void paintLine(SvgDocument* document, SceneModel* model) const; + + void paintLineSegment(SvgDocument* document, SceneModel* model) const; + void paintPath(SvgDocument* document, SceneModel* model) const; void paintText(SvgDocument* document, SceneText* model) const; diff --git a/src/rendering/visual_elements/svg/SvgShapeElement.cpp b/src/rendering/visual_elements/svg/SvgShapeElement.cpp index de74522..fd92e85 100644 --- a/src/rendering/visual_elements/svg/SvgShapeElement.cpp +++ b/src/rendering/visual_elements/svg/SvgShapeElement.cpp @@ -5,8 +5,9 @@ #include -SvgShapeElement::SvgShapeElement(const std::string& tagName) - : SvgElement(tagName) +SvgShapeElement::SvgShapeElement(const std::string& tagName, std::size_t precision) + : SvgElement(tagName), + mPrecision(precision) { } diff --git a/src/rendering/visual_elements/svg/SvgShapeElement.h b/src/rendering/visual_elements/svg/SvgShapeElement.h index 00fab67..36e345c 100644 --- a/src/rendering/visual_elements/svg/SvgShapeElement.h +++ b/src/rendering/visual_elements/svg/SvgShapeElement.h @@ -7,7 +7,7 @@ class SvgShapeElement : public SvgElement { public: - SvgShapeElement(const std::string& tagName); + SvgShapeElement(const std::string& tagName, std::size_t precision = 0); Transform getTransform() const; @@ -25,6 +25,9 @@ public: void setNoStroke(); +protected: + std::size_t mPrecision{ 0 }; + private: std::string getLabelledContent(const std::string& key, const std::string& content) const; Point parsePoint(const std::string& pointString, double defaultVal = 0.0) const; diff --git a/src/rendering/visual_elements/svg/elements/SvgShapeElements.cpp b/src/rendering/visual_elements/svg/elements/SvgShapeElements.cpp index a2511ea..97ec24c 100644 --- a/src/rendering/visual_elements/svg/elements/SvgShapeElements.cpp +++ b/src/rendering/visual_elements/svg/elements/SvgShapeElements.cpp @@ -1,5 +1,6 @@ #include "SvgShapeElements.h" +#include "PointParser.h" #include SvgCircle::SvgCircle(Type type) @@ -174,6 +175,45 @@ void SvgPolyline::setPoints(const std::vector& locs) addAttribute(std::move(points)); } +void SvgPolyline::setPoints(const Point& startPoint, const std::vector& locs) +{ + auto points = std::make_unique("points"); + + std::stringstream sstr; + sstr << startPoint.getX() << "," << startPoint.getY() << " "; + + for (const auto& loc : locs) + { + sstr << loc.getX() << "," << loc.getY() << " "; + } + points->setValue(sstr.str()); + addAttribute(std::move(points)); +} + +SvgLine::SvgLine(const Point& startPoint, const Point& endPoint, std::size_t precision) + : SvgShapeElement("line", precision) +{ + auto fill = std::make_unique("fill"); + fill->setValue("none"); + addAttribute(std::move(fill)); + + auto x1 = std::make_unique("x1"); + x1->setValue(PointParser::toString(startPoint.getX(), mPrecision)); + addAttribute(std::move(x1)); + + auto y1 = std::make_unique("y1"); + y1->setValue(PointParser::toString(startPoint.getY(), mPrecision)); + addAttribute(std::move(y1)); + + auto x2 = std::make_unique("x2"); + x2->setValue(PointParser::toString(endPoint.getX(), mPrecision)); + addAttribute(std::move(x2)); + + auto y2 = std::make_unique("y2"); + y2->setValue(PointParser::toString(endPoint.getY(), mPrecision)); + addAttribute(std::move(y2)); +} + SvgPath::SvgPath() : SvgShapeElement("path") diff --git a/src/rendering/visual_elements/svg/elements/SvgShapeElements.h b/src/rendering/visual_elements/svg/elements/SvgShapeElements.h index e62fa1f..458c52d 100644 --- a/src/rendering/visual_elements/svg/elements/SvgShapeElements.h +++ b/src/rendering/visual_elements/svg/elements/SvgShapeElements.h @@ -60,9 +60,17 @@ class SvgPolyline : public SvgShapeElement public: SvgPolyline(); + void setPoints(const Point& startPoint, const std::vector& loc); + void setPoints(const std::vector& loc); }; +class SvgLine : public SvgShapeElement +{ +public: + SvgLine(const Point& startPoint, const Point& endPoint, std::size_t precision = 0); +}; + class SvgPath : public SvgShapeElement { public: diff --git a/test/graphics/TestD2dOffScreenRendering.cpp b/test/graphics/TestD2dOffScreenRendering.cpp index 34f607c..6731778 100644 --- a/test/graphics/TestD2dOffScreenRendering.cpp +++ b/test/graphics/TestD2dOffScreenRendering.cpp @@ -7,14 +7,14 @@ #include "LineNode.h" #include "PathNode.h" -void addRect(const Point& loc, std::vector >& nodes, double radius = 0.0) +void addRect(const Point& loc, std::vector >& nodes, double radius = 0.0) { auto node = std::make_unique(loc, 150.0, 100.0); node->setRadius(radius); nodes.push_back(std::move(node)); } -void addCircle(const Point& loc, std::vector >& nodes, double minorRadius = 0.0) +void addCircle(const Point& loc, std::vector >& nodes, double minorRadius = 0.0) { const auto radius = 50.0; auto centre_loc = loc; @@ -30,26 +30,25 @@ void addCircle(const Point& loc, std::vector nodes.push_back(std::move(node)); } -void addLine(const Point& loc, std::vector >& nodes) +void addLine(const Point& loc, std::vector >& nodes) { std::vector points = { Point(150.0, 100.0) }; auto node = std::make_unique(loc, points); nodes.push_back(std::move(node)); } -void addPath(const Point& loc, const std::string& path, std::vector >& nodes) +void addPath(const Point& loc, const std::string& path, std::vector >& nodes) { auto node = std::make_unique(loc, path); nodes.push_back(std::move(node)); } -TEST_CASE(TestD2dOffScreenRendering, "graphics") +void addShapes(const Point& start_loc, std::vector >& nodes, bool use_fill = false) { - TestRenderer renderer(800, 800); + auto loc = start_loc; - std::vector > nodes; + auto fill_color = Color(200, 0, 200); - auto loc = Point(10, 10); addRect(loc, nodes); loc.move(250, 0); @@ -67,10 +66,60 @@ TEST_CASE(TestD2dOffScreenRendering, "graphics") loc.move(100, 0); addPath(loc, "M0 0 h150 v100 h-150Z", nodes); + loc = Point(10, 300); + addPath(loc, "M0 0 h150 q50 50 0 100 h-150Z", nodes); + + loc.move(250, 0); + addPath(loc, "M0 0 h150 c25 25 25 75 0 100 h-150Z", nodes); + + loc.move(250, 0); + addPath(loc, "M0 0 h150 a50 50 0 0 1 0 100 h-150Z", nodes); + + if (use_fill) + { + for (auto& node : nodes) + { + node->setFillColor(fill_color); + } + } +} + +TEST_CASE(TestD2dOffScreenRendering_Outlines, "graphics") +{ + TestRenderer renderer(800, 800); + + std::vector > nodes; + + auto loc = Point(10, 10); + + addShapes(loc, nodes, false); + auto scene = renderer.getScene(); for (const auto& node : nodes) { scene->addNode(node.get()); } - renderer.write(TestUtils::getTestOutputDir(__FILE__) / "out.png"); + + renderer.writeSvg(TestUtils::getTestOutputDir(__FILE__) / "outlines.svg"); + renderer.write(TestUtils::getTestOutputDir(__FILE__) / "outlines.png"); +}; + +TEST_CASE(TestD2dOffScreenRendering_Fill, "graphics") +{ + TestRenderer renderer(800, 800); + + std::vector > nodes; + + auto loc = Point(10, 10); + + addShapes(loc, nodes, true); + + auto scene = renderer.getScene(); + for (const auto& node : nodes) + { + scene->addNode(node.get()); + } + + renderer.writeSvg(TestUtils::getTestOutputDir(__FILE__) / "fill.svg"); + renderer.write(TestUtils::getTestOutputDir(__FILE__) / "fill.png"); }; \ No newline at end of file