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 "SkTwoPointConicalGradient.h"
9
10 #include "SkRasterPipeline.h"
11 #include "../../jumper/SkJumper.h"
12
SkTwoPointConicalGradient(const SkPoint & start,SkScalar startRadius,const SkPoint & end,SkScalar endRadius,bool flippedGrad,const Descriptor & desc)13 SkTwoPointConicalGradient::SkTwoPointConicalGradient(
14 const SkPoint& start, SkScalar startRadius,
15 const SkPoint& end, SkScalar endRadius,
16 bool flippedGrad, const Descriptor& desc)
17 : SkGradientShaderBase(desc, SkMatrix::I())
18 , fCenter1(start)
19 , fCenter2(end)
20 , fRadius1(startRadius)
21 , fRadius2(endRadius)
22 , fFlippedGrad(flippedGrad)
23 {
24 // this is degenerate, and should be caught by our caller
25 SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
26 }
27
isOpaque() const28 bool SkTwoPointConicalGradient::isOpaque() const {
29 // Because areas outside the cone are left untouched, we cannot treat the
30 // shader as opaque even if the gradient itself is opaque.
31 // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
32 return false;
33 }
34
35 // Returns the original non-sorted version of the gradient
asAGradient(GradientInfo * info) const36 SkShader::GradientType SkTwoPointConicalGradient::asAGradient(
37 GradientInfo* info) const {
38 if (info) {
39 commonAsAGradient(info, fFlippedGrad);
40 info->fPoint[0] = fCenter1;
41 info->fPoint[1] = fCenter2;
42 info->fRadius[0] = fRadius1;
43 info->fRadius[1] = fRadius2;
44 if (fFlippedGrad) {
45 SkTSwap(info->fPoint[0], info->fPoint[1]);
46 SkTSwap(info->fRadius[0], info->fRadius[1]);
47 }
48 }
49 return kConical_GradientType;
50 }
51
CreateProc(SkReadBuffer & buffer)52 sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) {
53 DescriptorScope desc;
54 if (!desc.unflatten(buffer)) {
55 return nullptr;
56 }
57 SkPoint c1 = buffer.readPoint();
58 SkPoint c2 = buffer.readPoint();
59 SkScalar r1 = buffer.readScalar();
60 SkScalar r2 = buffer.readScalar();
61
62 if (buffer.readBool()) { // flipped
63 SkTSwap(c1, c2);
64 SkTSwap(r1, r2);
65
66 SkColor4f* colors = desc.mutableColors();
67 SkScalar* pos = desc.mutablePos();
68 const int last = desc.fCount - 1;
69 const int half = desc.fCount >> 1;
70 for (int i = 0; i < half; ++i) {
71 SkTSwap(colors[i], colors[last - i]);
72 if (pos) {
73 SkScalar tmp = pos[i];
74 pos[i] = SK_Scalar1 - pos[last - i];
75 pos[last - i] = SK_Scalar1 - tmp;
76 }
77 }
78 if (pos) {
79 if (desc.fCount & 1) {
80 pos[half] = SK_Scalar1 - pos[half];
81 }
82 }
83 }
84
85 return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors,
86 std::move(desc.fColorSpace), desc.fPos,
87 desc.fCount, desc.fTileMode, desc.fGradFlags,
88 desc.fLocalMatrix);
89 }
90
flatten(SkWriteBuffer & buffer) const91 void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
92 this->INHERITED::flatten(buffer);
93 buffer.writePoint(fCenter1);
94 buffer.writePoint(fCenter2);
95 buffer.writeScalar(fRadius1);
96 buffer.writeScalar(fRadius2);
97 buffer.writeBool(fFlippedGrad);
98 }
99
100 #if SK_SUPPORT_GPU
101
102 #include "SkGr.h"
103 #include "SkTwoPointConicalGradient_gpu.h"
104
asFragmentProcessor(const AsFPArgs & args) const105 sk_sp<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor(
106 const AsFPArgs& args) const {
107 SkASSERT(args.fContext);
108 SkASSERT(fPtsToUnit.isIdentity());
109 sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
110 args.fDstColorSpace);
111 sk_sp<GrFragmentProcessor> inner(Gr2PtConicalGradientEffect::Make(
112 GrGradientEffect::CreateArgs(args.fContext, this, args.fLocalMatrix, fTileMode,
113 std::move(colorSpaceXform), SkToBool(args.fDstColorSpace))));
114 if (!inner) {
115 return nullptr;
116 }
117 return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner));
118 }
119
120 #endif
121
onMakeColorSpace(SkColorSpaceXformer * xformer) const122 sk_sp<SkShader> SkTwoPointConicalGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
123 SkSTArray<8, SkColor> origColorsStorage(fColorCount);
124 SkSTArray<8, SkScalar> origPosStorage(fColorCount);
125 SkSTArray<8, SkColor> xformedColorsStorage(fColorCount);
126 SkColor* origColors = origColorsStorage.begin();
127 SkScalar* origPos = fOrigPos ? origPosStorage.begin() : nullptr;
128 SkColor* xformedColors = xformedColorsStorage.begin();
129
130 // Flip if necessary
131 SkPoint center1 = fFlippedGrad ? fCenter2 : fCenter1;
132 SkPoint center2 = fFlippedGrad ? fCenter1 : fCenter2;
133 SkScalar radius1 = fFlippedGrad ? fRadius2 : fRadius1;
134 SkScalar radius2 = fFlippedGrad ? fRadius1 : fRadius2;
135 for (int i = 0; i < fColorCount; i++) {
136 origColors[i] = fFlippedGrad ? fOrigColors[fColorCount - i - 1] : fOrigColors[i];
137 if (origPos) {
138 origPos[i] = fFlippedGrad ? 1.0f - fOrigPos[fColorCount - i - 1] : fOrigPos[i];
139 }
140 }
141
142 xformer->apply(xformedColors, origColors, fColorCount);
143 return SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, xformedColors,
144 origPos, fColorCount, fTileMode, fGradFlags,
145 &this->getLocalMatrix());
146 }
147
148
149 #ifndef SK_IGNORE_TO_STRING
toString(SkString * str) const150 void SkTwoPointConicalGradient::toString(SkString* str) const {
151 str->append("SkTwoPointConicalGradient: (");
152
153 str->append("center1: (");
154 str->appendScalar(fCenter1.fX);
155 str->append(", ");
156 str->appendScalar(fCenter1.fY);
157 str->append(") radius1: ");
158 str->appendScalar(fRadius1);
159 str->append(" ");
160
161 str->append("center2: (");
162 str->appendScalar(fCenter2.fX);
163 str->append(", ");
164 str->appendScalar(fCenter2.fY);
165 str->append(") radius2: ");
166 str->appendScalar(fRadius2);
167 str->append(" ");
168
169 this->INHERITED::toString(str);
170
171 str->append(")");
172 }
173 #endif
174
adjustMatrixAndAppendStages(SkArenaAlloc * alloc,SkMatrix * matrix,SkRasterPipeline * p,SkRasterPipeline * postPipeline) const175 bool SkTwoPointConicalGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc,
176 SkMatrix* matrix,
177 SkRasterPipeline* p,
178 SkRasterPipeline* postPipeline) const {
179 const auto dCenter = (fCenter1 - fCenter2).length();
180 const auto dRadius = fRadius2 - fRadius1;
181 SkASSERT(dRadius >= 0);
182
183 // When the two circles are concentric, we can pretend we're radial (with a tiny *twist).
184 if (SkScalarNearlyZero(dCenter)) {
185 matrix->postTranslate(-fCenter1.fX, -fCenter1.fY);
186 matrix->postScale(1 / fRadius2, 1 / fRadius2);
187 p->append(SkRasterPipeline::xy_to_radius);
188
189 // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
190 auto scale = fRadius2 / dRadius;
191 auto bias = -fRadius1 / dRadius;
192
193 p->append_matrix(alloc, SkMatrix::Concat(SkMatrix::MakeTrans(bias, 0),
194 SkMatrix::MakeScale(scale, 1)));
195
196 return true;
197 }
198
199 // To simplify the stage math, we transform the universe (translate/scale/rotate)
200 // such that fCenter1 -> (0, 0) and fCenter2 -> (1, 0).
201 SkMatrix map_to_unit_vector;
202 const SkPoint centers[2] = { fCenter1, fCenter2 };
203 const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
204 if (!map_to_unit_vector.setPolyToPoly(centers, unitvec, 2)) {
205 return false;
206 }
207 matrix->postConcat(map_to_unit_vector);
208
209 // Since we've squashed the centers into a unit vector, we must also scale
210 // all the coefficient variables by (1 / dCenter).
211 const auto coeffA = 1 - dRadius * dRadius / (dCenter * dCenter);
212 auto* ctx = alloc->make<SkJumper_2PtConicalCtx>();
213 ctx->fCoeffA = coeffA;
214 ctx->fInvCoeffA = 1 / coeffA;
215 ctx->fR0 = fRadius1 / dCenter;
216 ctx->fDR = dRadius / dCenter;
217
218 // Is the solver guaranteed to not produce degenerates?
219 bool isWellBehaved = true;
220
221 if (SkScalarNearlyZero(coeffA)) {
222 // The focal point is on the edge of the end circle.
223 p->append(SkRasterPipeline::xy_to_2pt_conical_linear, ctx);
224 isWellBehaved = false;
225 } else {
226 if (dCenter + fRadius1 > fRadius2) {
227 // The focal point is outside the end circle.
228
229 // We want the larger root, per spec:
230 // "For all values of ω where r(ω) > 0, starting with the value of ω nearest
231 // to positive infinity and ending with the value of ω nearest to negative
232 // infinity, draw the circumference of the circle with radius r(ω) at position
233 // (x(ω), y(ω)), with the color at ω, but only painting on the parts of the
234 // bitmap that have not yet been painted on by earlier circles in this step for
235 // this rendering of the gradient."
236 // (https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createradialgradient)
237 p->append(fFlippedGrad ? SkRasterPipeline::xy_to_2pt_conical_quadratic_min
238 : SkRasterPipeline::xy_to_2pt_conical_quadratic_max, ctx);
239 isWellBehaved = false;
240 } else {
241 // The focal point is inside (well-behaved case).
242 p->append(SkRasterPipeline::xy_to_2pt_conical_quadratic_max, ctx);
243 }
244 }
245
246 if (!isWellBehaved) {
247 p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx);
248 postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
249 }
250
251 return true;
252 }
253