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 "SkReadBuffer.h"
12#include "SkWriteBuffer.h"
13#include "../../jumper/SkJumper.h"
14
15// 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    fIsSwapped = false;
19    fFocalX = r0 / (r0 - r1);
20    if (SkScalarNearlyZero(fFocalX - 1)) {
21        // swap r0, r1
22        matrix.postTranslate(-1, 0);
23        matrix.postScale(-1, 1);
24        std::swap(r0, r1);
25        fFocalX = 0; // because r0 is now 0
26        fIsSwapped = true;
27    }
28
29    // Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
30    const SkPoint from[2]   = { {fFocalX, 0}, {1, 0} };
31    const SkPoint to[2]     = { {0, 0}, {1, 0} };
32    SkMatrix focalMatrix;
33    if (!focalMatrix.setPolyToPoly(from, to, 2)) {
34        SkDEBUGFAILF("Mapping focal point failed unexpectedly for focalX = %f.\n", fFocalX);
35        // We won't be able to draw the gradient; at least make sure that we initialize the
36        // memory to prevent security issues.
37        focalMatrix = SkMatrix::MakeScale(1, 1);
38    }
39    matrix.postConcat(focalMatrix);
40    fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f)
41
42    // The following transformations are just to accelerate the shader computation by saving
43    // some arithmatic operations.
44    if (this->isFocalOnCircle()) {
45        matrix.postScale(0.5, 0.5);
46    } else {
47        matrix.postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
48    }
49    matrix.postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f|
50}
51
52sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0,
53                                                  const SkPoint& c1, SkScalar r1,
54                                                  const Descriptor& desc) {
55    SkMatrix gradientMatrix;
56    Type     gradientType;
57
58    if (SkScalarNearlyZero((c0 - c1).length())) {
59        // Concentric case: we can pretend we're radial (with a tiny twist).
60        const SkScalar scale = 1.0f / SkTMax(r0, r1);
61        gradientMatrix = SkMatrix::MakeTrans(-c1.x(), -c1.y());
62        gradientMatrix.postScale(scale, scale);
63
64        gradientType = Type::kRadial;
65    } else {
66        const SkPoint centers[2] = { c0    , c1     };
67        const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
68
69        if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) {
70            // Degenerate case.
71            return nullptr;
72        }
73
74        gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
75    }
76
77    FocalData focalData;
78    if (gradientType == Type::kFocal) {
79        const auto dCenter = (c0 - c1).length();
80        focalData.set(r0 / dCenter, r1 / dCenter, gradientMatrix); // this may change gradientMatrix
81    }
82    return sk_sp<SkShader>(new SkTwoPointConicalGradient(c0, r0, c1, r1, desc,
83                                                         gradientType, gradientMatrix, focalData));
84}
85
86SkTwoPointConicalGradient::SkTwoPointConicalGradient(
87        const SkPoint& start, SkScalar startRadius,
88        const SkPoint& end, SkScalar endRadius,
89        const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data)
90    : SkGradientShaderBase(desc, gradientMatrix)
91    , fCenter1(start)
92    , fCenter2(end)
93    , fRadius1(startRadius)
94    , fRadius2(endRadius)
95    , fType(type)
96{
97    // this is degenerate, and should be caught by our caller
98    SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
99    if (type == Type::kFocal) {
100        fFocalData = data;
101    }
102}
103
104bool SkTwoPointConicalGradient::isOpaque() const {
105    // Because areas outside the cone are left untouched, we cannot treat the
106    // shader as opaque even if the gradient itself is opaque.
107    // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
108    return false;
109}
110
111// Returns the original non-sorted version of the gradient
112SkShader::GradientType SkTwoPointConicalGradient::asAGradient(GradientInfo* info) const {
113    if (info) {
114        commonAsAGradient(info);
115        info->fPoint[0] = fCenter1;
116        info->fPoint[1] = fCenter2;
117        info->fRadius[0] = fRadius1;
118        info->fRadius[1] = fRadius2;
119    }
120    return kConical_GradientType;
121}
122
123sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) {
124    DescriptorScope desc;
125    if (!desc.unflatten(buffer)) {
126        return nullptr;
127    }
128    SkPoint c1 = buffer.readPoint();
129    SkPoint c2 = buffer.readPoint();
130    SkScalar r1 = buffer.readScalar();
131    SkScalar r2 = buffer.readScalar();
132
133    if (buffer.isVersionLT(SkReadBuffer::k2PtConicalNoFlip_Version) && buffer.readBool()) {
134        // legacy flipped gradient
135        SkTSwap(c1, c2);
136        SkTSwap(r1, r2);
137
138        SkColor4f* colors = desc.mutableColors();
139        SkScalar* pos = desc.mutablePos();
140        const int last = desc.fCount - 1;
141        const int half = desc.fCount >> 1;
142        for (int i = 0; i < half; ++i) {
143            SkTSwap(colors[i], colors[last - i]);
144            if (pos) {
145                SkScalar tmp = pos[i];
146                pos[i] = SK_Scalar1 - pos[last - i];
147                pos[last - i] = SK_Scalar1 - tmp;
148            }
149        }
150        if (pos) {
151            if (desc.fCount & 1) {
152                pos[half] = SK_Scalar1 - pos[half];
153            }
154        }
155    }
156
157    return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors,
158                                                 std::move(desc.fColorSpace), desc.fPos,
159                                                 desc.fCount, desc.fTileMode, desc.fGradFlags,
160                                                 desc.fLocalMatrix);
161}
162
163void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
164    this->INHERITED::flatten(buffer);
165    buffer.writePoint(fCenter1);
166    buffer.writePoint(fCenter2);
167    buffer.writeScalar(fRadius1);
168    buffer.writeScalar(fRadius2);
169}
170
171#if SK_SUPPORT_GPU
172
173#include "SkGr.h"
174#include "SkTwoPointConicalGradient_gpu.h"
175
176std::unique_ptr<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor(
177        const GrFPArgs& args) const {
178    SkASSERT(args.fContext);
179    return Gr2PtConicalGradientEffect::Make(
180            GrGradientEffect::CreateArgs(args.fContext, this, args.fLocalMatrix, fTileMode,
181                                         args.fDstColorSpaceInfo->colorSpace()));
182}
183
184#endif
185
186sk_sp<SkShader> SkTwoPointConicalGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
187    const AutoXformColors xformedColors(*this, xformer);
188    return SkGradientShader::MakeTwoPointConical(fCenter1, fRadius1, fCenter2, fRadius2,
189                                                 xformedColors.fColors.get(), fOrigPos, fColorCount,
190                                                 fTileMode, fGradFlags, &this->getLocalMatrix());
191}
192
193
194#ifndef SK_IGNORE_TO_STRING
195void SkTwoPointConicalGradient::toString(SkString* str) const {
196    str->append("SkTwoPointConicalGradient: (");
197
198    str->append("center1: (");
199    str->appendScalar(fCenter1.fX);
200    str->append(", ");
201    str->appendScalar(fCenter1.fY);
202    str->append(") radius1: ");
203    str->appendScalar(fRadius1);
204    str->append(" ");
205
206    str->append("center2: (");
207    str->appendScalar(fCenter2.fX);
208    str->append(", ");
209    str->appendScalar(fCenter2.fY);
210    str->append(") radius2: ");
211    str->appendScalar(fRadius2);
212    str->append(" ");
213
214    this->INHERITED::toString(str);
215
216    str->append(")");
217}
218#endif
219
220void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
221                                                     SkRasterPipeline* postPipeline) const {
222    const auto dRadius = fRadius2 - fRadius1;
223
224    if (fType == Type::kRadial) {
225        p->append(SkRasterPipeline::xy_to_radius);
226
227        // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
228        auto scale =  SkTMax(fRadius1, fRadius2) / dRadius;
229        auto bias  = -fRadius1 / dRadius;
230
231        p->append_matrix(alloc, SkMatrix::Concat(SkMatrix::MakeTrans(bias, 0),
232                                                 SkMatrix::MakeScale(scale, 1)));
233        return;
234    }
235
236    if (fType == Type::kStrip) {
237        auto* ctx = alloc->make<SkJumper_2PtConicalCtx>();
238        SkScalar scaledR0 = fRadius1 / this->getCenterX1();
239        ctx->fP0 = scaledR0 * scaledR0;
240        p->append(SkRasterPipeline::xy_to_2pt_conical_strip, ctx);
241        p->append(SkRasterPipeline::mask_2pt_conical_nan, ctx);
242        postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
243        return;
244    }
245
246    auto* ctx = alloc->make<SkJumper_2PtConicalCtx>();
247    ctx->fP0 = 1/fFocalData.fR1;
248    ctx->fP1 = fFocalData.fFocalX;
249
250    if (fFocalData.isFocalOnCircle()) {
251        p->append(SkRasterPipeline::xy_to_2pt_conical_focal_on_circle);
252    } else if (fFocalData.isWellBehaved()) {
253        p->append(SkRasterPipeline::xy_to_2pt_conical_well_behaved, ctx);
254    } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
255        p->append(SkRasterPipeline::xy_to_2pt_conical_smaller, ctx);
256    } else {
257        p->append(SkRasterPipeline::xy_to_2pt_conical_greater, ctx);
258    }
259
260    if (!fFocalData.isWellBehaved()) {
261        p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx);
262    }
263    if (1 - fFocalData.fFocalX < 0) {
264        p->append(SkRasterPipeline::negate_x);
265    }
266    if (!fFocalData.isNativelyFocal()) {
267        p->append(SkRasterPipeline::alter_2pt_conical_compensate_focal, ctx);
268    }
269    if (fFocalData.isSwapped()) {
270        p->append(SkRasterPipeline::alter_2pt_conical_unswap);
271    }
272    if (!fFocalData.isWellBehaved()) {
273        postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
274    }
275}
276