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 #include "src/shaders/gradients/SkConicalGradient.h"
8
9 #include "include/core/SkColor.h"
10 #include "include/core/SkColorSpace.h"
11 #include "include/core/SkMatrix.h"
12 #include "include/core/SkShader.h"
13 #include "include/core/SkTileMode.h"
14 #include "include/effects/SkGradientShader.h"
15 #include "include/private/base/SkAssert.h"
16 #include "include/private/base/SkFloatingPoint.h"
17 #include "include/private/base/SkTArray.h"
18 #include "src/base/SkArenaAlloc.h"
19 #include "src/core/SkRasterPipeline.h"
20 #include "src/core/SkRasterPipelineOpContexts.h"
21 #include "src/core/SkRasterPipelineOpList.h"
22 #include "src/core/SkReadBuffer.h"
23 #include "src/core/SkWriteBuffer.h"
24 #include "src/shaders/SkShaderBase.h"
25 #include "src/shaders/gradients/SkGradientBaseShader.h"
26
27 #include <algorithm>
28 #include <cmath>
29 #include <cstdint>
30 #include <utility>
31
set(SkScalar r0,SkScalar r1,SkMatrix * matrix)32 bool SkConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix* matrix) {
33 fIsSwapped = false;
34 fFocalX = sk_ieee_float_divide(r0, (r0 - r1));
35 if (SkScalarNearlyZero(fFocalX - 1)) {
36 // swap r0, r1
37 matrix->postTranslate(-1, 0);
38 matrix->postScale(-1, 1);
39 std::swap(r0, r1);
40 fFocalX = 0; // because r0 is now 0
41 fIsSwapped = true;
42 }
43
44 // Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
45 const SkPoint from[2] = { {fFocalX, 0}, {1, 0} };
46 const SkPoint to[2] = { {0, 0}, {1, 0} };
47 SkMatrix focalMatrix;
48 if (!focalMatrix.setPolyToPoly(from, to, 2)) {
49 return false;
50 }
51 matrix->postConcat(focalMatrix);
52 fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f)
53
54 // The following transformations are just to accelerate the shader computation by saving
55 // some arithmatic operations.
56 if (this->isFocalOnCircle()) {
57 matrix->postScale(0.5, 0.5);
58 } else {
59 matrix->postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
60 }
61 matrix->postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f|
62 return true;
63 }
64
MapToUnitX(const SkPoint & startCenter,const SkPoint & endCenter,SkMatrix * dstMatrix)65 bool SkConicalGradient::MapToUnitX(const SkPoint &startCenter,
66 const SkPoint &endCenter,
67 SkMatrix* dstMatrix) {
68 const SkPoint centers[2] = { startCenter, endCenter };
69 const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
70
71 return dstMatrix->setPolyToPoly(centers, unitvec, 2);
72 }
73
Create(const SkPoint & c0,SkScalar r0,const SkPoint & c1,SkScalar r1,const Descriptor & desc,const SkMatrix * localMatrix)74 sk_sp<SkShader> SkConicalGradient::Create(const SkPoint& c0,
75 SkScalar r0,
76 const SkPoint& c1,
77 SkScalar r1,
78 const Descriptor& desc,
79 const SkMatrix* localMatrix) {
80 SkMatrix gradientMatrix;
81 Type gradientType;
82
83 if (SkScalarNearlyZero((c0 - c1).length())) {
84 if (SkScalarNearlyZero(std::max(r0, r1)) || SkScalarNearlyEqual(r0, r1)) {
85 // Degenerate case; avoid dividing by zero. Should have been caught by caller but
86 // just in case, recheck here.
87 return nullptr;
88 }
89 // Concentric case: we can pretend we're radial (with a tiny twist).
90 const SkScalar scale = sk_ieee_float_divide(1, std::max(r0, r1));
91 gradientMatrix = SkMatrix::Translate(-c1.x(), -c1.y());
92 gradientMatrix.postScale(scale, scale);
93
94 gradientType = Type::kRadial;
95 } else {
96 if (!MapToUnitX(c0, c1, &gradientMatrix)) {
97 // Degenerate case.
98 return nullptr;
99 }
100
101 gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
102 }
103
104 FocalData focalData;
105 if (gradientType == Type::kFocal) {
106 const auto dCenter = (c0 - c1).length();
107 if (!focalData.set(r0 / dCenter, r1 / dCenter, &gradientMatrix)) {
108 return nullptr;
109 }
110 }
111
112 sk_sp<SkShader> s = sk_make_sp<SkConicalGradient>(
113 c0, r0, c1, r1, desc, gradientType, gradientMatrix, focalData);
114 return s->makeWithLocalMatrix(localMatrix ? *localMatrix : SkMatrix::I());
115 }
116
SkConicalGradient(const SkPoint & start,SkScalar startRadius,const SkPoint & end,SkScalar endRadius,const Descriptor & desc,Type type,const SkMatrix & gradientMatrix,const FocalData & data)117 SkConicalGradient::SkConicalGradient(const SkPoint& start,
118 SkScalar startRadius,
119 const SkPoint& end,
120 SkScalar endRadius,
121 const Descriptor& desc,
122 Type type,
123 const SkMatrix& gradientMatrix,
124 const FocalData& data)
125 : SkGradientBaseShader(desc, gradientMatrix)
126 , fCenter1(start)
127 , fCenter2(end)
128 , fRadius1(startRadius)
129 , fRadius2(endRadius)
130 , fType(type) {
131 // this is degenerate, and should be caught by our caller
132 SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
133 if (type == Type::kFocal) {
134 fFocalData = data;
135 }
136 }
137
isOpaque() const138 bool SkConicalGradient::isOpaque() const {
139 // Because areas outside the cone are left untouched, we cannot treat the
140 // shader as opaque even if the gradient itself is opaque.
141 // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
142 return false;
143 }
144
145 // Returns the original non-sorted version of the gradient
asGradient(GradientInfo * info,SkMatrix * localMatrix) const146 SkShaderBase::GradientType SkConicalGradient::asGradient(GradientInfo* info,
147 SkMatrix* localMatrix) const {
148 if (info) {
149 commonAsAGradient(info);
150 info->fPoint[0] = fCenter1;
151 info->fPoint[1] = fCenter2;
152 info->fRadius[0] = fRadius1;
153 info->fRadius[1] = fRadius2;
154 }
155 if (localMatrix) {
156 *localMatrix = SkMatrix::I();
157 }
158 return GradientType::kConical;
159 }
160
CreateProc(SkReadBuffer & buffer)161 sk_sp<SkFlattenable> SkConicalGradient::CreateProc(SkReadBuffer& buffer) {
162 DescriptorScope desc;
163 SkMatrix legacyLocalMatrix, *lmPtr = nullptr;
164 if (!desc.unflatten(buffer, &legacyLocalMatrix)) {
165 return nullptr;
166 }
167 if (!legacyLocalMatrix.isIdentity()) {
168 lmPtr = &legacyLocalMatrix;
169 }
170 SkPoint c1 = buffer.readPoint();
171 SkPoint c2 = buffer.readPoint();
172 SkScalar r1 = buffer.readScalar();
173 SkScalar r2 = buffer.readScalar();
174
175 if (!buffer.isValid()) {
176 return nullptr;
177 }
178 return SkGradientShader::MakeTwoPointConical(c1,
179 r1,
180 c2,
181 r2,
182 desc.fColors,
183 std::move(desc.fColorSpace),
184 desc.fPositions,
185 desc.fColorCount,
186 desc.fTileMode,
187 desc.fInterpolation,
188 lmPtr);
189 }
190
flatten(SkWriteBuffer & buffer) const191 void SkConicalGradient::flatten(SkWriteBuffer& buffer) const {
192 this->SkGradientBaseShader::flatten(buffer);
193 buffer.writePoint(fCenter1);
194 buffer.writePoint(fCenter2);
195 buffer.writeScalar(fRadius1);
196 buffer.writeScalar(fRadius2);
197 }
198
appendGradientStages(SkArenaAlloc * alloc,SkRasterPipeline * p,SkRasterPipeline * postPipeline) const199 void SkConicalGradient::appendGradientStages(SkArenaAlloc* alloc,
200 SkRasterPipeline* p,
201 SkRasterPipeline* postPipeline) const {
202 const auto dRadius = fRadius2 - fRadius1;
203
204 if (fType == Type::kRadial) {
205 p->append(SkRasterPipelineOp::xy_to_radius);
206
207 // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
208 auto scale = std::max(fRadius1, fRadius2) / dRadius;
209 auto bias = -fRadius1 / dRadius;
210
211 p->appendMatrix(alloc, SkMatrix::Translate(bias, 0) * SkMatrix::Scale(scale, 1));
212 return;
213 }
214
215 if (fType == Type::kStrip) {
216 auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
217 SkScalar scaledR0 = fRadius1 / this->getCenterX1();
218 ctx->fP0 = scaledR0 * scaledR0;
219 p->append(SkRasterPipelineOp::xy_to_2pt_conical_strip, ctx);
220 p->append(SkRasterPipelineOp::mask_2pt_conical_nan, ctx);
221 postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
222 return;
223 }
224
225 auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
226 ctx->fP0 = 1 / fFocalData.fR1;
227 ctx->fP1 = fFocalData.fFocalX;
228
229 if (fFocalData.isFocalOnCircle()) {
230 p->append(SkRasterPipelineOp::xy_to_2pt_conical_focal_on_circle);
231 } else if (fFocalData.isWellBehaved()) {
232 p->append(SkRasterPipelineOp::xy_to_2pt_conical_well_behaved, ctx);
233 } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
234 p->append(SkRasterPipelineOp::xy_to_2pt_conical_smaller, ctx);
235 } else {
236 p->append(SkRasterPipelineOp::xy_to_2pt_conical_greater, ctx);
237 }
238
239 if (!fFocalData.isWellBehaved()) {
240 p->append(SkRasterPipelineOp::mask_2pt_conical_degenerates, ctx);
241 }
242 if (1 - fFocalData.fFocalX < 0) {
243 p->append(SkRasterPipelineOp::negate_x);
244 }
245 if (!fFocalData.isNativelyFocal()) {
246 p->append(SkRasterPipelineOp::alter_2pt_conical_compensate_focal, ctx);
247 }
248 if (fFocalData.isSwapped()) {
249 p->append(SkRasterPipelineOp::alter_2pt_conical_unswap);
250 }
251 if (!fFocalData.isWellBehaved()) {
252 postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
253 }
254 }
255
256 // assumes colors is SkColor4f* and pos is SkScalar*
257 #define EXPAND_1_COLOR(count) \
258 SkColor4f tmp[2]; \
259 do { \
260 if (1 == count) { \
261 tmp[0] = tmp[1] = colors[0]; \
262 colors = tmp; \
263 pos = nullptr; \
264 count = 2; \
265 } \
266 } while (0)
267
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)268 sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
269 SkScalar startRadius,
270 const SkPoint& end,
271 SkScalar endRadius,
272 const SkColor4f colors[],
273 sk_sp<SkColorSpace> colorSpace,
274 const SkScalar pos[],
275 int colorCount,
276 SkTileMode mode,
277 const Interpolation& interpolation,
278 const SkMatrix* localMatrix) {
279 if (startRadius < 0 || endRadius < 0) {
280 return nullptr;
281 }
282 if (!SkGradientBaseShader::ValidGradient(colors, colorCount, mode, interpolation)) {
283 return nullptr;
284 }
285 if (SkScalarNearlyZero((start - end).length(), SkGradientBaseShader::kDegenerateThreshold)) {
286 // If the center positions are the same, then the gradient is the radial variant of a 2 pt
287 // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate
288 // (startRadius == endRadius).
289 if (SkScalarNearlyEqual(
290 startRadius, endRadius, SkGradientBaseShader::kDegenerateThreshold)) {
291 // Degenerate case, where the interpolation region area approaches zero. The proper
292 // behavior depends on the tile mode, which is consistent with the default degenerate
293 // gradient behavior, except when mode = clamp and the radii > 0.
294 if (mode == SkTileMode::kClamp &&
295 endRadius > SkGradientBaseShader::kDegenerateThreshold) {
296 // The interpolation region becomes an infinitely thin ring at the radius, so the
297 // final gradient will be the first color repeated from p=0 to 1, and then a hard
298 // stop switching to the last color at p=1.
299 static constexpr SkScalar circlePos[3] = {0, 1, 1};
300 SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]};
301 return MakeRadial(start,
302 endRadius,
303 reColors,
304 std::move(colorSpace),
305 circlePos,
306 3,
307 mode,
308 interpolation,
309 localMatrix);
310 } else {
311 // Otherwise use the default degenerate case
312 return SkGradientBaseShader::MakeDegenerateGradient(
313 colors, pos, colorCount, std::move(colorSpace), mode);
314 }
315 } else if (SkScalarNearlyZero(startRadius, SkGradientBaseShader::kDegenerateThreshold)) {
316 // We can treat this gradient as radial, which is faster. If we got here, we know
317 // that endRadius is not equal to 0, so this produces a meaningful gradient
318 return MakeRadial(start,
319 endRadius,
320 colors,
321 std::move(colorSpace),
322 pos,
323 colorCount,
324 mode,
325 interpolation,
326 localMatrix);
327 }
328 // Else it's the 2pt conical radial variant with no degenerate radii, so fall through to the
329 // regular 2pt constructor.
330 }
331
332 if (localMatrix && !localMatrix->invert(nullptr)) {
333 return nullptr;
334 }
335 EXPAND_1_COLOR(colorCount);
336
337 SkGradientBaseShader::Descriptor desc(
338 colors, std::move(colorSpace), pos, colorCount, mode, interpolation);
339 return SkConicalGradient::Create(start, startRadius, end, endRadius, desc, localMatrix);
340 }
341
342 #undef EXPAND_1_COLOR
343
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)344 sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
345 SkScalar startRadius,
346 const SkPoint& end,
347 SkScalar endRadius,
348 const SkColor colors[],
349 const SkScalar pos[],
350 int colorCount,
351 SkTileMode mode,
352 uint32_t flags,
353 const SkMatrix* localMatrix) {
354 SkColorConverter converter(colors, colorCount);
355 return MakeTwoPointConical(start,
356 startRadius,
357 end,
358 endRadius,
359 converter.fColors4f.begin(),
360 nullptr,
361 pos,
362 colorCount,
363 mode,
364 flags,
365 localMatrix);
366 }
367
SkRegisterConicalGradientShaderFlattenable()368 void SkRegisterConicalGradientShaderFlattenable() {
369 SK_REGISTER_FLATTENABLE(SkConicalGradient);
370 // Previous name
371 SkFlattenable::Register("SkTwoPointConicalGradient", SkConicalGradient::CreateProc);
372 }
373