xref: /aosp_15_r20/external/skia/modules/svg/src/SkSVGGradient.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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