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/SkFloatingPoint.h"
9 #include "src/core/SkRasterPipeline.h"
10 #include "src/core/SkReadBuffer.h"
11 #include "src/core/SkWriteBuffer.h"
12 #include "src/shaders/gradients/SkTwoPointConicalGradient.h"
13 
14 #include <utility>
15 
16 // Please see https://skia.org/dev/design/conical for how our shader works.
17 
set(SkScalar r0,SkScalar r1,SkMatrix * matrix)18 bool SkTwoPointConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix* matrix) {
19     fIsSwapped = false;
20     fFocalX = sk_ieee_float_divide(r0, (r0 - r1));
21     if (SkScalarNearlyZero(fFocalX - 1)) {
22         // swap r0, r1
23         matrix->postTranslate(-1, 0);
24         matrix->postScale(-1, 1);
25         std::swap(r0, r1);
26         fFocalX = 0; // because r0 is now 0
27         fIsSwapped = true;
28     }
29 
30     // Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
31     const SkPoint from[2]   = { {fFocalX, 0}, {1, 0} };
32     const SkPoint to[2]     = { {0, 0}, {1, 0} };
33     SkMatrix focalMatrix;
34     if (!focalMatrix.setPolyToPoly(from, to, 2)) {
35         return false;
36     }
37     matrix->postConcat(focalMatrix);
38     fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f)
39 
40     // The following transformations are just to accelerate the shader computation by saving
41     // some arithmatic operations.
42     if (this->isFocalOnCircle()) {
43         matrix->postScale(0.5, 0.5);
44     } else {
45         matrix->postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
46     }
47     matrix->postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f|
48     return true;
49 }
50 
Create(const SkPoint & c0,SkScalar r0,const SkPoint & c1,SkScalar r1,const Descriptor & desc)51 sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0,
52                                                   const SkPoint& c1, SkScalar r1,
53                                                   const Descriptor& desc) {
54     SkMatrix gradientMatrix;
55     Type     gradientType;
56 
57     if (SkScalarNearlyZero((c0 - c1).length())) {
58         if (SkScalarNearlyZero(std::max(r0, r1)) || SkScalarNearlyEqual(r0, r1)) {
59             // Degenerate case; avoid dividing by zero. Should have been caught by caller but
60             // just in case, recheck here.
61             return nullptr;
62         }
63         // Concentric case: we can pretend we're radial (with a tiny twist).
64         const SkScalar scale = sk_ieee_float_divide(1, std::max(r0, r1));
65         gradientMatrix = SkMatrix::MakeTrans(-c1.x(), -c1.y());
66         gradientMatrix.postScale(scale, scale);
67 
68         gradientType = Type::kRadial;
69     } else {
70         const SkPoint centers[2] = { c0    , c1     };
71         const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
72 
73         if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) {
74             // Degenerate case.
75             return nullptr;
76         }
77 
78         gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
79     }
80 
81     FocalData focalData;
82     if (gradientType == Type::kFocal) {
83         const auto dCenter = (c0 - c1).length();
84         if (!focalData.set(r0 / dCenter, r1 / dCenter, &gradientMatrix)) {
85             return nullptr;
86         }
87     }
88     return sk_sp<SkShader>(new SkTwoPointConicalGradient(c0, r0, c1, r1, desc,
89                                                          gradientType, gradientMatrix, focalData));
90 }
91 
SkTwoPointConicalGradient(const SkPoint & start,SkScalar startRadius,const SkPoint & end,SkScalar endRadius,const Descriptor & desc,Type type,const SkMatrix & gradientMatrix,const FocalData & data)92 SkTwoPointConicalGradient::SkTwoPointConicalGradient(
93         const SkPoint& start, SkScalar startRadius,
94         const SkPoint& end, SkScalar endRadius,
95         const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data)
96     : SkGradientShaderBase(desc, gradientMatrix)
97     , fCenter1(start)
98     , fCenter2(end)
99     , fRadius1(startRadius)
100     , fRadius2(endRadius)
101     , fType(type)
102 {
103     // this is degenerate, and should be caught by our caller
104     SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
105     if (type == Type::kFocal) {
106         fFocalData = data;
107     }
108 }
109 
isOpaque() const110 bool SkTwoPointConicalGradient::isOpaque() const {
111     // Because areas outside the cone are left untouched, we cannot treat the
112     // shader as opaque even if the gradient itself is opaque.
113     // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
114     return false;
115 }
116 
117 // Returns the original non-sorted version of the gradient
asAGradient(GradientInfo * info) const118 SkShader::GradientType SkTwoPointConicalGradient::asAGradient(GradientInfo* info) const {
119     if (info) {
120         commonAsAGradient(info);
121         info->fPoint[0] = fCenter1;
122         info->fPoint[1] = fCenter2;
123         info->fRadius[0] = fRadius1;
124         info->fRadius[1] = fRadius2;
125     }
126     return kConical_GradientType;
127 }
128 
CreateProc(SkReadBuffer & buffer)129 sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) {
130     DescriptorScope desc;
131     if (!desc.unflatten(buffer)) {
132         return nullptr;
133     }
134     SkPoint c1 = buffer.readPoint();
135     SkPoint c2 = buffer.readPoint();
136     SkScalar r1 = buffer.readScalar();
137     SkScalar r2 = buffer.readScalar();
138 
139     if (buffer.isVersionLT(SkPicturePriv::k2PtConicalNoFlip_Version) && buffer.readBool()) {
140         using std::swap;
141         // legacy flipped gradient
142         swap(c1, c2);
143         swap(r1, r2);
144 
145         SkColor4f* colors = desc.mutableColors();
146         SkScalar* pos = desc.mutablePos();
147         const int last = desc.fCount - 1;
148         const int half = desc.fCount >> 1;
149         for (int i = 0; i < half; ++i) {
150             swap(colors[i], colors[last - i]);
151             if (pos) {
152                 SkScalar tmp = pos[i];
153                 pos[i] = SK_Scalar1 - pos[last - i];
154                 pos[last - i] = SK_Scalar1 - tmp;
155             }
156         }
157         if (pos) {
158             if (desc.fCount & 1) {
159                 pos[half] = SK_Scalar1 - pos[half];
160             }
161         }
162     }
163     if (!buffer.isValid()) {
164         return nullptr;
165     }
166     return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors,
167                                                  std::move(desc.fColorSpace), desc.fPos,
168                                                  desc.fCount, desc.fTileMode, desc.fGradFlags,
169                                                  desc.fLocalMatrix);
170 }
171 
flatten(SkWriteBuffer & buffer) const172 void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
173     this->INHERITED::flatten(buffer);
174     buffer.writePoint(fCenter1);
175     buffer.writePoint(fCenter2);
176     buffer.writeScalar(fRadius1);
177     buffer.writeScalar(fRadius2);
178 }
179 
appendGradientStages(SkArenaAlloc * alloc,SkRasterPipeline * p,SkRasterPipeline * postPipeline) const180 void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
181                                                      SkRasterPipeline* postPipeline) const {
182     const auto dRadius = fRadius2 - fRadius1;
183 
184     if (fType == Type::kRadial) {
185         p->append(SkRasterPipeline::xy_to_radius);
186 
187         // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
188         auto scale =  std::max(fRadius1, fRadius2) / dRadius;
189         auto bias  = -fRadius1 / dRadius;
190 
191         p->append_matrix(alloc, SkMatrix::Concat(SkMatrix::MakeTrans(bias, 0),
192                                                  SkMatrix::MakeScale(scale, 1)));
193         return;
194     }
195 
196     if (fType == Type::kStrip) {
197         auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
198         SkScalar scaledR0 = fRadius1 / this->getCenterX1();
199         ctx->fP0 = scaledR0 * scaledR0;
200         p->append(SkRasterPipeline::xy_to_2pt_conical_strip, ctx);
201         p->append(SkRasterPipeline::mask_2pt_conical_nan, ctx);
202         postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
203         return;
204     }
205 
206     auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
207     ctx->fP0 = 1/fFocalData.fR1;
208     ctx->fP1 = fFocalData.fFocalX;
209 
210     if (fFocalData.isFocalOnCircle()) {
211         p->append(SkRasterPipeline::xy_to_2pt_conical_focal_on_circle);
212     } else if (fFocalData.isWellBehaved()) {
213         p->append(SkRasterPipeline::xy_to_2pt_conical_well_behaved, ctx);
214     } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
215         p->append(SkRasterPipeline::xy_to_2pt_conical_smaller, ctx);
216     } else {
217         p->append(SkRasterPipeline::xy_to_2pt_conical_greater, ctx);
218     }
219 
220     if (!fFocalData.isWellBehaved()) {
221         p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx);
222     }
223     if (1 - fFocalData.fFocalX < 0) {
224         p->append(SkRasterPipeline::negate_x);
225     }
226     if (!fFocalData.isNativelyFocal()) {
227         p->append(SkRasterPipeline::alter_2pt_conical_compensate_focal, ctx);
228     }
229     if (fFocalData.isSwapped()) {
230         p->append(SkRasterPipeline::alter_2pt_conical_unswap);
231     }
232     if (!fFocalData.isWellBehaved()) {
233         postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
234     }
235 }
236 
transformT(skvm::Builder * p,skvm::Uniforms * uniforms,skvm::F32 x,skvm::F32 y,skvm::I32 * mask) const237 skvm::F32 SkTwoPointConicalGradient::transformT(skvm::Builder* p, skvm::Uniforms* uniforms,
238                                                 skvm::F32 x, skvm::F32 y, skvm::I32* mask) const {
239     // See https://skia.org/dev/design/conical, and onAppendStages() above.
240     // There's a lot going on here, and I'm not really sure what's independent
241     // or disjoint, what can be reordered, simplified, etc.  Tweak carefully.
242 
243     if (fType == Type::kRadial) {
244         float denom = 1.0f / (fRadius2 - fRadius1),
245               scale = std::max(fRadius1, fRadius2) * denom,
246                bias =                  -fRadius1 * denom;
247         return p->mad(p->norm(x,y), p->uniformF(uniforms->pushF(scale))
248                                   , p->uniformF(uniforms->pushF(bias )));
249     }
250 
251     if (fType == Type::kStrip) {
252         float r = fRadius1 / this->getCenterX1();
253         skvm::F32 t = p->add(x, p->sqrt(p->sub(p->splat(r*r),
254                                         p->mul(y,y))));
255 
256         *mask = p->eq(t,t);   // t != NaN
257         return t;
258     }
259 
260     const skvm::F32 invR1 = p->uniformF(uniforms->pushF(1 / fFocalData.fR1));
261 
262     skvm::F32 t;
263     if (fFocalData.isFocalOnCircle()) {
264         t = p->mad(p->div(y,x),y,x);       // (x^2 + y^2) / x  ~~>  x + y^2/x  ~~>  y/x * y + x
265     } else if (fFocalData.isWellBehaved()) {
266         t = p->sub(p->norm(x,y), p->mul(x, invR1));
267     } else {
268         skvm::F32 k = p->sqrt(p->sub(p->mul(x,x),
269                                      p->mul(y,y)));
270         if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
271             k = -k;
272         }
273         t = p->sub(k, p->mul(x, invR1));
274     }
275 
276     if (!fFocalData.isWellBehaved()) {
277         // TODO: not sure why we consider t == 0 degenerate
278         *mask = p->gt(t, p->splat(0.0f));  // t > 0 and implicitly, t != NaN
279     }
280 
281     const skvm::F32 focalX = p->uniformF(uniforms->pushF(fFocalData.fFocalX));
282     if (1 - fFocalData.fFocalX < 0)    { t = -t; }
283     if (!fFocalData.isNativelyFocal()) { t = p->add(t, focalX); }
284     if (fFocalData.isSwapped())        { t = p->sub(p->splat(1.0f), t); }
285     return t;
286 }
287 
288 /////////////////////////////////////////////////////////////////////
289 
290 #if SK_SUPPORT_GPU
291 
292 #include "src/gpu/gradients/GrGradientShader.h"
293 
asFragmentProcessor(const GrFPArgs & args) const294 std::unique_ptr<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor(
295         const GrFPArgs& args) const {
296     return GrGradientShader::MakeConical(*this, args);
297 }
298 
299 #endif
300