• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::Translate(-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.isValid()) {
140         return nullptr;
141     }
142     return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors,
143                                                  std::move(desc.fColorSpace), desc.fPos,
144                                                  desc.fCount, desc.fTileMode, desc.fGradFlags,
145                                                  desc.fLocalMatrix);
146 }
147 
flatten(SkWriteBuffer & buffer) const148 void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
149     this->INHERITED::flatten(buffer);
150     buffer.writePoint(fCenter1);
151     buffer.writePoint(fCenter2);
152     buffer.writeScalar(fRadius1);
153     buffer.writeScalar(fRadius2);
154 }
155 
appendGradientStages(SkArenaAlloc * alloc,SkRasterPipeline * p,SkRasterPipeline * postPipeline) const156 void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
157                                                      SkRasterPipeline* postPipeline) const {
158     const auto dRadius = fRadius2 - fRadius1;
159 
160     if (fType == Type::kRadial) {
161         p->append(SkRasterPipeline::xy_to_radius);
162 
163         // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
164         auto scale =  std::max(fRadius1, fRadius2) / dRadius;
165         auto bias  = -fRadius1 / dRadius;
166 
167         p->append_matrix(alloc, SkMatrix::Translate(bias, 0) * SkMatrix::Scale(scale, 1));
168         return;
169     }
170 
171     if (fType == Type::kStrip) {
172         auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
173         SkScalar scaledR0 = fRadius1 / this->getCenterX1();
174         ctx->fP0 = scaledR0 * scaledR0;
175         p->append(SkRasterPipeline::xy_to_2pt_conical_strip, ctx);
176         p->append(SkRasterPipeline::mask_2pt_conical_nan, ctx);
177         postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
178         return;
179     }
180 
181     auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
182     ctx->fP0 = 1/fFocalData.fR1;
183     ctx->fP1 = fFocalData.fFocalX;
184 
185     if (fFocalData.isFocalOnCircle()) {
186         p->append(SkRasterPipeline::xy_to_2pt_conical_focal_on_circle);
187     } else if (fFocalData.isWellBehaved()) {
188         p->append(SkRasterPipeline::xy_to_2pt_conical_well_behaved, ctx);
189     } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
190         p->append(SkRasterPipeline::xy_to_2pt_conical_smaller, ctx);
191     } else {
192         p->append(SkRasterPipeline::xy_to_2pt_conical_greater, ctx);
193     }
194 
195     if (!fFocalData.isWellBehaved()) {
196         p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx);
197     }
198     if (1 - fFocalData.fFocalX < 0) {
199         p->append(SkRasterPipeline::negate_x);
200     }
201     if (!fFocalData.isNativelyFocal()) {
202         p->append(SkRasterPipeline::alter_2pt_conical_compensate_focal, ctx);
203     }
204     if (fFocalData.isSwapped()) {
205         p->append(SkRasterPipeline::alter_2pt_conical_unswap);
206     }
207     if (!fFocalData.isWellBehaved()) {
208         postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
209     }
210 }
211 
transformT(skvm::Builder * p,skvm::Uniforms * uniforms,skvm::Coord coord,skvm::I32 * mask) const212 skvm::F32 SkTwoPointConicalGradient::transformT(skvm::Builder* p, skvm::Uniforms* uniforms,
213                                                 skvm::Coord coord, skvm::I32* mask) const {
214     auto mag = [](skvm::F32 x, skvm::F32 y) { return sqrt(x*x + y*y); };
215 
216     // See https://skia.org/dev/design/conical, and onAppendStages() above.
217     // There's a lot going on here, and I'm not really sure what's independent
218     // or disjoint, what can be reordered, simplified, etc.  Tweak carefully.
219 
220     const skvm::F32 x = coord.x,
221                     y = coord.y;
222     if (fType == Type::kRadial) {
223         float denom = 1.0f / (fRadius2 - fRadius1),
224               scale = std::max(fRadius1, fRadius2) * denom,
225                bias =                  -fRadius1 * denom;
226         return mag(x,y) * p->uniformF(uniforms->pushF(scale))
227                         + p->uniformF(uniforms->pushF(bias ));
228     }
229 
230     if (fType == Type::kStrip) {
231         float r = fRadius1 / this->getCenterX1();
232         skvm::F32 t = x + sqrt(p->splat(r*r) - y*y);
233 
234         *mask = (t == t);   // t != NaN
235         return t;
236     }
237 
238     const skvm::F32 invR1 = p->uniformF(uniforms->pushF(1 / fFocalData.fR1));
239 
240     skvm::F32 t;
241     if (fFocalData.isFocalOnCircle()) {
242         t = (y/x) * y + x;       // (x^2 + y^2) / x  ~~>  x + y^2/x  ~~>  y/x * y + x
243     } else if (fFocalData.isWellBehaved()) {
244         t = mag(x,y) - x*invR1;
245     } else {
246         skvm::F32 k = sqrt(x*x - y*y);
247         if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
248             k = -k;
249         }
250         t = k - x*invR1;
251     }
252 
253     if (!fFocalData.isWellBehaved()) {
254         // TODO: not sure why we consider t == 0 degenerate
255         *mask = (t > 0.0f);  // and implicitly, t != NaN
256     }
257 
258     const skvm::F32 focalX = p->uniformF(uniforms->pushF(fFocalData.fFocalX));
259     if (1 - fFocalData.fFocalX < 0)    { t = -t; }
260     if (!fFocalData.isNativelyFocal()) { t += focalX; }
261     if ( fFocalData.isSwapped())       { t = 1.0f - t; }
262     return t;
263 }
264 
265 /////////////////////////////////////////////////////////////////////
266 
267 #if SK_SUPPORT_GPU
268 
269 #include "src/gpu/gradients/GrGradientShader.h"
270 
asFragmentProcessor(const GrFPArgs & args) const271 std::unique_ptr<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor(
272         const GrFPArgs& args) const {
273     return GrGradientShader::MakeConical(*this, args);
274 }
275 
276 #endif
277