1 /*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/private/base/SkFloatingPoint.h"
9 #include "src/core/SkRasterPipeline.h"
10 #include "src/core/SkReadBuffer.h"
11 #include "src/core/SkWriteBuffer.h"
12 #include "src/shaders/SkLocalMatrixShader.h"
13 #include "src/shaders/gradients/SkGradientShaderBase.h"
14
15 #include <utility>
16
17 #if defined(SK_GRAPHITE)
18 #include "src/gpu/graphite/KeyContext.h"
19 #include "src/gpu/graphite/KeyHelpers.h"
20 #include "src/gpu/graphite/PaintParamsKey.h"
21 #endif
22
23 // Please see https://skia.org/dev/design/conical for how our shader works.
24
25 class SkTwoPointConicalGradient final : public SkGradientShaderBase {
26 public:
27 // See https://skia.org/dev/design/conical for what focal data means and how our shader works.
28 // We make it public so the GPU shader can also use it.
29 struct FocalData {
30 SkScalar fR1; // r1 after mapping focal point to (0, 0)
31 SkScalar fFocalX; // f
32 bool fIsSwapped; // whether we swapped r0, r1
33
34 // The input r0, r1 are the radii when we map centers to {(0, 0), (1, 0)}.
35 // We'll post concat matrix with our transformation matrix that maps focal point to (0, 0).
36 // Returns true if the set succeeded
37 bool set(SkScalar r0, SkScalar r1, SkMatrix* matrix);
38
39 // Whether the focal point (0, 0) is on the end circle with center (1, 0) and radius r1. If
40 // this is true, it's as if an aircraft is flying at Mach 1 and all circles (soundwaves)
41 // will go through the focal point (aircraft). In our previous implementations, this was
42 // known as the edge case where the inside circle touches the outside circle (on the focal
43 // point). If we were to solve for t bruteforcely using a quadratic equation, this case
44 // implies that the quadratic equation degenerates to a linear equation.
isFocalOnCircleSkTwoPointConicalGradient::FocalData45 bool isFocalOnCircle() const { return SkScalarNearlyZero(1 - fR1); }
46
isSwappedSkTwoPointConicalGradient::FocalData47 bool isSwapped() const { return fIsSwapped; }
isWellBehavedSkTwoPointConicalGradient::FocalData48 bool isWellBehaved() const { return !this->isFocalOnCircle() && fR1 > 1; }
isNativelyFocalSkTwoPointConicalGradient::FocalData49 bool isNativelyFocal() const { return SkScalarNearlyZero(fFocalX); }
50 };
51
52 enum class Type {
53 kRadial,
54 kStrip,
55 kFocal
56 };
57
58 static sk_sp<SkShader> Create(const SkPoint& start, SkScalar startRadius,
59 const SkPoint& end, SkScalar endRadius,
60 const Descriptor&, const SkMatrix* localMatrix);
61
62 GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
63 #if defined(SK_GANESH)
64 std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
65 const MatrixRec&) const override;
66 #endif
67 #if defined(SK_GRAPHITE)
68 void addToKey(const skgpu::graphite::KeyContext&,
69 skgpu::graphite::PaintParamsKeyBuilder*,
70 skgpu::graphite::PipelineDataGatherer*) const override;
71 #endif
72 bool isOpaque() const override;
73
getCenterX1() const74 SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); }
getStartRadius() const75 SkScalar getStartRadius() const { return fRadius1; }
getDiffRadius() const76 SkScalar getDiffRadius() const { return fRadius2 - fRadius1; }
getStartCenter() const77 const SkPoint& getStartCenter() const { return fCenter1; }
getEndCenter() const78 const SkPoint& getEndCenter() const { return fCenter2; }
getEndRadius() const79 SkScalar getEndRadius() const { return fRadius2; }
80
getType() const81 Type getType() const { return fType; }
getFocalData() const82 const FocalData& getFocalData() const { return fFocalData; }
83
84 SkTwoPointConicalGradient(const SkPoint& c0, SkScalar r0,
85 const SkPoint& c1, SkScalar r1,
86 const Descriptor&, Type, const SkMatrix&, const FocalData&);
87
88 protected:
89 void flatten(SkWriteBuffer& buffer) const override;
90
91 void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
92 SkRasterPipeline* postPipeline) const override;
93
94 skvm::F32 transformT(skvm::Builder*, skvm::Uniforms*,
95 skvm::Coord coord, skvm::I32* mask) const final;
96
97 private:
98 friend void ::SkRegisterTwoPointConicalGradientShaderFlattenable();
99 SK_FLATTENABLE_HOOKS(SkTwoPointConicalGradient)
100
101 SkPoint fCenter1;
102 SkPoint fCenter2;
103 SkScalar fRadius1;
104 SkScalar fRadius2;
105 Type fType;
106
107 FocalData fFocalData;
108 };
109
set(SkScalar r0,SkScalar r1,SkMatrix * matrix)110 bool SkTwoPointConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix* matrix) {
111 fIsSwapped = false;
112 fFocalX = sk_ieee_float_divide(r0, (r0 - r1));
113 if (SkScalarNearlyZero(fFocalX - 1)) {
114 // swap r0, r1
115 matrix->postTranslate(-1, 0);
116 matrix->postScale(-1, 1);
117 std::swap(r0, r1);
118 fFocalX = 0; // because r0 is now 0
119 fIsSwapped = true;
120 }
121
122 // Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
123 const SkPoint from[2] = { {fFocalX, 0}, {1, 0} };
124 const SkPoint to[2] = { {0, 0}, {1, 0} };
125 SkMatrix focalMatrix;
126 if (!focalMatrix.setPolyToPoly(from, to, 2)) {
127 return false;
128 }
129 matrix->postConcat(focalMatrix);
130 fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f)
131
132 // The following transformations are just to accelerate the shader computation by saving
133 // some arithmatic operations.
134 if (this->isFocalOnCircle()) {
135 matrix->postScale(0.5, 0.5);
136 } else {
137 matrix->postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
138 }
139 matrix->postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f|
140 return true;
141 }
142
Create(const SkPoint & c0,SkScalar r0,const SkPoint & c1,SkScalar r1,const Descriptor & desc,const SkMatrix * localMatrix)143 sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0,
144 const SkPoint& c1, SkScalar r1,
145 const Descriptor& desc,
146 const SkMatrix* localMatrix) {
147 SkMatrix gradientMatrix;
148 Type gradientType;
149
150 if (SkScalarNearlyZero((c0 - c1).length())) {
151 if (SkScalarNearlyZero(std::max(r0, r1)) || SkScalarNearlyEqual(r0, r1)) {
152 // Degenerate case; avoid dividing by zero. Should have been caught by caller but
153 // just in case, recheck here.
154 return nullptr;
155 }
156 // Concentric case: we can pretend we're radial (with a tiny twist).
157 const SkScalar scale = sk_ieee_float_divide(1, std::max(r0, r1));
158 gradientMatrix = SkMatrix::Translate(-c1.x(), -c1.y());
159 gradientMatrix.postScale(scale, scale);
160
161 gradientType = Type::kRadial;
162 } else {
163 const SkPoint centers[2] = { c0 , c1 };
164 const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
165
166 if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) {
167 // Degenerate case.
168 return nullptr;
169 }
170
171 gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
172 }
173
174 FocalData focalData;
175 if (gradientType == Type::kFocal) {
176 const auto dCenter = (c0 - c1).length();
177 if (!focalData.set(r0 / dCenter, r1 / dCenter, &gradientMatrix)) {
178 return nullptr;
179 }
180 }
181 return SkLocalMatrixShader::MakeWrapped<SkTwoPointConicalGradient>(localMatrix,
182 c0, r0,
183 c1, r1,
184 desc,
185 gradientType,
186 gradientMatrix,
187 focalData);
188 }
189
SkTwoPointConicalGradient(const SkPoint & start,SkScalar startRadius,const SkPoint & end,SkScalar endRadius,const Descriptor & desc,Type type,const SkMatrix & gradientMatrix,const FocalData & data)190 SkTwoPointConicalGradient::SkTwoPointConicalGradient(
191 const SkPoint& start, SkScalar startRadius,
192 const SkPoint& end, SkScalar endRadius,
193 const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data)
194 : SkGradientShaderBase(desc, gradientMatrix)
195 , fCenter1(start)
196 , fCenter2(end)
197 , fRadius1(startRadius)
198 , fRadius2(endRadius)
199 , fType(type)
200 {
201 // this is degenerate, and should be caught by our caller
202 SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
203 if (type == Type::kFocal) {
204 fFocalData = data;
205 }
206 }
207
isOpaque() const208 bool SkTwoPointConicalGradient::isOpaque() const {
209 // Because areas outside the cone are left untouched, we cannot treat the
210 // shader as opaque even if the gradient itself is opaque.
211 // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
212 return false;
213 }
214
215 // Returns the original non-sorted version of the gradient
asGradient(GradientInfo * info,SkMatrix * localMatrix) const216 SkShaderBase::GradientType SkTwoPointConicalGradient::asGradient(GradientInfo* info,
217 SkMatrix* localMatrix) const {
218 if (info) {
219 commonAsAGradient(info);
220 info->fPoint[0] = fCenter1;
221 info->fPoint[1] = fCenter2;
222 info->fRadius[0] = fRadius1;
223 info->fRadius[1] = fRadius2;
224 }
225 if (localMatrix) {
226 *localMatrix = SkMatrix::I();
227 }
228 return GradientType::kConical;
229 }
230
CreateProc(SkReadBuffer & buffer)231 sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) {
232 DescriptorScope desc;
233 SkMatrix legacyLocalMatrix;
234 if (!desc.unflatten(buffer, &legacyLocalMatrix)) {
235 return nullptr;
236 }
237 SkPoint c1 = buffer.readPoint();
238 SkPoint c2 = buffer.readPoint();
239 SkScalar r1 = buffer.readScalar();
240 SkScalar r2 = buffer.readScalar();
241
242 if (!buffer.isValid()) {
243 return nullptr;
244 }
245 return SkGradientShader::MakeTwoPointConical(c1, r1,
246 c2, r2,
247 desc.fColors,
248 std::move(desc.fColorSpace),
249 desc.fPositions,
250 desc.fColorCount,
251 desc.fTileMode,
252 desc.fInterpolation,
253 &legacyLocalMatrix);
254 }
255
flatten(SkWriteBuffer & buffer) const256 void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
257 this->SkGradientShaderBase::flatten(buffer);
258 buffer.writePoint(fCenter1);
259 buffer.writePoint(fCenter2);
260 buffer.writeScalar(fRadius1);
261 buffer.writeScalar(fRadius2);
262 }
263
appendGradientStages(SkArenaAlloc * alloc,SkRasterPipeline * p,SkRasterPipeline * postPipeline) const264 void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
265 SkRasterPipeline* postPipeline) const {
266 const auto dRadius = fRadius2 - fRadius1;
267
268 if (fType == Type::kRadial) {
269 p->append(SkRasterPipelineOp::xy_to_radius);
270
271 // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
272 auto scale = std::max(fRadius1, fRadius2) / dRadius;
273 auto bias = -fRadius1 / dRadius;
274
275 p->append_matrix(alloc, SkMatrix::Translate(bias, 0) * SkMatrix::Scale(scale, 1));
276 return;
277 }
278
279 if (fType == Type::kStrip) {
280 auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
281 SkScalar scaledR0 = fRadius1 / this->getCenterX1();
282 ctx->fP0 = scaledR0 * scaledR0;
283 p->append(SkRasterPipelineOp::xy_to_2pt_conical_strip, ctx);
284 p->append(SkRasterPipelineOp::mask_2pt_conical_nan, ctx);
285 postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
286 return;
287 }
288
289 auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
290 ctx->fP0 = 1/fFocalData.fR1;
291 ctx->fP1 = fFocalData.fFocalX;
292
293 if (fFocalData.isFocalOnCircle()) {
294 p->append(SkRasterPipelineOp::xy_to_2pt_conical_focal_on_circle);
295 } else if (fFocalData.isWellBehaved()) {
296 p->append(SkRasterPipelineOp::xy_to_2pt_conical_well_behaved, ctx);
297 } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
298 p->append(SkRasterPipelineOp::xy_to_2pt_conical_smaller, ctx);
299 } else {
300 p->append(SkRasterPipelineOp::xy_to_2pt_conical_greater, ctx);
301 }
302
303 if (!fFocalData.isWellBehaved()) {
304 p->append(SkRasterPipelineOp::mask_2pt_conical_degenerates, ctx);
305 }
306 if (1 - fFocalData.fFocalX < 0) {
307 p->append(SkRasterPipelineOp::negate_x);
308 }
309 if (!fFocalData.isNativelyFocal()) {
310 p->append(SkRasterPipelineOp::alter_2pt_conical_compensate_focal, ctx);
311 }
312 if (fFocalData.isSwapped()) {
313 p->append(SkRasterPipelineOp::alter_2pt_conical_unswap);
314 }
315 if (!fFocalData.isWellBehaved()) {
316 postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
317 }
318 }
319
transformT(skvm::Builder * p,skvm::Uniforms * uniforms,skvm::Coord coord,skvm::I32 * mask) const320 skvm::F32 SkTwoPointConicalGradient::transformT(skvm::Builder* p, skvm::Uniforms* uniforms,
321 skvm::Coord coord, skvm::I32* mask) const {
322 auto mag = [](skvm::F32 x, skvm::F32 y) { return sqrt(x*x + y*y); };
323
324 // See https://skia.org/dev/design/conical, and appendStages() above.
325 // There's a lot going on here, and I'm not really sure what's independent
326 // or disjoint, what can be reordered, simplified, etc. Tweak carefully.
327
328 const skvm::F32 x = coord.x,
329 y = coord.y;
330 if (fType == Type::kRadial) {
331 float denom = 1.0f / (fRadius2 - fRadius1),
332 scale = std::max(fRadius1, fRadius2) * denom,
333 bias = -fRadius1 * denom;
334 return mag(x,y) * p->uniformF(uniforms->pushF(scale))
335 + p->uniformF(uniforms->pushF(bias ));
336 }
337
338 if (fType == Type::kStrip) {
339 float r = fRadius1 / this->getCenterX1();
340 skvm::F32 t = x + sqrt(p->uniformF(uniforms->pushF(r*r)) - y*y);
341
342 *mask = (t == t); // t != NaN
343 return t;
344 }
345
346 const skvm::F32 invR1 = p->uniformF(uniforms->pushF(1 / fFocalData.fR1));
347
348 skvm::F32 t;
349 if (fFocalData.isFocalOnCircle()) {
350 t = (y/x) * y + x; // (x^2 + y^2) / x ~~> x + y^2/x ~~> y/x * y + x
351 } else if (fFocalData.isWellBehaved()) {
352 t = mag(x,y) - x*invR1;
353 } else {
354 skvm::F32 k = sqrt(x*x - y*y);
355 if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
356 k = -k;
357 }
358 t = k - x*invR1;
359 }
360
361 if (!fFocalData.isWellBehaved()) {
362 // TODO: not sure why we consider t == 0 degenerate
363 *mask = (t > 0.0f); // and implicitly, t != NaN
364 }
365
366 const skvm::F32 focalX = p->uniformF(uniforms->pushF(fFocalData.fFocalX));
367 if (1 - fFocalData.fFocalX < 0) { t = -t; }
368 if (!fFocalData.isNativelyFocal()) { t += focalX; }
369 if ( fFocalData.isSwapped()) { t = 1.0f - t; }
370 return t;
371 }
372
373 /////////////////////////////////////////////////////////////////////
374
375 #if defined(SK_GANESH)
376
377 #include "src/core/SkRuntimeEffectPriv.h"
378 #include "src/gpu/ganesh/effects/GrSkSLFP.h"
379 #include "src/gpu/ganesh/gradients/GrGradientShader.h"
380
381 std::unique_ptr<GrFragmentProcessor>
asFragmentProcessor(const GrFPArgs & args,const MatrixRec & mRec) const382 SkTwoPointConicalGradient::asFragmentProcessor(const GrFPArgs& args, const MatrixRec& mRec) const {
383 // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
384 // was opaque. Thus, all of these layout FPs disable that optimization.
385 std::unique_ptr<GrFragmentProcessor> fp;
386 SkTLazy<SkMatrix> matrix;
387 switch (this->getType()) {
388 case SkTwoPointConicalGradient::Type::kStrip: {
389 static const SkRuntimeEffect* kEffect =
390 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
391 "uniform half r0_2;"
392 "half4 main(float2 p) {"
393 "half v = 1;" // validation flag,set to negative to discard fragment later
394 "float t = r0_2 - p.y * p.y;"
395 "if (t >= 0) {"
396 "t = p.x + sqrt(t);"
397 "} else {"
398 "v = -1;"
399 "}"
400 "return half4(half(t), v, 0, 0);"
401 "}"
402 );
403 float r0 = this->getStartRadius() / this->getCenterX1();
404 fp = GrSkSLFP::Make(kEffect, "TwoPointConicalStripLayout", /*inputFP=*/nullptr,
405 GrSkSLFP::OptFlags::kNone,
406 "r0_2", r0 * r0);
407 } break;
408
409 case SkTwoPointConicalGradient::Type::kRadial: {
410 static const SkRuntimeEffect* kEffect =
411 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
412 "uniform half r0;"
413 "uniform half lengthScale;"
414 "half4 main(float2 p) {"
415 "half v = 1;" // validation flag,set to negative to discard fragment later
416 "float t = length(p) * lengthScale - r0;"
417 "return half4(half(t), v, 0, 0);"
418 "}"
419 );
420 float dr = this->getDiffRadius();
421 float r0 = this->getStartRadius() / dr;
422 bool isRadiusIncreasing = dr >= 0;
423 fp = GrSkSLFP::Make(kEffect, "TwoPointConicalRadialLayout", /*inputFP=*/nullptr,
424 GrSkSLFP::OptFlags::kNone,
425 "r0", r0,
426 "lengthScale", isRadiusIncreasing ? 1.0f : -1.0f);
427
428 // GPU radial matrix is different from the original matrix, since we map the diff radius
429 // to have |dr| = 1, so manually compute the final gradient matrix here.
430
431 // Map center to (0, 0)
432 matrix.set(SkMatrix::Translate(-this->getStartCenter().fX,
433 -this->getStartCenter().fY));
434 // scale |diffRadius| to 1
435 matrix->postScale(1 / dr, 1 / dr);
436 } break;
437
438 case SkTwoPointConicalGradient::Type::kFocal: {
439 static const SkRuntimeEffect* kEffect =
440 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
441 // Optimization flags, all specialized:
442 "uniform int isRadiusIncreasing;"
443 "uniform int isFocalOnCircle;"
444 "uniform int isWellBehaved;"
445 "uniform int isSwapped;"
446 "uniform int isNativelyFocal;"
447
448 "uniform half invR1;" // 1/r1
449 "uniform half fx;" // focalX = r0/(r0-r1)
450
451 "half4 main(float2 p) {"
452 "float t = -1;"
453 "half v = 1;" // validation flag,set to negative to discard fragment later
454
455 "float x_t = -1;"
456 "if (bool(isFocalOnCircle)) {"
457 "x_t = dot(p, p) / p.x;"
458 "} else if (bool(isWellBehaved)) {"
459 "x_t = length(p) - p.x * invR1;"
460 "} else {"
461 "float temp = p.x * p.x - p.y * p.y;"
462
463 // Only do sqrt if temp >= 0; this is significantly slower than
464 // checking temp >= 0 in the if statement that checks r(t) >= 0.
465 // But GPU may break if we sqrt a negative float. (Although I
466 // haven't observed that on any devices so far, and the old
467 // approach also does sqrt negative value without a check.) If
468 // the performance is really critical, maybe we should just
469 // compute the area where temp and x_t are always valid and drop
470 // all these ifs.
471 "if (temp >= 0) {"
472 "if (bool(isSwapped) || !bool(isRadiusIncreasing)) {"
473 "x_t = -sqrt(temp) - p.x * invR1;"
474 "} else {"
475 "x_t = sqrt(temp) - p.x * invR1;"
476 "}"
477 "}"
478 "}"
479
480 // The final calculation of t from x_t has lots of static
481 // optimizations but only do them when x_t is positive (which
482 // can be assumed true if isWellBehaved is true)
483 "if (!bool(isWellBehaved)) {"
484 // This will still calculate t even though it will be ignored
485 // later in the pipeline to avoid a branch
486 "if (x_t <= 0.0) {"
487 "v = -1;"
488 "}"
489 "}"
490 "if (bool(isRadiusIncreasing)) {"
491 "if (bool(isNativelyFocal)) {"
492 "t = x_t;"
493 "} else {"
494 "t = x_t + fx;"
495 "}"
496 "} else {"
497 "if (bool(isNativelyFocal)) {"
498 "t = -x_t;"
499 "} else {"
500 "t = -x_t + fx;"
501 "}"
502 "}"
503
504 "if (bool(isSwapped)) {"
505 "t = 1 - t;"
506 "}"
507
508 "return half4(half(t), v, 0, 0);"
509 "}"
510 );
511
512 const SkTwoPointConicalGradient::FocalData& focalData = this->getFocalData();
513 bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
514 isFocalOnCircle = focalData.isFocalOnCircle(),
515 isWellBehaved = focalData.isWellBehaved(),
516 isSwapped = focalData.isSwapped(),
517 isNativelyFocal = focalData.isNativelyFocal();
518
519 fp = GrSkSLFP::Make(kEffect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
520 GrSkSLFP::OptFlags::kNone,
521 "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
522 "isFocalOnCircle", GrSkSLFP::Specialize<int>(isFocalOnCircle),
523 "isWellBehaved", GrSkSLFP::Specialize<int>(isWellBehaved),
524 "isSwapped", GrSkSLFP::Specialize<int>(isSwapped),
525 "isNativelyFocal", GrSkSLFP::Specialize<int>(isNativelyFocal),
526 "invR1", 1.0f / focalData.fR1,
527 "fx", focalData.fFocalX);
528 } break;
529 }
530 return GrGradientShader::MakeGradientFP(*this,
531 args,
532 mRec,
533 std::move(fp),
534 matrix.getMaybeNull());
535 }
536
537 #endif
538
539 #if defined(SK_GRAPHITE)
addToKey(const skgpu::graphite::KeyContext & keyContext,skgpu::graphite::PaintParamsKeyBuilder * builder,skgpu::graphite::PipelineDataGatherer * gatherer) const540 void SkTwoPointConicalGradient::addToKey(const skgpu::graphite::KeyContext& keyContext,
541 skgpu::graphite::PaintParamsKeyBuilder* builder,
542 skgpu::graphite::PipelineDataGatherer* gatherer) const {
543 using namespace skgpu::graphite;
544
545 SkColor4fXformer xformedColors(this, keyContext.dstColorInfo().colorSpace());
546 const SkPMColor4f* colors = xformedColors.fColors.begin();
547
548 GradientShaderBlocks::GradientData data(GradientType::kConical,
549 fCenter1, fCenter2,
550 fRadius1, fRadius2,
551 0.0f, 0.0f,
552 fTileMode,
553 fColorCount,
554 colors,
555 fPositions,
556 fInterpolation);
557
558 MakeInterpolatedToDst(keyContext, builder, gatherer,
559 data, fInterpolation,
560 xformedColors.fIntermediateColorSpace.get());
561 }
562 #endif
563
564 // assumes colors is SkColor4f* and pos is SkScalar*
565 #define EXPAND_1_COLOR(count) \
566 SkColor4f tmp[2]; \
567 do { \
568 if (1 == count) { \
569 tmp[0] = tmp[1] = colors[0]; \
570 colors = tmp; \
571 pos = nullptr; \
572 count = 2; \
573 } \
574 } while (0)
575
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)576 sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
577 SkScalar startRadius,
578 const SkPoint& end,
579 SkScalar endRadius,
580 const SkColor4f colors[],
581 sk_sp<SkColorSpace> colorSpace,
582 const SkScalar pos[],
583 int colorCount,
584 SkTileMode mode,
585 const Interpolation& interpolation,
586 const SkMatrix* localMatrix) {
587 if (startRadius < 0 || endRadius < 0) {
588 return nullptr;
589 }
590 if (!SkGradientShaderBase::ValidGradient(colors, colorCount, mode, interpolation)) {
591 return nullptr;
592 }
593 if (SkScalarNearlyZero((start - end).length(), SkGradientShaderBase::kDegenerateThreshold)) {
594 // If the center positions are the same, then the gradient is the radial variant of a 2 pt
595 // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate
596 // (startRadius == endRadius).
597 if (SkScalarNearlyEqual(startRadius, endRadius,
598 SkGradientShaderBase::kDegenerateThreshold)) {
599 // Degenerate case, where the interpolation region area approaches zero. The proper
600 // behavior depends on the tile mode, which is consistent with the default degenerate
601 // gradient behavior, except when mode = clamp and the radii > 0.
602 if (mode == SkTileMode::kClamp &&
603 endRadius > SkGradientShaderBase::kDegenerateThreshold) {
604 // The interpolation region becomes an infinitely thin ring at the radius, so the
605 // final gradient will be the first color repeated from p=0 to 1, and then a hard
606 // stop switching to the last color at p=1.
607 static constexpr SkScalar circlePos[3] = {0, 1, 1};
608 SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]};
609 return MakeRadial(start, endRadius, reColors, std::move(colorSpace),
610 circlePos, 3, mode, interpolation, localMatrix);
611 } else {
612 // Otherwise use the default degenerate case
613 return SkGradientShaderBase::MakeDegenerateGradient(colors, pos, colorCount,
614 std::move(colorSpace), mode);
615 }
616 } else if (SkScalarNearlyZero(startRadius, SkGradientShaderBase::kDegenerateThreshold)) {
617 // We can treat this gradient as radial, which is faster. If we got here, we know
618 // that endRadius is not equal to 0, so this produces a meaningful gradient
619 return MakeRadial(start, endRadius, colors, std::move(colorSpace), pos, colorCount,
620 mode, interpolation, localMatrix);
621 }
622 // Else it's the 2pt conical radial variant with no degenerate radii, so fall through to the
623 // regular 2pt constructor.
624 }
625
626 if (localMatrix && !localMatrix->invert(nullptr)) {
627 return nullptr;
628 }
629 EXPAND_1_COLOR(colorCount);
630
631 SkGradientShaderBase::ColorStopOptimizer opt(colors, pos, colorCount, mode);
632
633 SkGradientShaderBase::Descriptor desc(opt.fColors, std::move(colorSpace), opt.fPos,
634 opt.fCount, mode, interpolation);
635 return SkTwoPointConicalGradient::Create(start, startRadius, end, endRadius, desc, localMatrix);
636 }
637
638 #undef EXPAND_1_COLOR
639
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)640 sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
641 SkScalar startRadius,
642 const SkPoint& end,
643 SkScalar endRadius,
644 const SkColor colors[],
645 const SkScalar pos[],
646 int colorCount,
647 SkTileMode mode,
648 uint32_t flags,
649 const SkMatrix* localMatrix) {
650 SkColorConverter converter(colors, colorCount);
651 return MakeTwoPointConical(start, startRadius, end, endRadius, converter.fColors4f.begin(),
652 nullptr, pos, colorCount, mode, flags, localMatrix);
653 }
654
SkRegisterTwoPointConicalGradientShaderFlattenable()655 void SkRegisterTwoPointConicalGradientShaderFlattenable() {
656 SK_REGISTER_FLATTENABLE(SkTwoPointConicalGradient);
657 }
658