| /* |
| * Copyright 2012 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/private/base/SkFloatingPoint.h" |
| #include "src/core/SkRasterPipeline.h" |
| #include "src/core/SkReadBuffer.h" |
| #include "src/core/SkWriteBuffer.h" |
| #include "src/shaders/SkLocalMatrixShader.h" |
| #include "src/shaders/gradients/SkGradientShaderBase.h" |
| |
| #include <utility> |
| |
| #ifdef SK_GRAPHITE_ENABLED |
| #include "src/gpu/graphite/KeyHelpers.h" |
| #include "src/gpu/graphite/PaintParamsKey.h" |
| #endif |
| |
| // Please see https://skia.org/dev/design/conical for how our shader works. |
| |
| class SkTwoPointConicalGradient final : public SkGradientShaderBase { |
| public: |
| // See https://skia.org/dev/design/conical for what focal data means and how our shader works. |
| // We make it public so the GPU shader can also use it. |
| struct FocalData { |
| SkScalar fR1; // r1 after mapping focal point to (0, 0) |
| SkScalar fFocalX; // f |
| bool fIsSwapped; // whether we swapped r0, r1 |
| |
| // The input r0, r1 are the radii when we map centers to {(0, 0), (1, 0)}. |
| // We'll post concat matrix with our transformation matrix that maps focal point to (0, 0). |
| // Returns true if the set succeeded |
| bool set(SkScalar r0, SkScalar r1, SkMatrix* matrix); |
| |
| // Whether the focal point (0, 0) is on the end circle with center (1, 0) and radius r1. If |
| // this is true, it's as if an aircraft is flying at Mach 1 and all circles (soundwaves) |
| // will go through the focal point (aircraft). In our previous implementations, this was |
| // known as the edge case where the inside circle touches the outside circle (on the focal |
| // point). If we were to solve for t bruteforcely using a quadratic equation, this case |
| // implies that the quadratic equation degenerates to a linear equation. |
| bool isFocalOnCircle() const { return SkScalarNearlyZero(1 - fR1); } |
| |
| bool isSwapped() const { return fIsSwapped; } |
| bool isWellBehaved() const { return !this->isFocalOnCircle() && fR1 > 1; } |
| bool isNativelyFocal() const { return SkScalarNearlyZero(fFocalX); } |
| }; |
| |
| enum class Type { |
| kRadial, |
| kStrip, |
| kFocal |
| }; |
| |
| static sk_sp<SkShader> Create(const SkPoint& start, SkScalar startRadius, |
| const SkPoint& end, SkScalar endRadius, |
| const Descriptor&, const SkMatrix* localMatrix); |
| |
| GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override; |
| #if SK_SUPPORT_GPU |
| std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override; |
| #endif |
| #ifdef SK_GRAPHITE_ENABLED |
| void addToKey(const skgpu::graphite::KeyContext&, |
| skgpu::graphite::PaintParamsKeyBuilder*, |
| skgpu::graphite::PipelineDataGatherer*) const override; |
| #endif |
| bool isOpaque() const override; |
| |
| SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); } |
| SkScalar getStartRadius() const { return fRadius1; } |
| SkScalar getDiffRadius() const { return fRadius2 - fRadius1; } |
| const SkPoint& getStartCenter() const { return fCenter1; } |
| const SkPoint& getEndCenter() const { return fCenter2; } |
| SkScalar getEndRadius() const { return fRadius2; } |
| |
| Type getType() const { return fType; } |
| const FocalData& getFocalData() const { return fFocalData; } |
| |
| SkTwoPointConicalGradient(const SkPoint& c0, SkScalar r0, |
| const SkPoint& c1, SkScalar r1, |
| const Descriptor&, Type, const SkMatrix&, const FocalData&); |
| |
| protected: |
| void flatten(SkWriteBuffer& buffer) const override; |
| |
| void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline, |
| SkRasterPipeline* postPipeline) const override; |
| |
| skvm::F32 transformT(skvm::Builder*, skvm::Uniforms*, |
| skvm::Coord coord, skvm::I32* mask) const final; |
| |
| private: |
| friend void ::SkRegisterTwoPointConicalGradientShaderFlattenable(); |
| SK_FLATTENABLE_HOOKS(SkTwoPointConicalGradient) |
| |
| SkPoint fCenter1; |
| SkPoint fCenter2; |
| SkScalar fRadius1; |
| SkScalar fRadius2; |
| Type fType; |
| |
| FocalData fFocalData; |
| }; |
| |
| bool SkTwoPointConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix* matrix) { |
| fIsSwapped = false; |
| fFocalX = sk_ieee_float_divide(r0, (r0 - r1)); |
| if (SkScalarNearlyZero(fFocalX - 1)) { |
| // swap r0, r1 |
| matrix->postTranslate(-1, 0); |
| matrix->postScale(-1, 1); |
| std::swap(r0, r1); |
| fFocalX = 0; // because r0 is now 0 |
| fIsSwapped = true; |
| } |
| |
| // Map {focal point, (1, 0)} to {(0, 0), (1, 0)} |
| const SkPoint from[2] = { {fFocalX, 0}, {1, 0} }; |
| const SkPoint to[2] = { {0, 0}, {1, 0} }; |
| SkMatrix focalMatrix; |
| if (!focalMatrix.setPolyToPoly(from, to, 2)) { |
| return false; |
| } |
| matrix->postConcat(focalMatrix); |
| fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f) |
| |
| // The following transformations are just to accelerate the shader computation by saving |
| // some arithmatic operations. |
| if (this->isFocalOnCircle()) { |
| matrix->postScale(0.5, 0.5); |
| } else { |
| matrix->postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1))); |
| } |
| matrix->postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f| |
| return true; |
| } |
| |
| sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0, |
| const SkPoint& c1, SkScalar r1, |
| const Descriptor& desc, |
| const SkMatrix* localMatrix) { |
| SkMatrix gradientMatrix; |
| Type gradientType; |
| |
| if (SkScalarNearlyZero((c0 - c1).length())) { |
| if (SkScalarNearlyZero(std::max(r0, r1)) || SkScalarNearlyEqual(r0, r1)) { |
| // Degenerate case; avoid dividing by zero. Should have been caught by caller but |
| // just in case, recheck here. |
| return nullptr; |
| } |
| // Concentric case: we can pretend we're radial (with a tiny twist). |
| const SkScalar scale = sk_ieee_float_divide(1, std::max(r0, r1)); |
| gradientMatrix = SkMatrix::Translate(-c1.x(), -c1.y()); |
| gradientMatrix.postScale(scale, scale); |
| |
| gradientType = Type::kRadial; |
| } else { |
| const SkPoint centers[2] = { c0 , c1 }; |
| const SkPoint unitvec[2] = { {0, 0}, {1, 0} }; |
| |
| if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) { |
| // Degenerate case. |
| return nullptr; |
| } |
| |
| gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal; |
| } |
| |
| FocalData focalData; |
| if (gradientType == Type::kFocal) { |
| const auto dCenter = (c0 - c1).length(); |
| if (!focalData.set(r0 / dCenter, r1 / dCenter, &gradientMatrix)) { |
| return nullptr; |
| } |
| } |
| return SkLocalMatrixShader::MakeWrapped<SkTwoPointConicalGradient>(localMatrix, |
| c0, r0, |
| c1, r1, |
| desc, |
| gradientType, |
| gradientMatrix, |
| focalData); |
| } |
| |
| SkTwoPointConicalGradient::SkTwoPointConicalGradient( |
| const SkPoint& start, SkScalar startRadius, |
| const SkPoint& end, SkScalar endRadius, |
| const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data) |
| : SkGradientShaderBase(desc, gradientMatrix) |
| , fCenter1(start) |
| , fCenter2(end) |
| , fRadius1(startRadius) |
| , fRadius2(endRadius) |
| , fType(type) |
| { |
| // this is degenerate, and should be caught by our caller |
| SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2); |
| if (type == Type::kFocal) { |
| fFocalData = data; |
| } |
| } |
| |
| bool SkTwoPointConicalGradient::isOpaque() const { |
| // Because areas outside the cone are left untouched, we cannot treat the |
| // shader as opaque even if the gradient itself is opaque. |
| // TODO(junov): Compute whether the cone fills the plane crbug.com/222380 |
| return false; |
| } |
| |
| // Returns the original non-sorted version of the gradient |
| SkShaderBase::GradientType SkTwoPointConicalGradient::asGradient(GradientInfo* info, |
| SkMatrix* localMatrix) const { |
| if (info) { |
| commonAsAGradient(info); |
| info->fPoint[0] = fCenter1; |
| info->fPoint[1] = fCenter2; |
| info->fRadius[0] = fRadius1; |
| info->fRadius[1] = fRadius2; |
| } |
| if (localMatrix) { |
| *localMatrix = SkMatrix::I(); |
| } |
| return GradientType::kConical; |
| } |
| |
| sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) { |
| DescriptorScope desc; |
| SkMatrix legacyLocalMatrix; |
| if (!desc.unflatten(buffer, &legacyLocalMatrix)) { |
| return nullptr; |
| } |
| SkPoint c1 = buffer.readPoint(); |
| SkPoint c2 = buffer.readPoint(); |
| SkScalar r1 = buffer.readScalar(); |
| SkScalar r2 = buffer.readScalar(); |
| |
| if (!buffer.isValid()) { |
| return nullptr; |
| } |
| return SkGradientShader::MakeTwoPointConical(c1, r1, |
| c2, r2, |
| desc.fColors, |
| std::move(desc.fColorSpace), |
| desc.fPositions, |
| desc.fColorCount, |
| desc.fTileMode, |
| desc.fInterpolation, |
| &legacyLocalMatrix); |
| } |
| |
| void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const { |
| this->SkGradientShaderBase::flatten(buffer); |
| buffer.writePoint(fCenter1); |
| buffer.writePoint(fCenter2); |
| buffer.writeScalar(fRadius1); |
| buffer.writeScalar(fRadius2); |
| } |
| |
| void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p, |
| SkRasterPipeline* postPipeline) const { |
| const auto dRadius = fRadius2 - fRadius1; |
| |
| if (fType == Type::kRadial) { |
| p->append(SkRasterPipelineOp::xy_to_radius); |
| |
| // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2]. |
| auto scale = std::max(fRadius1, fRadius2) / dRadius; |
| auto bias = -fRadius1 / dRadius; |
| |
| p->append_matrix(alloc, SkMatrix::Translate(bias, 0) * SkMatrix::Scale(scale, 1)); |
| return; |
| } |
| |
| if (fType == Type::kStrip) { |
| auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>(); |
| SkScalar scaledR0 = fRadius1 / this->getCenterX1(); |
| ctx->fP0 = scaledR0 * scaledR0; |
| p->append(SkRasterPipelineOp::xy_to_2pt_conical_strip, ctx); |
| p->append(SkRasterPipelineOp::mask_2pt_conical_nan, ctx); |
| postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask); |
| return; |
| } |
| |
| auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>(); |
| ctx->fP0 = 1/fFocalData.fR1; |
| ctx->fP1 = fFocalData.fFocalX; |
| |
| if (fFocalData.isFocalOnCircle()) { |
| p->append(SkRasterPipelineOp::xy_to_2pt_conical_focal_on_circle); |
| } else if (fFocalData.isWellBehaved()) { |
| p->append(SkRasterPipelineOp::xy_to_2pt_conical_well_behaved, ctx); |
| } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) { |
| p->append(SkRasterPipelineOp::xy_to_2pt_conical_smaller, ctx); |
| } else { |
| p->append(SkRasterPipelineOp::xy_to_2pt_conical_greater, ctx); |
| } |
| |
| if (!fFocalData.isWellBehaved()) { |
| p->append(SkRasterPipelineOp::mask_2pt_conical_degenerates, ctx); |
| } |
| if (1 - fFocalData.fFocalX < 0) { |
| p->append(SkRasterPipelineOp::negate_x); |
| } |
| if (!fFocalData.isNativelyFocal()) { |
| p->append(SkRasterPipelineOp::alter_2pt_conical_compensate_focal, ctx); |
| } |
| if (fFocalData.isSwapped()) { |
| p->append(SkRasterPipelineOp::alter_2pt_conical_unswap); |
| } |
| if (!fFocalData.isWellBehaved()) { |
| postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask); |
| } |
| } |
| |
| skvm::F32 SkTwoPointConicalGradient::transformT(skvm::Builder* p, skvm::Uniforms* uniforms, |
| skvm::Coord coord, skvm::I32* mask) const { |
| auto mag = [](skvm::F32 x, skvm::F32 y) { return sqrt(x*x + y*y); }; |
| |
| // See https://skia.org/dev/design/conical, and appendStages() above. |
| // There's a lot going on here, and I'm not really sure what's independent |
| // or disjoint, what can be reordered, simplified, etc. Tweak carefully. |
| |
| const skvm::F32 x = coord.x, |
| y = coord.y; |
| if (fType == Type::kRadial) { |
| float denom = 1.0f / (fRadius2 - fRadius1), |
| scale = std::max(fRadius1, fRadius2) * denom, |
| bias = -fRadius1 * denom; |
| return mag(x,y) * p->uniformF(uniforms->pushF(scale)) |
| + p->uniformF(uniforms->pushF(bias )); |
| } |
| |
| if (fType == Type::kStrip) { |
| float r = fRadius1 / this->getCenterX1(); |
| skvm::F32 t = x + sqrt(p->uniformF(uniforms->pushF(r*r)) - y*y); |
| |
| *mask = (t == t); // t != NaN |
| return t; |
| } |
| |
| const skvm::F32 invR1 = p->uniformF(uniforms->pushF(1 / fFocalData.fR1)); |
| |
| skvm::F32 t; |
| if (fFocalData.isFocalOnCircle()) { |
| t = (y/x) * y + x; // (x^2 + y^2) / x ~~> x + y^2/x ~~> y/x * y + x |
| } else if (fFocalData.isWellBehaved()) { |
| t = mag(x,y) - x*invR1; |
| } else { |
| skvm::F32 k = sqrt(x*x - y*y); |
| if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) { |
| k = -k; |
| } |
| t = k - x*invR1; |
| } |
| |
| if (!fFocalData.isWellBehaved()) { |
| // TODO: not sure why we consider t == 0 degenerate |
| *mask = (t > 0.0f); // and implicitly, t != NaN |
| } |
| |
| const skvm::F32 focalX = p->uniformF(uniforms->pushF(fFocalData.fFocalX)); |
| if (1 - fFocalData.fFocalX < 0) { t = -t; } |
| if (!fFocalData.isNativelyFocal()) { t += focalX; } |
| if ( fFocalData.isSwapped()) { t = 1.0f - t; } |
| return t; |
| } |
| |
| ///////////////////////////////////////////////////////////////////// |
| |
| #if SK_SUPPORT_GPU |
| |
| #include "src/core/SkRuntimeEffectPriv.h" |
| #include "src/gpu/ganesh/effects/GrSkSLFP.h" |
| #include "src/gpu/ganesh/gradients/GrGradientShader.h" |
| |
| std::unique_ptr<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor( |
| const GrFPArgs& args) const { |
| // The 2 point conical gradient can reject a pixel so it does change opacity even if the input |
| // was opaque. Thus, all of these layout FPs disable that optimization. |
| std::unique_ptr<GrFragmentProcessor> fp; |
| SkTLazy<SkMatrix> matrix; |
| switch (this->getType()) { |
| case SkTwoPointConicalGradient::Type::kStrip: { |
| static const SkRuntimeEffect* kEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| "uniform half r0_2;" |
| "half4 main(float2 p) {" |
| "half v = 1;" // validation flag,set to negative to discard fragment later |
| "float t = r0_2 - p.y * p.y;" |
| "if (t >= 0) {" |
| "t = p.x + sqrt(t);" |
| "} else {" |
| "v = -1;" |
| "}" |
| "return half4(half(t), v, 0, 0);" |
| "}" |
| ); |
| float r0 = this->getStartRadius() / this->getCenterX1(); |
| fp = GrSkSLFP::Make(kEffect, "TwoPointConicalStripLayout", /*inputFP=*/nullptr, |
| GrSkSLFP::OptFlags::kNone, |
| "r0_2", r0 * r0); |
| } break; |
| |
| case SkTwoPointConicalGradient::Type::kRadial: { |
| static const SkRuntimeEffect* kEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| "uniform half r0;" |
| "uniform half lengthScale;" |
| "half4 main(float2 p) {" |
| "half v = 1;" // validation flag,set to negative to discard fragment later |
| "float t = length(p) * lengthScale - r0;" |
| "return half4(half(t), v, 0, 0);" |
| "}" |
| ); |
| float dr = this->getDiffRadius(); |
| float r0 = this->getStartRadius() / dr; |
| bool isRadiusIncreasing = dr >= 0; |
| fp = GrSkSLFP::Make(kEffect, "TwoPointConicalRadialLayout", /*inputFP=*/nullptr, |
| GrSkSLFP::OptFlags::kNone, |
| "r0", r0, |
| "lengthScale", isRadiusIncreasing ? 1.0f : -1.0f); |
| |
| // GPU radial matrix is different from the original matrix, since we map the diff radius |
| // to have |dr| = 1, so manually compute the final gradient matrix here. |
| |
| // Map center to (0, 0) |
| matrix.set(SkMatrix::Translate(-this->getStartCenter().fX, |
| -this->getStartCenter().fY)); |
| // scale |diffRadius| to 1 |
| matrix->postScale(1 / dr, 1 / dr); |
| } break; |
| |
| case SkTwoPointConicalGradient::Type::kFocal: { |
| static const SkRuntimeEffect* kEffect = |
| SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, |
| // Optimization flags, all specialized: |
| "uniform int isRadiusIncreasing;" |
| "uniform int isFocalOnCircle;" |
| "uniform int isWellBehaved;" |
| "uniform int isSwapped;" |
| "uniform int isNativelyFocal;" |
| |
| "uniform half invR1;" // 1/r1 |
| "uniform half fx;" // focalX = r0/(r0-r1) |
| |
| "half4 main(float2 p) {" |
| "float t = -1;" |
| "half v = 1;" // validation flag,set to negative to discard fragment later |
| |
| "float x_t = -1;" |
| "if (bool(isFocalOnCircle)) {" |
| "x_t = dot(p, p) / p.x;" |
| "} else if (bool(isWellBehaved)) {" |
| "x_t = length(p) - p.x * invR1;" |
| "} else {" |
| "float temp = p.x * p.x - p.y * p.y;" |
| |
| // Only do sqrt if temp >= 0; this is significantly slower than |
| // checking temp >= 0 in the if statement that checks r(t) >= 0. |
| // But GPU may break if we sqrt a negative float. (Although I |
| // haven't observed that on any devices so far, and the old |
| // approach also does sqrt negative value without a check.) If |
| // the performance is really critical, maybe we should just |
| // compute the area where temp and x_t are always valid and drop |
| // all these ifs. |
| "if (temp >= 0) {" |
| "if (bool(isSwapped) || !bool(isRadiusIncreasing)) {" |
| "x_t = -sqrt(temp) - p.x * invR1;" |
| "} else {" |
| "x_t = sqrt(temp) - p.x * invR1;" |
| "}" |
| "}" |
| "}" |
| |
| // The final calculation of t from x_t has lots of static |
| // optimizations but only do them when x_t is positive (which |
| // can be assumed true if isWellBehaved is true) |
| "if (!bool(isWellBehaved)) {" |
| // This will still calculate t even though it will be ignored |
| // later in the pipeline to avoid a branch |
| "if (x_t <= 0.0) {" |
| "v = -1;" |
| "}" |
| "}" |
| "if (bool(isRadiusIncreasing)) {" |
| "if (bool(isNativelyFocal)) {" |
| "t = x_t;" |
| "} else {" |
| "t = x_t + fx;" |
| "}" |
| "} else {" |
| "if (bool(isNativelyFocal)) {" |
| "t = -x_t;" |
| "} else {" |
| "t = -x_t + fx;" |
| "}" |
| "}" |
| |
| "if (bool(isSwapped)) {" |
| "t = 1 - t;" |
| "}" |
| |
| "return half4(half(t), v, 0, 0);" |
| "}" |
| ); |
| |
| const SkTwoPointConicalGradient::FocalData& focalData = this->getFocalData(); |
| bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0, |
| isFocalOnCircle = focalData.isFocalOnCircle(), |
| isWellBehaved = focalData.isWellBehaved(), |
| isSwapped = focalData.isSwapped(), |
| isNativelyFocal = focalData.isNativelyFocal(); |
| |
| fp = GrSkSLFP::Make(kEffect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr, |
| GrSkSLFP::OptFlags::kNone, |
| "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing), |
| "isFocalOnCircle", GrSkSLFP::Specialize<int>(isFocalOnCircle), |
| "isWellBehaved", GrSkSLFP::Specialize<int>(isWellBehaved), |
| "isSwapped", GrSkSLFP::Specialize<int>(isSwapped), |
| "isNativelyFocal", GrSkSLFP::Specialize<int>(isNativelyFocal), |
| "invR1", 1.0f / focalData.fR1, |
| "fx", focalData.fFocalX); |
| } break; |
| } |
| return GrGradientShader::MakeGradientFP(*this, args, std::move(fp), matrix.getMaybeNull()); |
| } |
| |
| #endif |
| |
| #ifdef SK_GRAPHITE_ENABLED |
| void SkTwoPointConicalGradient::addToKey(const skgpu::graphite::KeyContext& keyContext, |
| skgpu::graphite::PaintParamsKeyBuilder* builder, |
| skgpu::graphite::PipelineDataGatherer* gatherer) const { |
| using namespace skgpu::graphite; |
| |
| GradientShaderBlocks::GradientData data(GradientType::kConical, |
| fCenter1, fCenter2, |
| fRadius1, fRadius2, |
| 0.0f, 0.0f, |
| fTileMode, |
| fColorCount, |
| fColors, |
| fPositions); |
| |
| GradientShaderBlocks::BeginBlock(keyContext, builder, gatherer, data); |
| builder->endBlock(); |
| } |
| #endif |
| |
| // assumes colors is SkColor4f* and pos is SkScalar* |
| #define EXPAND_1_COLOR(count) \ |
| SkColor4f tmp[2]; \ |
| do { \ |
| if (1 == count) { \ |
| tmp[0] = tmp[1] = colors[0]; \ |
| colors = tmp; \ |
| pos = nullptr; \ |
| count = 2; \ |
| } \ |
| } while (0) |
| |
| sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start, |
| SkScalar startRadius, |
| const SkPoint& end, |
| SkScalar endRadius, |
| const SkColor4f colors[], |
| sk_sp<SkColorSpace> colorSpace, |
| const SkScalar pos[], |
| int colorCount, |
| SkTileMode mode, |
| const Interpolation& interpolation, |
| const SkMatrix* localMatrix) { |
| if (startRadius < 0 || endRadius < 0) { |
| return nullptr; |
| } |
| if (!SkGradientShaderBase::ValidGradient(colors, colorCount, mode, interpolation)) { |
| return nullptr; |
| } |
| if (SkScalarNearlyZero((start - end).length(), SkGradientShaderBase::kDegenerateThreshold)) { |
| // If the center positions are the same, then the gradient is the radial variant of a 2 pt |
| // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate |
| // (startRadius == endRadius). |
| if (SkScalarNearlyEqual(startRadius, endRadius, |
| SkGradientShaderBase::kDegenerateThreshold)) { |
| // Degenerate case, where the interpolation region area approaches zero. The proper |
| // behavior depends on the tile mode, which is consistent with the default degenerate |
| // gradient behavior, except when mode = clamp and the radii > 0. |
| if (mode == SkTileMode::kClamp && |
| endRadius > SkGradientShaderBase::kDegenerateThreshold) { |
| // The interpolation region becomes an infinitely thin ring at the radius, so the |
| // final gradient will be the first color repeated from p=0 to 1, and then a hard |
| // stop switching to the last color at p=1. |
| static constexpr SkScalar circlePos[3] = {0, 1, 1}; |
| SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]}; |
| return MakeRadial(start, endRadius, reColors, std::move(colorSpace), |
| circlePos, 3, mode, interpolation, localMatrix); |
| } else { |
| // Otherwise use the default degenerate case |
| return SkGradientShaderBase::MakeDegenerateGradient(colors, pos, colorCount, |
| std::move(colorSpace), mode); |
| } |
| } else if (SkScalarNearlyZero(startRadius, SkGradientShaderBase::kDegenerateThreshold)) { |
| // We can treat this gradient as radial, which is faster. If we got here, we know |
| // that endRadius is not equal to 0, so this produces a meaningful gradient |
| return MakeRadial(start, endRadius, colors, std::move(colorSpace), pos, colorCount, |
| mode, interpolation, localMatrix); |
| } |
| // Else it's the 2pt conical radial variant with no degenerate radii, so fall through to the |
| // regular 2pt constructor. |
| } |
| |
| if (localMatrix && !localMatrix->invert(nullptr)) { |
| return nullptr; |
| } |
| EXPAND_1_COLOR(colorCount); |
| |
| SkGradientShaderBase::ColorStopOptimizer opt(colors, pos, colorCount, mode); |
| |
| SkGradientShaderBase::Descriptor desc(opt.fColors, std::move(colorSpace), opt.fPos, |
| opt.fCount, mode, interpolation); |
| return SkTwoPointConicalGradient::Create(start, startRadius, end, endRadius, desc, localMatrix); |
| } |
| |
| #undef EXPAND_1_COLOR |
| |
| sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start, |
| SkScalar startRadius, |
| const SkPoint& end, |
| SkScalar endRadius, |
| const SkColor colors[], |
| const SkScalar pos[], |
| int colorCount, |
| SkTileMode mode, |
| uint32_t flags, |
| const SkMatrix* localMatrix) { |
| SkColorConverter converter(colors, colorCount); |
| return MakeTwoPointConical(start, startRadius, end, endRadius, converter.fColors4f.begin(), |
| nullptr, pos, colorCount, mode, flags, localMatrix); |
| } |
| |
| void SkRegisterTwoPointConicalGradientShaderFlattenable() { |
| SK_REGISTER_FLATTENABLE(SkTwoPointConicalGradient); |
| } |