Skip to content

Commit

Permalink
Add linear gradient unit
Browse files Browse the repository at this point in the history
  • Loading branch information
hoangfitus committed Dec 21, 2023
1 parent 5f4ca2e commit 3fdfafd
Show file tree
Hide file tree
Showing 16 changed files with 174 additions and 44 deletions.
16 changes: 13 additions & 3 deletions external/samples/gradient/linear.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 30 additions & 16 deletions src/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,14 @@ SVGElement *Parser::parseElements(std::string file_name) {
doc.parse< 0 >(&buffer[0]);

xml_node<> *svg = doc.first_node();
viewBox.second.x = getFloatAttribute(svg, "width");
viewBox.second.y = getFloatAttribute(svg, "height");
std::string viewBox = getAttribute(svg, "viewBox");
std::stringstream ss(viewBox);
ss >> this->viewBox.first.x >> this->viewBox.first.y >>
this->viewBox.second.x >> this->viewBox.second.y;
if (viewBox != "") {
std::stringstream ss(viewBox);
ss >> this->viewBox.first.x >> this->viewBox.first.y >>
this->viewBox.second.x >> this->viewBox.second.y;
}
xml_node<> *node = svg->first_node();
xml_node<> *prev = NULL;

Expand Down Expand Up @@ -300,6 +304,8 @@ std::string Parser::getAttribute(xml_node<> *node, std::string name) {
result = "start";
else if (name == "fill-rule")
result = "nonzero";
else if (name == "gradientUnits")
result = "objectBoundingBox";
} else {
result = node->first_attribute(name.c_str())->value();
}
Expand Down Expand Up @@ -408,18 +414,20 @@ void Parser::GetGradients(xml_node<> *node) {
while (gradient_node) {
if (std::string(gradient_node->name()) == "linearGradient") {
std::string id = getAttribute(gradient_node, "id");
std::string units = getAttribute(gradient_node, "gradientUnits");
float x1 = getFloatAttribute(gradient_node, "x1");
float y1 = getFloatAttribute(gradient_node, "y1");
float x2 = getFloatAttribute(gradient_node, "x2");
float y2 = getFloatAttribute(gradient_node, "y2");
std::vector< Stop > stops = getGradientStops(gradient_node);
std::pair< Vector2Df, Vector2Df > points = {{x1, y1}, {x2, y2}};
Gradient *gradient = new LinearGradient(stops, points);
Gradient *gradient = new LinearGradient(stops, points, units);
gradient->setTransforms(getTransformOrder(gradient_node));
if (this->gradients.find(id) == this->gradients.end())
this->gradients[id] = gradient;
} else if (std::string(gradient_node->name()) == "radialGradient") {
std::string id = getAttribute(gradient_node, "id");
std::string units = getAttribute(gradient_node, "gradientUnits");
float cx = getFloatAttribute(gradient_node, "cx");
float cy = getFloatAttribute(gradient_node, "cy");
float fx = getFloatAttribute(gradient_node, "fx");
Expand All @@ -429,7 +437,8 @@ void Parser::GetGradients(xml_node<> *node) {
std::vector< Stop > stops = getGradientStops(gradient_node);
std::pair< Vector2Df, Vector2Df > points = {{cx, cy}, {fx, fy}};
Vector2Df radius(r, fr);
Gradient *gradient = new RadialGradient(stops, points, radius);
Gradient *gradient =
new RadialGradient(stops, points, radius, units);
gradient->setTransforms(getTransformOrder(gradient_node));
if (this->gradients.find(id) == this->gradients.end())
this->gradients[id] = gradient;
Expand Down Expand Up @@ -639,10 +648,20 @@ SVGElement *Parser::parseShape(xml_node<> *node) {
} else if (type == "path") {
shape = parsePath(node, fill_color, stroke_color, stroke_width);
} else if (type == "text") {
return parseText(node, fill_color, stroke_color, stroke_width);
shape = parseText(node, fill_color, stroke_color, stroke_width);
}
if (shape != NULL) {
shape->setTransforms(getTransformOrder(node));
if (type == "text") {
float dx = getFloatAttribute(node, "dx");
float dy = getFloatAttribute(node, "dy");
std::string transform = "translate(" + std::to_string(dx) + " " +
std::to_string(dy) + ")";
std::vector< std::string > transform_order =
getTransformOrder(node);
transform_order.push_back(transform);
shape->setTransforms(transform_order);
} else
shape->setTransforms(getTransformOrder(node));
if (id != "") {
shape->setGradient(parseGradient(id));
}
Expand Down Expand Up @@ -728,8 +747,10 @@ Text *Parser::parseText(xml_node<> *node, const mColor &fill_color,
float font_size = getFloatAttribute(node, "font-size");
std::string text = getAttribute(node, "text");

Text *shape = new Text(Vector2Df(x - 7, y - font_size + 5), text, font_size,
fill_color, stroke_color, stroke_width);
Text *shape =
new Text(Vector2Df(x - (font_size * 6.6 / 40),
y - font_size + (font_size * 4.4 / 40)),
text, font_size, fill_color, stroke_color, stroke_width);

std::string anchor = getAttribute(node, "text-anchor");
anchor.erase(std::remove(anchor.begin(), anchor.end(), ' '), anchor.end());
Expand All @@ -739,13 +760,6 @@ Text *Parser::parseText(xml_node<> *node, const mColor &fill_color,
style.erase(std::remove(style.begin(), style.end(), ' '), style.end());
shape->setFontStyle(style);

float dx = getFloatAttribute(node, "dx");
float dy = getFloatAttribute(node, "dy");
std::string transform =
"translate(" + std::to_string(dx) + " " + std::to_string(dy) + ")";
std::vector< std::string > transform_order = getTransformOrder(node);
transform_order.push_back(transform);
shape->setTransforms(transform_order);
return shape;
}

Expand Down
54 changes: 39 additions & 15 deletions src/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ void Renderer::drawRectangle(Gdiplus::Graphics& graphics,
Gdiplus::Pen rect_outline(Gdiplus::Color(outline_color.a, outline_color.r,
outline_color.g, outline_color.b),
rectangle->getOutlineThickness());
Gdiplus::Brush* rect_fill = getBrush(rectangle);
Gdiplus::RectF bound(x, y, width, height);
Gdiplus::Brush* rect_fill = getBrush(rectangle, bound);
if (rectangle->getRadius().x != 0 || rectangle->getRadius().y != 0) {
float dx = rectangle->getRadius().x * 2;
float dy = rectangle->getRadius().y * 2;
Expand All @@ -147,7 +148,11 @@ void Renderer::drawCircle(Gdiplus::Graphics& graphics, Circle* circle) const {
Gdiplus::Color(outline_color.a, outline_color.r, outline_color.g,
outline_color.b),
circle->getOutlineThickness());
Gdiplus::Brush* circle_fill = getBrush(circle);
Vector2Df min_bound = circle->getMinBound();
Vector2Df max_bound = circle->getMaxBound();
Gdiplus::RectF bound(min_bound.x, min_bound.y, max_bound.x - min_bound.x,
max_bound.y - min_bound.y);
Gdiplus::Brush* circle_fill = getBrush(circle, bound);
graphics.FillEllipse(circle_fill,
circle->getPosition().x - circle->getRadius().x,
circle->getPosition().y - circle->getRadius().y,
Expand All @@ -165,7 +170,11 @@ void Renderer::drawEllipse(Gdiplus::Graphics& graphics, Ell* ellipse) const {
Gdiplus::Color(outline_color.a, outline_color.r, outline_color.g,
outline_color.b),
ellipse->getOutlineThickness());
Gdiplus::Brush* ellipse_fill = getBrush(ellipse);
Vector2Df min_bound = ellipse->getMinBound();
Vector2Df max_bound = ellipse->getMaxBound();
Gdiplus::RectF bound(min_bound.x, min_bound.y, max_bound.x - min_bound.x,
max_bound.y - min_bound.y);
Gdiplus::Brush* ellipse_fill = getBrush(ellipse, bound);
graphics.FillEllipse(
ellipse_fill, ellipse->getPosition().x - ellipse->getRadius().x,
ellipse->getPosition().y - ellipse->getRadius().y,
Expand All @@ -183,7 +192,6 @@ void Renderer::drawPolygon(Gdiplus::Graphics& graphics, Plygon* polygon) const {
Gdiplus::Color(outline_color.a, outline_color.r, outline_color.g,
outline_color.b),
polygon->getOutlineThickness());
Gdiplus::Brush* polygon_fill = getBrush(polygon);

Gdiplus::PointF* points = new Gdiplus::PointF[polygon->getPoints().size()];
int idx = 0;
Expand All @@ -198,6 +206,11 @@ void Renderer::drawPolygon(Gdiplus::Graphics& graphics, Plygon* polygon) const {
} else if (polygon->getFillRule() == "nonzero") {
fill_mode = Gdiplus::FillModeWinding;
}
Vector2Df min_bound = polygon->getMinBound();
Vector2Df max_bound = polygon->getMaxBound();
Gdiplus::RectF bound(min_bound.x, min_bound.y, max_bound.x - min_bound.x,
max_bound.y - min_bound.y);
Gdiplus::Brush* polygon_fill = getBrush(polygon, bound);
graphics.FillPolygon(polygon_fill, points, idx, fill_mode);
graphics.DrawPolygon(&polygon_outline, points, idx);
delete[] points;
Expand All @@ -209,7 +222,6 @@ void Renderer::drawPolygon(Gdiplus::Graphics& graphics, Plygon* polygon) const {
void Renderer::drawText(Gdiplus::Graphics& graphics, Text* text) const {
mColor outline_color = text->getOutlineColor();
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit);
Gdiplus::Brush* text_fill = getBrush(text);

Gdiplus::Pen text_outline(Gdiplus::Color(outline_color.a, outline_color.r,
outline_color.g, outline_color.b),
Expand Down Expand Up @@ -240,6 +252,9 @@ void Renderer::drawText(Gdiplus::Graphics& graphics, Text* text) const {

path.AddString(wide_content.c_str(), wide_content.size(), &font_family,
font_style, text->getFontSize(), position, &string_format);
Gdiplus::RectF bound;
path.GetBounds(&bound);
Gdiplus::Brush* text_fill = getBrush(text, bound);
graphics.FillPath(text_fill, &path);
graphics.DrawPath(&text_outline, &path);
delete text_fill;
Expand All @@ -252,7 +267,6 @@ void Renderer::drawPolyline(Gdiplus::Graphics& graphics,
Gdiplus::Color(outline_color.a, outline_color.r, outline_color.g,
outline_color.b),
polyline->getOutlineThickness());
Gdiplus::Brush* polyline_fill = getBrush(polyline);

Gdiplus::FillMode fill_mode;
if (polyline->getFillRule() == "evenodd") {
Expand All @@ -272,6 +286,11 @@ void Renderer::drawPolyline(Gdiplus::Graphics& graphics,
path.AddLine(points[i - 1].x, points[i - 1].y, points[i].x,
points[i].y);
}
Vector2Df min_bound = polyline->getMinBound();
Vector2Df max_bound = polyline->getMaxBound();
Gdiplus::RectF bound(min_bound.x, min_bound.y, max_bound.x - min_bound.x,
max_bound.y - min_bound.y);
Gdiplus::Brush* polyline_fill = getBrush(polyline, bound);
graphics.FillPath(polyline_fill, &path);
graphics.DrawPath(&polyline_outline, &path);
delete polyline_fill;
Expand All @@ -282,7 +301,6 @@ void Renderer::drawPath(Gdiplus::Graphics& graphics, Path* path) const {
Gdiplus::Pen path_outline(Gdiplus::Color(outline_color.a, outline_color.r,
outline_color.g, outline_color.b),
path->getOutlineThickness());
Gdiplus::Brush* path_fill = getBrush(path);

Gdiplus::FillMode fill_mode;
if (path->getFillRule() == "evenodd") {
Expand Down Expand Up @@ -373,12 +391,16 @@ void Renderer::drawPath(Gdiplus::Graphics& graphics, Path* path) const {
cur_point = points[i].point;
}
}
Gdiplus::RectF bound;
gdi_path.GetBounds(&bound);
Gdiplus::Brush* path_fill = getBrush(path, bound);
graphics.FillPath(path_fill, &gdi_path);
graphics.DrawPath(&path_outline, &gdi_path);
delete path_fill;
}

Gdiplus::Brush* Renderer::getBrush(SVGElement* shape) const {
Gdiplus::Brush* Renderer::getBrush(SVGElement* shape,
Gdiplus::RectF bound) const {
Gradient* gradient = shape->getGradient();
if (gradient != NULL) {
if (gradient->getClass() == "LinearGradient") {
Expand All @@ -403,16 +425,18 @@ Gdiplus::Brush* Renderer::getBrush(SVGElement* shape) const {
stops[i - 1].getColor().g, stops[i - 1].getColor().b);
offsets[i] = stops[i - 1].getOffset();
}
if (gradient->getUnits() == "userSpaceOnUse") {
bound.X = points.first.x;
bound.Y = points.first.y;
bound.Width = points.second.x;
bound.Height = points.second.y;
}
Gdiplus::LinearGradientBrush* fill =
new Gdiplus::LinearGradientBrush(
Gdiplus::PointF(points.first.x, points.first.y),
Gdiplus::PointF(points.second.x, points.second.y),
colors[0], colors[stop_size - 1]);
bound, colors[0], colors[stop_size - 1],
Gdiplus::LinearGradientMode::LinearGradientModeHorizontal);
fill->SetInterpolationColors(colors, offsets, stop_size);

// nếu hình nằm ở một vùng của gradient thì ta chỉ thấy 1 phần
// chuyển màu của vùng đó -> cần phải sửa lại

fill->SetWrapMode(Gdiplus::WrapModeTileFlipXY);
applyTransformsOnBrush(gradient->getTransforms(), fill);
delete[] colors;
delete[] offsets;
Expand Down
3 changes: 2 additions & 1 deletion src/Renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,10 @@ class Renderer {
* @brief Gets the Gdiplus::brush object for the shape fill.
*
* @param shape The SVGElement representing the shape.
* @param bound The bounding box of the shape.
* @return The Gdiplus::brush object for the shape fill.
*/
Gdiplus::Brush* getBrush(SVGElement* shape) const;
Gdiplus::Brush* getBrush(SVGElement* shape, Gdiplus::RectF bound) const;

/**
* @brief Utility function to apply a series of transformations to the brush
Expand Down
10 changes: 10 additions & 0 deletions src/graphics/Ellipse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ void Ell::setRadius(const Vector2Df &radius) { this->radius = radius; }

Vector2Df Ell::getRadius() const { return radius; }

Vector2Df Ell::getMinBound() const {
return Vector2Df(getPosition().x - getRadius().x,
getPosition().y - getRadius().y);
}

Vector2Df Ell::getMaxBound() const {
return Vector2Df(getPosition().x + getRadius().x,
getPosition().y + getRadius().y);
}

void Ell::printData() const {
SVGElement::printData();
std::cout << "Radius: " << getRadius().x << " " << getRadius().y
Expand Down
14 changes: 14 additions & 0 deletions src/graphics/Ellipse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ class Ell : public SVGElement {
*/
Vector2Df getRadius() const;

/**
* @brief Gets the minimum bounding box of the shape.
*
* @return The minimum bounding box of the shape.
*/
Vector2Df getMinBound() const override;

/**
* @brief Gets the maximum bounding box of the shape.
*
* @return The maximum bounding box of the shape.
*/
Vector2Df getMaxBound() const override;

/**
* @brief Prints the data of the shape.
*
Expand Down
4 changes: 2 additions & 2 deletions src/graphics/Gradient.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include "Gradient.hpp"

Gradient::Gradient(std::vector< Stop > stops,
std::pair< Vector2Df, Vector2Df > points)
: stops(stops), points(points) {}
std::pair< Vector2Df, Vector2Df > points, std::string units)
: stops(stops), points(points), units(units) {}

std::vector< Stop > Gradient::getStops() const { return stops; }

Expand Down
3 changes: 2 additions & 1 deletion src/graphics/Gradient.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ class Gradient {
*
* @param stops The stops of the gradient.
* @param points The start and end points of the gradient.
* @param units The units of the gradient.
*/
Gradient(std::vector< Stop > stops,
std::pair< Vector2Df, Vector2Df > points);
std::pair< Vector2Df, Vector2Df > points, std::string units);

/**
* @brief Destructs a Gradient object.
Expand Down
5 changes: 3 additions & 2 deletions src/graphics/LinearGradient.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#include "LinearGradient.hpp"

LinearGradient::LinearGradient(std::vector< Stop > stops,
std::pair< Vector2Df, Vector2Df > points)
: Gradient(stops, points) {}
std::pair< Vector2Df, Vector2Df > points,
std::string units)
: Gradient(stops, points, units) {}

std::string LinearGradient::getClass() const { return "LinearGradient"; }
3 changes: 2 additions & 1 deletion src/graphics/LinearGradient.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ class LinearGradient : public Gradient {
*
* @param stops The stops of the gradient.
* @param points The start and end points of the gradient.
* @param units The units of the gradient.
*/
LinearGradient(std::vector< Stop > stops,
std::pair< Vector2Df, Vector2Df > points);
std::pair< Vector2Df, Vector2Df > points, std::string units);

/**
* @brief Gets the type of the gradient.
Expand Down
Loading

0 comments on commit 3fdfafd

Please sign in to comment.