blob: 7c6b3342af660f6fd14c210dd79c4699a7329c0b [file] [log] [blame]
rileya@google.com589708b2012-07-26 20:04:23 +00001/*
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 "SkTwoPointConicalGradient.h"
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +00009
Florin Malitaa66ef2d2017-06-28 10:02:40 -040010#include "SkRasterPipeline.h"
Florin Malitad4e9ec82017-10-25 18:00:26 -040011#include "SkReadBuffer.h"
12#include "SkWriteBuffer.h"
Florin Malitaa66ef2d2017-06-28 10:02:40 -040013#include "../../jumper/SkJumper.h"
14
Yuqian Lid208a882018-01-04 10:08:42 -050015// Please see https://skia.org/dev/design/conical for how our shader works.
16
17void SkTwoPointConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix& matrix) {
18#ifdef SK_SUPPORT_LEGACY_2PT_CONICAL
19 // Just initialize the memory. We are not supposed to do anything in legacy mode.
20 fIsSwapped = false;
21 fFocalX = fR1 = 0;
22#else
23 fIsSwapped = false;
24 fFocalX = r0 / (r0 - r1);
25 if (SkScalarNearlyZero(fFocalX - 1)) {
26 // swap r0, r1
27 matrix.postTranslate(-1, 0);
28 matrix.postScale(-1, 1);
29 std::swap(r0, r1);
30 fFocalX = 0; // because r0 is now 0
31 fIsSwapped = true;
32 }
33
34 // Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
35 const SkPoint from[2] = { {fFocalX, 0}, {1, 0} };
36 const SkPoint to[2] = { {0, 0}, {1, 0} };
37 SkMatrix focalMatrix;
38 if (!focalMatrix.setPolyToPoly(from, to, 2)) {
39 SkDEBUGFAILF("Mapping focal point failed unexpectedly for focalX = %f.\n", fFocalX);
40 // We won't be able to draw the gradient; at least make sure that we initialize the
41 // memory to prevent security issues.
42 focalMatrix = SkMatrix::MakeScale(1, 1);
43 }
44 matrix.postConcat(focalMatrix);
45 fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f)
46
47 // The following transformations are just to accelerate the shader computation by saving
48 // some arithmatic operations.
49 if (this->isFocalOnCircle()) {
50 matrix.postScale(0.5, 0.5);
51 } else {
52 matrix.postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
53 }
54 matrix.postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f|
55#endif
56}
57
Florin Malita9c2212f2017-07-29 18:23:10 -040058sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0,
59 const SkPoint& c1, SkScalar r1,
Florin Malita5f379a82017-10-18 16:22:35 -040060 const Descriptor& desc) {
Florin Malita9c2212f2017-07-29 18:23:10 -040061 SkMatrix gradientMatrix;
62 Type gradientType;
63
64 if (SkScalarNearlyZero((c0 - c1).length())) {
65 // Concentric case: we can pretend we're radial (with a tiny twist).
Florin Malita5f379a82017-10-18 16:22:35 -040066 const SkScalar scale = 1.0f / SkTMax(r0, r1);
Florin Malita9c2212f2017-07-29 18:23:10 -040067 gradientMatrix = SkMatrix::MakeTrans(-c1.x(), -c1.y());
Florin Malita5f379a82017-10-18 16:22:35 -040068 gradientMatrix.postScale(scale, scale);
Florin Malita9c2212f2017-07-29 18:23:10 -040069
70 gradientType = Type::kRadial;
71 } else {
72 const SkPoint centers[2] = { c0 , c1 };
73 const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
74
75 if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) {
76 // Degenerate case.
77 return nullptr;
78 }
79
Yuqian Lid208a882018-01-04 10:08:42 -050080 gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
Florin Malita9c2212f2017-07-29 18:23:10 -040081 }
82
Yuqian Lid208a882018-01-04 10:08:42 -050083 FocalData focalData;
84 if (gradientType == Type::kFocal) {
85 const auto dCenter = (c0 - c1).length();
86 focalData.set(r0 / dCenter, r1 / dCenter, gradientMatrix); // this may change gradientMatrix
87 }
Florin Malita5f379a82017-10-18 16:22:35 -040088 return sk_sp<SkShader>(new SkTwoPointConicalGradient(c0, r0, c1, r1, desc,
Yuqian Lid208a882018-01-04 10:08:42 -050089 gradientType, gradientMatrix, focalData));
Florin Malita9c2212f2017-07-29 18:23:10 -040090}
91
rileya@google.com589708b2012-07-26 20:04:23 +000092SkTwoPointConicalGradient::SkTwoPointConicalGradient(
reed@google.com3d3a8602013-05-24 14:58:44 +000093 const SkPoint& start, SkScalar startRadius,
94 const SkPoint& end, SkScalar endRadius,
Yuqian Lid208a882018-01-04 10:08:42 -050095 const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data)
Florin Malita9c2212f2017-07-29 18:23:10 -040096 : SkGradientShaderBase(desc, gradientMatrix)
reedaddf2ed2014-08-11 08:28:24 -070097 , fCenter1(start)
98 , fCenter2(end)
99 , fRadius1(startRadius)
100 , fRadius2(endRadius)
Florin Malita9c2212f2017-07-29 18:23:10 -0400101 , fType(type)
reedaddf2ed2014-08-11 08:28:24 -0700102{
rileya@google.com589708b2012-07-26 20:04:23 +0000103 // this is degenerate, and should be caught by our caller
104 SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
Yuqian Lid208a882018-01-04 10:08:42 -0500105 if (type == Type::kFocal) {
106 fFocalData = data;
107 }
rileya@google.com589708b2012-07-26 20:04:23 +0000108}
109
commit-bot@chromium.org3fbab822013-03-20 00:49:57 +0000110bool SkTwoPointConicalGradient::isOpaque() const {
robertphillips@google.comcb6d97c2013-07-09 13:50:09 +0000111 // 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;
commit-bot@chromium.org3fbab822013-03-20 00:49:57 +0000115}
116
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000117// Returns the original non-sorted version of the gradient
Florin Malita5f379a82017-10-18 16:22:35 -0400118SkShader::GradientType SkTwoPointConicalGradient::asAGradient(GradientInfo* info) const {
rileya@google.com589708b2012-07-26 20:04:23 +0000119 if (info) {
Florin Malita5f379a82017-10-18 16:22:35 -0400120 commonAsAGradient(info);
rileya@google.com589708b2012-07-26 20:04:23 +0000121 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
reed60c9b582016-04-03 09:11:13 -0700129sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) {
reed9fa60da2014-08-21 07:59:51 -0700130 DescriptorScope desc;
131 if (!desc.unflatten(buffer)) {
halcanary96fcdcc2015-08-27 07:41:13 -0700132 return nullptr;
reed9fa60da2014-08-21 07:59:51 -0700133 }
134 SkPoint c1 = buffer.readPoint();
135 SkPoint c2 = buffer.readPoint();
136 SkScalar r1 = buffer.readScalar();
137 SkScalar r2 = buffer.readScalar();
138
Florin Malita5f379a82017-10-18 16:22:35 -0400139 if (buffer.isVersionLT(SkReadBuffer::k2PtConicalNoFlip_Version) && buffer.readBool()) {
140 // legacy flipped gradient
reed9fa60da2014-08-21 07:59:51 -0700141 SkTSwap(c1, c2);
142 SkTSwap(r1, r2);
143
brianosmane25d71c2016-09-28 11:27:28 -0700144 SkColor4f* colors = desc.mutableColors();
reed9fa60da2014-08-21 07:59:51 -0700145 SkScalar* pos = desc.mutablePos();
146 const int last = desc.fCount - 1;
147 const int half = desc.fCount >> 1;
148 for (int i = 0; i < half; ++i) {
149 SkTSwap(colors[i], colors[last - i]);
150 if (pos) {
151 SkScalar tmp = pos[i];
152 pos[i] = SK_Scalar1 - pos[last - i];
153 pos[last - i] = SK_Scalar1 - tmp;
154 }
155 }
156 if (pos) {
157 if (desc.fCount & 1) {
158 pos[half] = SK_Scalar1 - pos[half];
159 }
160 }
161 }
162
brianosmane25d71c2016-09-28 11:27:28 -0700163 return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors,
164 std::move(desc.fColorSpace), desc.fPos,
reed8a21c9f2016-03-08 18:50:00 -0800165 desc.fCount, desc.fTileMode, desc.fGradFlags,
reed60c9b582016-04-03 09:11:13 -0700166 desc.fLocalMatrix);
reed9fa60da2014-08-21 07:59:51 -0700167}
168
169void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
rileya@google.com589708b2012-07-26 20:04:23 +0000170 this->INHERITED::flatten(buffer);
171 buffer.writePoint(fCenter1);
172 buffer.writePoint(fCenter2);
173 buffer.writeScalar(fRadius1);
174 buffer.writeScalar(fRadius2);
175}
176
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000177#if SK_SUPPORT_GPU
178
dandov9de5b512014-06-10 14:38:28 -0700179#include "SkGr.h"
brianosman9557c272016-09-15 06:59:15 -0700180#include "SkTwoPointConicalGradient_gpu.h"
dandov9de5b512014-06-10 14:38:28 -0700181
Brian Salomonaff329b2017-08-11 09:40:37 -0400182std::unique_ptr<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor(
brianosman1638c0d2016-07-25 05:12:53 -0700183 const AsFPArgs& args) const {
brianosman839345d2016-07-22 11:04:53 -0700184 SkASSERT(args.fContext);
Brian Salomon4cbb6e62017-10-25 15:12:19 -0400185 return Gr2PtConicalGradientEffect::Make(
186 GrGradientEffect::CreateArgs(args.fContext, this, args.fLocalMatrix, fTileMode,
187 args.fDstColorSpaceInfo->colorSpace()));
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000188}
189
twiz@google.coma5e65ec2012-08-02 15:15:16 +0000190#endif
robertphillips@google.com76f9e932013-01-15 20:17:47 +0000191
Matt Sarett6cc6ae752017-04-18 18:29:12 -0400192sk_sp<SkShader> SkTwoPointConicalGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
Florin Malita39d71de2017-10-31 11:33:49 -0400193 const AutoXformColors xformedColors(*this, xformer);
Florin Malita5f379a82017-10-18 16:22:35 -0400194 return SkGradientShader::MakeTwoPointConical(fCenter1, fRadius1, fCenter2, fRadius2,
Florin Malita39d71de2017-10-31 11:33:49 -0400195 xformedColors.fColors.get(), fOrigPos, fColorCount,
Florin Malita5f379a82017-10-18 16:22:35 -0400196 fTileMode, fGradFlags, &this->getLocalMatrix());
Matt Sarett6cc6ae752017-04-18 18:29:12 -0400197}
198
199
commit-bot@chromium.org0f10f7b2014-03-13 18:02:17 +0000200#ifndef SK_IGNORE_TO_STRING
robertphillips@google.com76f9e932013-01-15 20:17:47 +0000201void SkTwoPointConicalGradient::toString(SkString* str) const {
202 str->append("SkTwoPointConicalGradient: (");
203
204 str->append("center1: (");
205 str->appendScalar(fCenter1.fX);
206 str->append(", ");
207 str->appendScalar(fCenter1.fY);
208 str->append(") radius1: ");
209 str->appendScalar(fRadius1);
210 str->append(" ");
211
212 str->append("center2: (");
213 str->appendScalar(fCenter2.fX);
214 str->append(", ");
215 str->appendScalar(fCenter2.fY);
216 str->append(") radius2: ");
217 str->appendScalar(fRadius2);
218 str->append(" ");
219
220 this->INHERITED::toString(str);
221
222 str->append(")");
223}
224#endif
Florin Malita0bb04112017-06-27 14:35:50 -0400225
Florin Malita50b20842017-07-29 19:08:28 -0400226void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
227 SkRasterPipeline* postPipeline) const {
Florin Malitaa66ef2d2017-06-28 10:02:40 -0400228 const auto dRadius = fRadius2 - fRadius1;
Florin Malitaa66ef2d2017-06-28 10:02:40 -0400229
Florin Malita9c2212f2017-07-29 18:23:10 -0400230 if (fType == Type::kRadial) {
Florin Malita0bb04112017-06-27 14:35:50 -0400231 p->append(SkRasterPipeline::xy_to_radius);
232
233 // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
Florin Malita5f379a82017-10-18 16:22:35 -0400234 auto scale = SkTMax(fRadius1, fRadius2) / dRadius;
Florin Malitaa66ef2d2017-06-28 10:02:40 -0400235 auto bias = -fRadius1 / dRadius;
Florin Malita0bb04112017-06-27 14:35:50 -0400236
Mike Reed6b59bf42017-07-03 21:26:44 -0400237 p->append_matrix(alloc, SkMatrix::Concat(SkMatrix::MakeTrans(bias, 0),
238 SkMatrix::MakeScale(scale, 1)));
Florin Malita50b20842017-07-29 19:08:28 -0400239 return;
Florin Malita0bb04112017-06-27 14:35:50 -0400240 }
241
Yuqian Lid208a882018-01-04 10:08:42 -0500242#ifdef SK_SUPPORT_LEGACY_2PT_CONICAL
Florin Malita9c2212f2017-07-29 18:23:10 -0400243 const auto dCenter = (fCenter1 - fCenter2).length();
Florin Malitaa66ef2d2017-06-28 10:02:40 -0400244
245 // Since we've squashed the centers into a unit vector, we must also scale
246 // all the coefficient variables by (1 / dCenter).
247 const auto coeffA = 1 - dRadius * dRadius / (dCenter * dCenter);
Florin Malitaa66ef2d2017-06-28 10:02:40 -0400248 auto* ctx = alloc->make<SkJumper_2PtConicalCtx>();
249 ctx->fCoeffA = coeffA;
250 ctx->fInvCoeffA = 1 / coeffA;
251 ctx->fR0 = fRadius1 / dCenter;
252 ctx->fDR = dRadius / dCenter;
253
Florin Malita9026fe12017-06-29 11:03:45 -0400254 // Is the solver guaranteed to not produce degenerates?
255 bool isWellBehaved = true;
256
Florin Malita2e409002017-06-28 14:46:54 -0400257 if (SkScalarNearlyZero(coeffA)) {
258 // The focal point is on the edge of the end circle.
259 p->append(SkRasterPipeline::xy_to_2pt_conical_linear, ctx);
Florin Malita9026fe12017-06-29 11:03:45 -0400260 isWellBehaved = false;
Florin Malita2e409002017-06-28 14:46:54 -0400261 } else {
Florin Malita5f379a82017-10-18 16:22:35 -0400262 isWellBehaved = SkScalarAbs(dRadius) >= dCenter;
263 bool isFlipped = isWellBehaved && dRadius < 0;
Yuqian Li7741c752017-12-11 14:17:47 -0500264 bool isFirst = (coeffA > 0) != isFlipped;
Florin Malita9026fe12017-06-29 11:03:45 -0400265
Florin Malita5f379a82017-10-18 16:22:35 -0400266 // We want the larger root, per spec:
267 // "For all values of ω where r(ω) > 0, starting with the value of ω nearest
268 // to positive infinity and ending with the value of ω nearest to negative
269 // infinity, draw the circumference of the circle with radius r(ω) at position
270 // (x(ω), y(ω)), with the color at ω, but only painting on the parts of the
271 // bitmap that have not yet been painted on by earlier circles in this step for
272 // this rendering of the gradient."
273 // (https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createradialgradient)
274 //
275 // ... except when the gradient is flipped.
Yuqian Li7741c752017-12-11 14:17:47 -0500276 //
277 // The first root (-b + sqrt(...)) / 2a is larger if a (i.e. coeffA) > 0 and the second
278 // root (-b - sqrt(...)) / 2a is larger if a < 0.
279 p->append(isFirst ? SkRasterPipeline::xy_to_2pt_conical_quadratic_first
280 : SkRasterPipeline::xy_to_2pt_conical_quadratic_second, ctx);
Florin Malita9026fe12017-06-29 11:03:45 -0400281 }
282
283 if (!isWellBehaved) {
Yuqian Lid208a882018-01-04 10:08:42 -0500284 p->append(SkRasterPipeline::mask_2pt_conical_degenerates_legacy, ctx);
Florin Malita9026fe12017-06-29 11:03:45 -0400285 postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
Florin Malita2e409002017-06-28 14:46:54 -0400286 }
Yuqian Lid208a882018-01-04 10:08:42 -0500287#else
288 if (fType == Type::kStrip) {
289 auto* ctx = alloc->make<SkJumper_2PtConicalCtx>();
290 SkScalar scaledR0 = fRadius1 / this->getCenterX1();
291 ctx->fP0 = scaledR0 * scaledR0;
292 p->append(SkRasterPipeline::xy_to_2pt_conical_strip, ctx);
293 p->append(SkRasterPipeline::mask_2pt_conical_nan, ctx);
294 postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
295 return;
296 }
297
298 auto* ctx = alloc->make<SkJumper_2PtConicalCtx>();
299 ctx->fP0 = 1/fFocalData.fR1;
300 ctx->fP1 = fFocalData.fFocalX;
301
302 if (fFocalData.isFocalOnCircle()) {
303 p->append(SkRasterPipeline::xy_to_2pt_conical_focal_on_circle);
304 } else if (fFocalData.isWellBehaved()) {
305 p->append(SkRasterPipeline::xy_to_2pt_conical_well_behaved, ctx);
306 } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
307 p->append(SkRasterPipeline::xy_to_2pt_conical_smaller, ctx);
308 } else {
309 p->append(SkRasterPipeline::xy_to_2pt_conical_greater, ctx);
310 }
311
312 if (!fFocalData.isWellBehaved()) {
313 p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx);
314 }
315 if (1 - fFocalData.fFocalX < 0) {
316 p->append(SkRasterPipeline::negate_x);
317 }
318 if (!fFocalData.isNativelyFocal()) {
319 p->append(SkRasterPipeline::alter_2pt_conical_compensate_focal, ctx);
320 }
321 if (fFocalData.isSwapped()) {
322 p->append(SkRasterPipeline::alter_2pt_conical_unswap);
323 }
324 if (!fFocalData.isWellBehaved()) {
325 postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
326 }
327#endif
Florin Malita0bb04112017-06-27 14:35:50 -0400328}