Files
autosample/vendor/lunasvg/source/svgpaintelement.cpp

365 lines
15 KiB
C++

#include "svgpaintelement.h"
#include "svglayoutstate.h"
#include "svgrenderstate.h"
#include <cmath>
#include <set>
namespace lunasvg {
SVGPaintElement::SVGPaintElement(Document* document, ElementID id)
: SVGElement(document, id)
{
}
SVGStopElement::SVGStopElement(Document* document)
: SVGElement(document, ElementID::Stop)
, m_offset(PropertyID::Offset, 0.f)
{
addProperty(m_offset);
}
void SVGStopElement::layoutElement(const SVGLayoutState& state)
{
m_stop_color = state.stop_color();
m_stop_opacity = state.stop_opacity();
SVGElement::layoutElement(state);
}
GradientStop SVGStopElement::gradientStop(float opacity) const
{
Color stopColor = m_stop_color.colorWithAlpha(m_stop_opacity * opacity);
GradientStop gradientStop = {
m_offset.value(), { stopColor.redF(), stopColor.greenF(), stopColor.blueF(), stopColor.alphaF() }
};
return gradientStop;
}
SVGGradientElement::SVGGradientElement(Document* document, ElementID id)
: SVGPaintElement(document, id)
, SVGURIReference(this)
, m_gradientTransform(PropertyID::GradientTransform)
, m_gradientUnits(PropertyID::GradientUnits, Units::ObjectBoundingBox)
, m_spreadMethod(PropertyID::SpreadMethod, SpreadMethod::Pad)
{
addProperty(m_gradientTransform);
addProperty(m_gradientUnits);
addProperty(m_spreadMethod);
}
void SVGGradientElement::collectGradientAttributes(SVGGradientAttributes& attributes) const
{
if(!attributes.hasGradientTransform() && hasAttribute(PropertyID::GradientTransform))
attributes.setGradientTransform(this);
if(!attributes.hasSpreadMethod() && hasAttribute(PropertyID::SpreadMethod))
attributes.setSpreadMethod(this);
if(!attributes.hasGradientUnits() && hasAttribute(PropertyID::GradientUnits))
attributes.setGradientUnits(this);
if(!attributes.hasGradientContentElement()) {
for(const auto& child : children()) {
if(auto element = toSVGElement(child); element && element->id() == ElementID::Stop) {
attributes.setGradientContentElement(this);
break;
}
}
}
}
SVGLinearGradientElement::SVGLinearGradientElement(Document* document)
: SVGGradientElement(document, ElementID::LinearGradient)
, m_x1(PropertyID::X1, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
, m_y1(PropertyID::Y1, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
, m_x2(PropertyID::X2, LengthDirection::Horizontal, LengthNegativeMode::Allow, 100.f, LengthUnits::Percent)
, m_y2(PropertyID::Y2, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
{
addProperty(m_x1);
addProperty(m_y1);
addProperty(m_x2);
addProperty(m_y2);
}
SVGLinearGradientAttributes SVGLinearGradientElement::collectGradientAttributes() const
{
SVGLinearGradientAttributes attributes;
std::set<const SVGGradientElement*> processedGradients;
const SVGGradientElement* current = this;
while(true) {
current->collectGradientAttributes(attributes);
if(current->id() == ElementID::LinearGradient) {
auto element = static_cast<const SVGLinearGradientElement*>(current);
if(!attributes.hasX1() && element->hasAttribute(PropertyID::X1))
attributes.setX1(element);
if(!attributes.hasY1() && element->hasAttribute(PropertyID::Y1))
attributes.setY1(element);
if(!attributes.hasX2() && element->hasAttribute(PropertyID::X2))
attributes.setX2(element);
if(!attributes.hasY2() && element->hasAttribute(PropertyID::Y2)) {
attributes.setY2(element);
}
}
auto targetElement = current->getTargetElement(document());
if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
break;
processedGradients.insert(current);
current = static_cast<const SVGGradientElement*>(targetElement);
if(processedGradients.count(current) > 0) {
break;
}
}
attributes.setDefaultValues(this);
return attributes;
}
static GradientStops buildGradientStops(const SVGGradientElement* element, float opacity)
{
GradientStops gradientStops;
const auto& children = element->children();
gradientStops.reserve(children.size());
for(const auto& child : children) {
auto childElement = toSVGElement(child);
if(childElement && childElement->id() == ElementID::Stop) {
auto stopElement = static_cast<SVGStopElement*>(childElement);
gradientStops.push_back(stopElement->gradientStop(opacity));
}
}
return gradientStops;
}
bool SVGLinearGradientElement::applyPaint(SVGRenderState& state, float opacity) const
{
auto attributes = collectGradientAttributes();
auto gradientContentElement = attributes.gradientContentElement();
auto gradientStops = buildGradientStops(gradientContentElement, opacity);
if(gradientStops.empty())
return false;
LengthContext lengthContext(this, attributes.gradientUnits());
auto x1 = lengthContext.valueForLength(attributes.x1());
auto y1 = lengthContext.valueForLength(attributes.y1());
auto x2 = lengthContext.valueForLength(attributes.x2());
auto y2 = lengthContext.valueForLength(attributes.y2());
if(gradientStops.size() == 1 || (x1 == x2 && y1 == y2)) {
const auto& lastStop = gradientStops.back();
state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
return true;
}
auto spreadMethod = attributes.spreadMethod();
auto gradientUnits = attributes.gradientUnits();
auto gradientTransform = attributes.gradientTransform();
if(gradientUnits == Units::ObjectBoundingBox) {
auto bbox = state.fillBoundingBox();
gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
}
state->setLinearGradient(x1, y1, x2, y2, spreadMethod, gradientStops, gradientTransform);
return true;
}
SVGRadialGradientElement::SVGRadialGradientElement(Document* document)
: SVGGradientElement(document, ElementID::RadialGradient)
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent)
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent)
, m_r(PropertyID::R, LengthDirection::Diagonal, LengthNegativeMode::Forbid, 50.f, LengthUnits::Percent)
, m_fx(PropertyID::Fx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
, m_fy(PropertyID::Fy, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
{
addProperty(m_cx);
addProperty(m_cy);
addProperty(m_r);
addProperty(m_fx);
addProperty(m_fy);
}
bool SVGRadialGradientElement::applyPaint(SVGRenderState& state, float opacity) const
{
auto attributes = collectGradientAttributes();
auto gradientContentElement = attributes.gradientContentElement();
auto gradientStops = buildGradientStops(gradientContentElement, opacity);
if(gradientStops.empty())
return false;
LengthContext lengthContext(this, attributes.gradientUnits());
auto r = lengthContext.valueForLength(attributes.r());
if(r == 0.f || gradientStops.size() == 1) {
const auto& lastStop = gradientStops.back();
state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
return true;
}
auto fx = lengthContext.valueForLength(attributes.fx());
auto fy = lengthContext.valueForLength(attributes.fy());
auto cx = lengthContext.valueForLength(attributes.cx());
auto cy = lengthContext.valueForLength(attributes.cy());
auto spreadMethod = attributes.spreadMethod();
auto gradientUnits = attributes.gradientUnits();
auto gradientTransform = attributes.gradientTransform();
if(gradientUnits == Units::ObjectBoundingBox) {
auto bbox = state.fillBoundingBox();
gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
}
state->setRadialGradient(cx, cy, r, fx, fy, spreadMethod, gradientStops, gradientTransform);
return true;
}
SVGRadialGradientAttributes SVGRadialGradientElement::collectGradientAttributes() const
{
SVGRadialGradientAttributes attributes;
std::set<const SVGGradientElement*> processedGradients;
const SVGGradientElement* current = this;
while(true) {
current->collectGradientAttributes(attributes);
if(current->id() == ElementID::RadialGradient) {
auto element = static_cast<const SVGRadialGradientElement*>(current);
if(!attributes.hasCx() && element->hasAttribute(PropertyID::Cx))
attributes.setCx(element);
if(!attributes.hasCy() && element->hasAttribute(PropertyID::Cy))
attributes.setCy(element);
if(!attributes.hasR() && element->hasAttribute(PropertyID::R))
attributes.setR(element);
if(!attributes.hasFx() && element->hasAttribute(PropertyID::Fx))
attributes.setFx(element);
if(!attributes.hasFy() && element->hasAttribute(PropertyID::Fy)) {
attributes.setFy(element);
}
}
auto targetElement = current->getTargetElement(document());
if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
break;
processedGradients.insert(current);
current = static_cast<const SVGGradientElement*>(targetElement);
if(processedGradients.count(current) > 0) {
break;
}
}
attributes.setDefaultValues(this);
return attributes;
}
SVGPatternElement::SVGPatternElement(Document* document)
: SVGPaintElement(document, ElementID::Pattern)
, SVGURIReference(this)
, SVGFitToViewBox(this)
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow)
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow)
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid)
, m_patternTransform(PropertyID::PatternTransform)
, m_patternUnits(PropertyID::PatternUnits, Units::ObjectBoundingBox)
, m_patternContentUnits(PropertyID::PatternContentUnits, Units::UserSpaceOnUse)
{
addProperty(m_x);
addProperty(m_y);
addProperty(m_width);
addProperty(m_height);
addProperty(m_patternTransform);
addProperty(m_patternUnits);
addProperty(m_patternContentUnits);
}
bool SVGPatternElement::applyPaint(SVGRenderState& state, float opacity) const
{
if(state.hasCycleReference(this))
return false;
auto attributes = collectPatternAttributes();
auto patternContentElement = attributes.patternContentElement();
if(patternContentElement == nullptr)
return false;
LengthContext lengthContext(this, attributes.patternUnits());
Rect patternRect = {
lengthContext.valueForLength(attributes.x()),
lengthContext.valueForLength(attributes.y()),
lengthContext.valueForLength(attributes.width()),
lengthContext.valueForLength(attributes.height())
};
if(attributes.patternUnits() == Units::ObjectBoundingBox) {
auto bbox = state.fillBoundingBox();
patternRect.x = patternRect.x * bbox.w + bbox.x;
patternRect.y = patternRect.y * bbox.h + bbox.y;
patternRect.w = patternRect.w * bbox.w;
patternRect.h = patternRect.h * bbox.h;
}
auto currentTransform = attributes.patternTransform() * state.currentTransform();
auto xScale = currentTransform.xScale();
auto yScale = currentTransform.yScale();
auto patternImage = Canvas::create(0, 0, patternRect.w * xScale, patternRect.h * yScale);
auto patternImageTransform = Transform::scaled(xScale, yScale);
const auto& viewBoxRect = attributes.viewBox();
if(viewBoxRect.isValid()) {
const auto& preserveAspectRatio = attributes.preserveAspectRatio();
patternImageTransform.multiply(preserveAspectRatio.getTransform(viewBoxRect, patternRect.size()));
} else if(attributes.patternContentUnits() == Units::ObjectBoundingBox) {
auto bbox = state.fillBoundingBox();
patternImageTransform.scale(bbox.w, bbox.h);
}
SVGRenderState newState(this, &state, patternImageTransform, SVGRenderMode::Painting, patternImage);
patternContentElement->renderChildren(newState);
auto patternTransform = attributes.patternTransform();
patternTransform.translate(patternRect.x, patternRect.y);
patternTransform.scale(patternRect.w / patternImage->width(), patternRect.h / patternImage->height());
state->setTexture(*patternImage, TextureType::Tiled, opacity, patternTransform);
return true;
}
SVGPatternAttributes SVGPatternElement::collectPatternAttributes() const
{
SVGPatternAttributes attributes;
std::set<const SVGPatternElement*> processedPatterns;
const SVGPatternElement* current = this;
while(true) {
if(!attributes.hasX() && current->hasAttribute(PropertyID::X))
attributes.setX(current);
if(!attributes.hasY() && current->hasAttribute(PropertyID::Y))
attributes.setY(current);
if(!attributes.hasWidth() && current->hasAttribute(PropertyID::Width))
attributes.setWidth(current);
if(!attributes.hasHeight() && current->hasAttribute(PropertyID::Height))
attributes.setHeight(current);
if(!attributes.hasPatternTransform() && current->hasAttribute(PropertyID::PatternTransform))
attributes.setPatternTransform(current);
if(!attributes.hasPatternUnits() && current->hasAttribute(PropertyID::PatternUnits))
attributes.setPatternUnits(current);
if(!attributes.hasPatternContentUnits() && current->hasAttribute(PropertyID::PatternContentUnits))
attributes.setPatternContentUnits(current);
if(!attributes.hasViewBox() && current->hasAttribute(PropertyID::ViewBox))
attributes.setViewBox(current);
if(!attributes.hasPreserveAspectRatio() && current->hasAttribute(PropertyID::PreserveAspectRatio))
attributes.setPreserveAspectRatio(current);
if(!attributes.hasPatternContentElement()) {
for(const auto& child : current->children()) {
if(child->isElement()) {
attributes.setPatternContentElement(current);
break;
}
}
}
auto targetElement = current->getTargetElement(document());
if(!targetElement || targetElement->id() != ElementID::Pattern)
break;
processedPatterns.insert(current);
current = static_cast<const SVGPatternElement*>(targetElement);
if(processedPatterns.count(current) > 0) {
break;
}
}
attributes.setDefaultValues(this);
return attributes;
}
} // namespace lunasvg