1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2017 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "modules/svg/include/SkSVGGradient.h"
9*c8dee2aaSAndroid Build Coastguard Worker
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkM44.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkShader.h" // IWYU pragma: keep
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTileMode.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkDebug.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTPin.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "modules/svg/include/SkSVGAttributeParser.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "modules/svg/include/SkSVGRenderContext.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "modules/svg/include/SkSVGStop.h"
21*c8dee2aaSAndroid Build Coastguard Worker
22*c8dee2aaSAndroid Build Coastguard Worker #include <array>
23*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
24*c8dee2aaSAndroid Build Coastguard Worker
parseAndSetAttribute(const char * name,const char * value)25*c8dee2aaSAndroid Build Coastguard Worker bool SkSVGGradient::parseAndSetAttribute(const char* name, const char* value) {
26*c8dee2aaSAndroid Build Coastguard Worker return INHERITED::parseAndSetAttribute(name, value) ||
27*c8dee2aaSAndroid Build Coastguard Worker this->setGradientTransform(SkSVGAttributeParser::parse<SkSVGTransformType>(
28*c8dee2aaSAndroid Build Coastguard Worker "gradientTransform", name, value)) ||
29*c8dee2aaSAndroid Build Coastguard Worker this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", name, value)) ||
30*c8dee2aaSAndroid Build Coastguard Worker this->setSpreadMethod(
31*c8dee2aaSAndroid Build Coastguard Worker SkSVGAttributeParser::parse<SkSVGSpreadMethod>("spreadMethod", name, value)) ||
32*c8dee2aaSAndroid Build Coastguard Worker this->setGradientUnits(SkSVGAttributeParser::parse<SkSVGObjectBoundingBoxUnits>(
33*c8dee2aaSAndroid Build Coastguard Worker "gradientUnits", name, value));
34*c8dee2aaSAndroid Build Coastguard Worker }
35*c8dee2aaSAndroid Build Coastguard Worker
36*c8dee2aaSAndroid Build Coastguard Worker // https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementHrefAttribute
collectColorStops(const SkSVGRenderContext & ctx,StopPositionArray * pos,StopColorArray * colors) const37*c8dee2aaSAndroid Build Coastguard Worker void SkSVGGradient::collectColorStops(const SkSVGRenderContext& ctx,
38*c8dee2aaSAndroid Build Coastguard Worker StopPositionArray* pos,
39*c8dee2aaSAndroid Build Coastguard Worker StopColorArray* colors) const {
40*c8dee2aaSAndroid Build Coastguard Worker // Used to resolve percentage offsets.
41*c8dee2aaSAndroid Build Coastguard Worker const SkSVGLengthContext ltx(SkSize::Make(1, 1));
42*c8dee2aaSAndroid Build Coastguard Worker
43*c8dee2aaSAndroid Build Coastguard Worker this->forEachChild<SkSVGStop>([&](const SkSVGStop* stop) {
44*c8dee2aaSAndroid Build Coastguard Worker colors->push_back(this->resolveStopColor(ctx, *stop));
45*c8dee2aaSAndroid Build Coastguard Worker pos->push_back(
46*c8dee2aaSAndroid Build Coastguard Worker SkTPin(ltx.resolve(stop->getOffset(), SkSVGLengthContext::LengthType::kOther),
47*c8dee2aaSAndroid Build Coastguard Worker 0.f, 1.f));
48*c8dee2aaSAndroid Build Coastguard Worker });
49*c8dee2aaSAndroid Build Coastguard Worker
50*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(colors->size() == pos->size());
51*c8dee2aaSAndroid Build Coastguard Worker
52*c8dee2aaSAndroid Build Coastguard Worker if (pos->empty() && !fHref.iri().isEmpty()) {
53*c8dee2aaSAndroid Build Coastguard Worker const auto ref = ctx.findNodeById(fHref);
54*c8dee2aaSAndroid Build Coastguard Worker if (ref && (ref->tag() == SkSVGTag::kLinearGradient ||
55*c8dee2aaSAndroid Build Coastguard Worker ref->tag() == SkSVGTag::kRadialGradient)) {
56*c8dee2aaSAndroid Build Coastguard Worker static_cast<const SkSVGGradient*>(ref.get())->collectColorStops(ctx, pos, colors);
57*c8dee2aaSAndroid Build Coastguard Worker }
58*c8dee2aaSAndroid Build Coastguard Worker }
59*c8dee2aaSAndroid Build Coastguard Worker }
60*c8dee2aaSAndroid Build Coastguard Worker
resolveStopColor(const SkSVGRenderContext & ctx,const SkSVGStop & stop) const61*c8dee2aaSAndroid Build Coastguard Worker SkColor4f SkSVGGradient::resolveStopColor(const SkSVGRenderContext& ctx,
62*c8dee2aaSAndroid Build Coastguard Worker const SkSVGStop& stop) const {
63*c8dee2aaSAndroid Build Coastguard Worker const auto& stopColor = stop.getStopColor();
64*c8dee2aaSAndroid Build Coastguard Worker const auto& stopOpacity = stop.getStopOpacity();
65*c8dee2aaSAndroid Build Coastguard Worker // Uninherited presentation attrs should have a concrete value at this point.
66*c8dee2aaSAndroid Build Coastguard Worker if (!stopColor.isValue() || !stopOpacity.isValue()) {
67*c8dee2aaSAndroid Build Coastguard Worker SkDEBUGF("unhandled: stop-color or stop-opacity has no value\n");
68*c8dee2aaSAndroid Build Coastguard Worker return SkColors::kBlack;
69*c8dee2aaSAndroid Build Coastguard Worker }
70*c8dee2aaSAndroid Build Coastguard Worker
71*c8dee2aaSAndroid Build Coastguard Worker const auto color = SkColor4f::FromColor(ctx.resolveSvgColor(*stopColor));
72*c8dee2aaSAndroid Build Coastguard Worker
73*c8dee2aaSAndroid Build Coastguard Worker return { color.fR, color.fG, color.fB, *stopOpacity * color.fA };
74*c8dee2aaSAndroid Build Coastguard Worker }
75*c8dee2aaSAndroid Build Coastguard Worker
onAsPaint(const SkSVGRenderContext & ctx,SkPaint * paint) const76*c8dee2aaSAndroid Build Coastguard Worker bool SkSVGGradient::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const {
77*c8dee2aaSAndroid Build Coastguard Worker StopColorArray colors;
78*c8dee2aaSAndroid Build Coastguard Worker StopPositionArray pos;
79*c8dee2aaSAndroid Build Coastguard Worker
80*c8dee2aaSAndroid Build Coastguard Worker this->collectColorStops(ctx, &pos, &colors);
81*c8dee2aaSAndroid Build Coastguard Worker
82*c8dee2aaSAndroid Build Coastguard Worker // TODO:
83*c8dee2aaSAndroid Build Coastguard Worker // * stop (lazy?) sorting
84*c8dee2aaSAndroid Build Coastguard Worker // * href loop detection
85*c8dee2aaSAndroid Build Coastguard Worker // * href attribute inheritance (not just color stops)
86*c8dee2aaSAndroid Build Coastguard Worker // * objectBoundingBox units support
87*c8dee2aaSAndroid Build Coastguard Worker
88*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kPad) ==
89*c8dee2aaSAndroid Build Coastguard Worker SkTileMode::kClamp, "SkSVGSpreadMethod::Type is out of sync");
90*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kRepeat) ==
91*c8dee2aaSAndroid Build Coastguard Worker SkTileMode::kRepeat, "SkSVGSpreadMethod::Type is out of sync");
92*c8dee2aaSAndroid Build Coastguard Worker static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kReflect) ==
93*c8dee2aaSAndroid Build Coastguard Worker SkTileMode::kMirror, "SkSVGSpreadMethod::Type is out of sync");
94*c8dee2aaSAndroid Build Coastguard Worker const auto tileMode = static_cast<SkTileMode>(fSpreadMethod.type());
95*c8dee2aaSAndroid Build Coastguard Worker
96*c8dee2aaSAndroid Build Coastguard Worker const auto obbt = ctx.transformForCurrentOBB(fGradientUnits);
97*c8dee2aaSAndroid Build Coastguard Worker const auto localMatrix = SkMatrix::Translate(obbt.offset.x, obbt.offset.y)
98*c8dee2aaSAndroid Build Coastguard Worker * SkMatrix::Scale(obbt.scale.x, obbt.scale.y)
99*c8dee2aaSAndroid Build Coastguard Worker * fGradientTransform;
100*c8dee2aaSAndroid Build Coastguard Worker
101*c8dee2aaSAndroid Build Coastguard Worker paint->setShader(this->onMakeShader(ctx, colors.begin(), pos.begin(), colors.size(), tileMode,
102*c8dee2aaSAndroid Build Coastguard Worker localMatrix));
103*c8dee2aaSAndroid Build Coastguard Worker return true;
104*c8dee2aaSAndroid Build Coastguard Worker }
105*c8dee2aaSAndroid Build Coastguard Worker
106*c8dee2aaSAndroid Build Coastguard Worker // https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementSpreadMethodAttribute
107*c8dee2aaSAndroid Build Coastguard Worker template <>
parse(SkSVGSpreadMethod * spread)108*c8dee2aaSAndroid Build Coastguard Worker bool SkSVGAttributeParser::parse(SkSVGSpreadMethod* spread) {
109*c8dee2aaSAndroid Build Coastguard Worker static const struct {
110*c8dee2aaSAndroid Build Coastguard Worker SkSVGSpreadMethod::Type fType;
111*c8dee2aaSAndroid Build Coastguard Worker const char* fName;
112*c8dee2aaSAndroid Build Coastguard Worker } gSpreadInfo[] = {
113*c8dee2aaSAndroid Build Coastguard Worker { SkSVGSpreadMethod::Type::kPad , "pad" },
114*c8dee2aaSAndroid Build Coastguard Worker { SkSVGSpreadMethod::Type::kReflect, "reflect" },
115*c8dee2aaSAndroid Build Coastguard Worker { SkSVGSpreadMethod::Type::kRepeat , "repeat" },
116*c8dee2aaSAndroid Build Coastguard Worker };
117*c8dee2aaSAndroid Build Coastguard Worker
118*c8dee2aaSAndroid Build Coastguard Worker bool parsedValue = false;
119*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < std::size(gSpreadInfo); ++i) {
120*c8dee2aaSAndroid Build Coastguard Worker if (this->parseExpectedStringToken(gSpreadInfo[i].fName)) {
121*c8dee2aaSAndroid Build Coastguard Worker *spread = SkSVGSpreadMethod(gSpreadInfo[i].fType);
122*c8dee2aaSAndroid Build Coastguard Worker parsedValue = true;
123*c8dee2aaSAndroid Build Coastguard Worker break;
124*c8dee2aaSAndroid Build Coastguard Worker }
125*c8dee2aaSAndroid Build Coastguard Worker }
126*c8dee2aaSAndroid Build Coastguard Worker
127*c8dee2aaSAndroid Build Coastguard Worker return parsedValue && this->parseEOSToken();
128*c8dee2aaSAndroid Build Coastguard Worker }
129