/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "modules/svg/include/SkSVGGradient.h" #include "include/core/SkM44.h" #include "include/core/SkPaint.h" #include "include/core/SkShader.h" // IWYU pragma: keep #include "include/core/SkSize.h" #include "include/core/SkTileMode.h" #include "include/private/base/SkAssert.h" #include "include/private/base/SkDebug.h" #include "include/private/base/SkTPin.h" #include "modules/svg/include/SkSVGAttributeParser.h" #include "modules/svg/include/SkSVGRenderContext.h" #include "modules/svg/include/SkSVGStop.h" #include #include bool SkSVGGradient::parseAndSetAttribute(const char* name, const char* value) { return INHERITED::parseAndSetAttribute(name, value) || this->setGradientTransform(SkSVGAttributeParser::parse( "gradientTransform", name, value)) || this->setHref(SkSVGAttributeParser::parse("xlink:href", name, value)) || this->setSpreadMethod( SkSVGAttributeParser::parse("spreadMethod", name, value)) || this->setGradientUnits(SkSVGAttributeParser::parse( "gradientUnits", name, value)); } // https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementHrefAttribute void SkSVGGradient::collectColorStops(const SkSVGRenderContext& ctx, StopPositionArray* pos, StopColorArray* colors) const { // Used to resolve percentage offsets. const SkSVGLengthContext ltx(SkSize::Make(1, 1)); this->forEachChild([&](const SkSVGStop* stop) { colors->push_back(this->resolveStopColor(ctx, *stop)); pos->push_back( SkTPin(ltx.resolve(stop->getOffset(), SkSVGLengthContext::LengthType::kOther), 0.f, 1.f)); }); SkASSERT(colors->size() == pos->size()); if (pos->empty() && !fHref.iri().isEmpty()) { const auto ref = ctx.findNodeById(fHref); if (ref && (ref->tag() == SkSVGTag::kLinearGradient || ref->tag() == SkSVGTag::kRadialGradient)) { static_cast(ref.get())->collectColorStops(ctx, pos, colors); } } } SkColor4f SkSVGGradient::resolveStopColor(const SkSVGRenderContext& ctx, const SkSVGStop& stop) const { const auto& stopColor = stop.getStopColor(); const auto& stopOpacity = stop.getStopOpacity(); // Uninherited presentation attrs should have a concrete value at this point. if (!stopColor.isValue() || !stopOpacity.isValue()) { SkDEBUGF("unhandled: stop-color or stop-opacity has no value\n"); return SkColors::kBlack; } const auto color = SkColor4f::FromColor(ctx.resolveSvgColor(*stopColor)); return { color.fR, color.fG, color.fB, *stopOpacity * color.fA }; } bool SkSVGGradient::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const { StopColorArray colors; StopPositionArray pos; this->collectColorStops(ctx, &pos, &colors); // TODO: // * stop (lazy?) sorting // * href loop detection // * href attribute inheritance (not just color stops) // * objectBoundingBox units support static_assert(static_cast(SkSVGSpreadMethod::Type::kPad) == SkTileMode::kClamp, "SkSVGSpreadMethod::Type is out of sync"); static_assert(static_cast(SkSVGSpreadMethod::Type::kRepeat) == SkTileMode::kRepeat, "SkSVGSpreadMethod::Type is out of sync"); static_assert(static_cast(SkSVGSpreadMethod::Type::kReflect) == SkTileMode::kMirror, "SkSVGSpreadMethod::Type is out of sync"); const auto tileMode = static_cast(fSpreadMethod.type()); const auto obbt = ctx.transformForCurrentOBB(fGradientUnits); const auto localMatrix = SkMatrix::Translate(obbt.offset.x, obbt.offset.y) * SkMatrix::Scale(obbt.scale.x, obbt.scale.y) * fGradientTransform; paint->setShader(this->onMakeShader(ctx, colors.begin(), pos.begin(), colors.size(), tileMode, localMatrix)); return true; } // https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementSpreadMethodAttribute template <> bool SkSVGAttributeParser::parse(SkSVGSpreadMethod* spread) { static const struct { SkSVGSpreadMethod::Type fType; const char* fName; } gSpreadInfo[] = { { SkSVGSpreadMethod::Type::kPad , "pad" }, { SkSVGSpreadMethod::Type::kReflect, "reflect" }, { SkSVGSpreadMethod::Type::kRepeat , "repeat" }, }; bool parsedValue = false; for (size_t i = 0; i < std::size(gSpreadInfo); ++i) { if (this->parseExpectedStringToken(gSpreadInfo[i].fName)) { *spread = SkSVGSpreadMethod(gSpreadInfo[i].fType); parsedValue = true; break; } } return parsedValue && this->parseEOSToken(); }