/* * 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 "experimental/svg/model/SkSVGGradient.h" #include "experimental/svg/model/SkSVGRenderContext.h" #include "experimental/svg/model/SkSVGStop.h" #include "experimental/svg/model/SkSVGValue.h" void SkSVGGradient::setHref(const SkSVGStringType& href) { fHref = std::move(href); } void SkSVGGradient::setGradientTransform(const SkSVGTransformType& t) { fGradientTransform = t; } void SkSVGGradient::setSpreadMethod(const SkSVGSpreadMethod& spread) { fSpreadMethod = spread; } void SkSVGGradient::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) { switch (attr) { case SkSVGAttribute::kGradientTransform: if (const auto* t = v.as()) { this->setGradientTransform(*t); } break; case SkSVGAttribute::kHref: if (const auto* href = v.as()) { this->setHref(*href); } break; case SkSVGAttribute::kSpreadMethod: if (const auto* spread = v.as()) { this->setSpreadMethod(*spread); } break; default: this->INHERITED::onSetAttribute(attr, v); } } // 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)); for (const auto& child : fChildren) { if (child->tag() != SkSVGTag::kStop) { continue; } const auto& stop = static_cast(*child); colors->push_back(this->resolveStopColor(ctx, stop)); pos->push_back(SkTPin(ltx.resolve(stop.offset(), SkSVGLengthContext::LengthType::kOther), 0.f, 1.f)); } SkASSERT(colors->count() == pos->count()); if (pos->empty() && !fHref.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); } } } SkColor SkSVGGradient::resolveStopColor(const SkSVGRenderContext& ctx, const SkSVGStop& stop) const { const SkSVGStopColor& stopColor = stop.stopColor(); SkColor color; switch (stopColor.type()) { case SkSVGStopColor::Type::kColor: color = stopColor.color(); break; case SkSVGStopColor::Type::kCurrentColor: color = *ctx.presentationContext().fInherited.fColor; break; case SkSVGStopColor::Type::kICCColor: SkDebugf("unimplemented 'icccolor' stop-color type\n"); color = SK_ColorBLACK; break; case SkSVGStopColor::Type::kInherit: SkDebugf("unimplemented 'inherit' stop-color type\n"); color = SK_ColorBLACK; break; } return SkColorSetA(color, SkScalarRoundToInt(stop.stopOpacity() * 255)); } 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()); paint->setShader(this->onMakeShader(ctx, colors.begin(), pos.begin(), colors.count(), tileMode, fGradientTransform)); return true; }