blob: 205a18f05b3611e365e0f9f5e4f7bea9c99b95ea [file] [log] [blame]
rileya@google.com589708b2012-07-26 20:04:23 +00001/*
2 * Copyright 2006 The Android Open Source Project
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
Herb Derby4de13042017-05-15 10:49:39 -04008#include <algorithm>
Mike Kleinc0bd9f92019-04-23 12:05:21 -05009#include "include/core/SkMallocPixelRef.h"
10#include "include/private/SkFloatBits.h"
11#include "include/private/SkHalf.h"
Mike Klein85754d52020-01-22 10:04:11 -060012#include "include/private/SkVx.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050013#include "src/core/SkColorSpacePriv.h"
14#include "src/core/SkConvertPixels.h"
Brian Osman9aaec362020-05-08 14:54:37 -040015#include "src/core/SkMatrixProvider.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050016#include "src/core/SkReadBuffer.h"
Mike Klein85754d52020-01-22 10:04:11 -060017#include "src/core/SkVM.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050018#include "src/core/SkWriteBuffer.h"
19#include "src/shaders/gradients/Sk4fLinearGradient.h"
20#include "src/shaders/gradients/SkGradientShaderPriv.h"
21#include "src/shaders/gradients/SkLinearGradient.h"
22#include "src/shaders/gradients/SkRadialGradient.h"
23#include "src/shaders/gradients/SkSweepGradient.h"
24#include "src/shaders/gradients/SkTwoPointConicalGradient.h"
rileya@google.com589708b2012-07-26 20:04:23 +000025
brianosmane25d71c2016-09-28 11:27:28 -070026enum GradientSerializationFlags {
27 // Bits 29:31 used for various boolean flags
28 kHasPosition_GSF = 0x80000000,
29 kHasLocalMatrix_GSF = 0x40000000,
30 kHasColorSpace_GSF = 0x20000000,
31
32 // Bits 12:28 unused
33
34 // Bits 8:11 for fTileMode
35 kTileModeShift_GSF = 8,
36 kTileModeMask_GSF = 0xF,
37
38 // Bits 0:7 for fGradFlags (note that kForce4fContext_PrivateFlag is 0x80)
39 kGradFlagsShift_GSF = 0,
40 kGradFlagsMask_GSF = 0xFF,
41};
42
reed9fa60da2014-08-21 07:59:51 -070043void SkGradientShaderBase::Descriptor::flatten(SkWriteBuffer& buffer) const {
brianosmane25d71c2016-09-28 11:27:28 -070044 uint32_t flags = 0;
reed9fa60da2014-08-21 07:59:51 -070045 if (fPos) {
brianosmane25d71c2016-09-28 11:27:28 -070046 flags |= kHasPosition_GSF;
reed9fa60da2014-08-21 07:59:51 -070047 }
reed9fa60da2014-08-21 07:59:51 -070048 if (fLocalMatrix) {
brianosmane25d71c2016-09-28 11:27:28 -070049 flags |= kHasLocalMatrix_GSF;
50 }
51 sk_sp<SkData> colorSpaceData = fColorSpace ? fColorSpace->serialize() : nullptr;
52 if (colorSpaceData) {
53 flags |= kHasColorSpace_GSF;
54 }
55 SkASSERT(static_cast<uint32_t>(fTileMode) <= kTileModeMask_GSF);
Mike Reedfae8fce2019-04-03 10:27:45 -040056 flags |= ((unsigned)fTileMode << kTileModeShift_GSF);
brianosmane25d71c2016-09-28 11:27:28 -070057 SkASSERT(fGradFlags <= kGradFlagsMask_GSF);
58 flags |= (fGradFlags << kGradFlagsShift_GSF);
59
60 buffer.writeUInt(flags);
61
62 buffer.writeColor4fArray(fColors, fCount);
63 if (colorSpaceData) {
64 buffer.writeDataAsByteArray(colorSpaceData.get());
65 }
66 if (fPos) {
67 buffer.writeScalarArray(fPos, fCount);
68 }
69 if (fLocalMatrix) {
reed9fa60da2014-08-21 07:59:51 -070070 buffer.writeMatrix(*fLocalMatrix);
reed9fa60da2014-08-21 07:59:51 -070071 }
72}
73
Florin Malitaf77db112018-05-10 09:52:27 -040074template <int N, typename T, bool MEM_MOVE>
75static bool validate_array(SkReadBuffer& buffer, size_t count, SkSTArray<N, T, MEM_MOVE>* array) {
Kevin Lubickdaebae92018-05-17 11:29:10 -040076 if (!buffer.validateCanReadN<T>(count)) {
Florin Malitaf77db112018-05-10 09:52:27 -040077 return false;
78 }
79
80 array->resize_back(count);
81 return true;
82}
83
reed9fa60da2014-08-21 07:59:51 -070084bool SkGradientShaderBase::DescriptorScope::unflatten(SkReadBuffer& buffer) {
Mike Reed70bc94f2017-06-08 12:45:52 -040085 // New gradient format. Includes floating point color, color space, densely packed flags
86 uint32_t flags = buffer.readUInt();
reed9fa60da2014-08-21 07:59:51 -070087
Mike Reedfae8fce2019-04-03 10:27:45 -040088 fTileMode = (SkTileMode)((flags >> kTileModeShift_GSF) & kTileModeMask_GSF);
Mike Reed70bc94f2017-06-08 12:45:52 -040089 fGradFlags = (flags >> kGradFlagsShift_GSF) & kGradFlagsMask_GSF;
reed9fa60da2014-08-21 07:59:51 -070090
Mike Reed70bc94f2017-06-08 12:45:52 -040091 fCount = buffer.getArrayCount();
Florin Malitaf77db112018-05-10 09:52:27 -040092
93 if (!(validate_array(buffer, fCount, &fColorStorage) &&
94 buffer.readColor4fArray(fColorStorage.begin(), fCount))) {
Mike Reed70bc94f2017-06-08 12:45:52 -040095 return false;
96 }
Florin Malitaf77db112018-05-10 09:52:27 -040097 fColors = fColorStorage.begin();
98
Mike Reed70bc94f2017-06-08 12:45:52 -040099 if (SkToBool(flags & kHasColorSpace_GSF)) {
100 sk_sp<SkData> data = buffer.readByteArrayAsData();
Florin Malitac2ea3272018-05-10 09:41:38 -0400101 fColorSpace = data ? SkColorSpace::Deserialize(data->data(), data->size()) : nullptr;
Mike Reed70bc94f2017-06-08 12:45:52 -0400102 } else {
brianosmane25d71c2016-09-28 11:27:28 -0700103 fColorSpace = nullptr;
Mike Reed70bc94f2017-06-08 12:45:52 -0400104 }
105 if (SkToBool(flags & kHasPosition_GSF)) {
Florin Malitaf77db112018-05-10 09:52:27 -0400106 if (!(validate_array(buffer, fCount, &fPosStorage) &&
107 buffer.readScalarArray(fPosStorage.begin(), fCount))) {
Mike Reed70bc94f2017-06-08 12:45:52 -0400108 return false;
brianosmane25d71c2016-09-28 11:27:28 -0700109 }
Florin Malitaf77db112018-05-10 09:52:27 -0400110 fPos = fPosStorage.begin();
reed9fa60da2014-08-21 07:59:51 -0700111 } else {
Mike Reed70bc94f2017-06-08 12:45:52 -0400112 fPos = nullptr;
113 }
114 if (SkToBool(flags & kHasLocalMatrix_GSF)) {
115 fLocalMatrix = &fLocalMatrixStorage;
116 buffer.readMatrix(&fLocalMatrixStorage);
117 } else {
118 fLocalMatrix = nullptr;
reed9fa60da2014-08-21 07:59:51 -0700119 }
120 return buffer.isValid();
121}
122
123////////////////////////////////////////////////////////////////////////////////////////////
124
mtkleincc695fe2014-12-10 10:29:19 -0800125SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit)
reedaddf2ed2014-08-11 08:28:24 -0700126 : INHERITED(desc.fLocalMatrix)
mtkleincc695fe2014-12-10 10:29:19 -0800127 , fPtsToUnit(ptsToUnit)
Brian Osman6667fb12018-07-03 16:44:02 -0400128 , fColorSpace(desc.fColorSpace ? desc.fColorSpace : SkColorSpace::MakeSRGB())
Florin Malita39d71de2017-10-31 11:33:49 -0400129 , fColorsAreOpaque(true)
commit-bot@chromium.org9c9005a2014-04-28 14:55:39 +0000130{
mtkleincc695fe2014-12-10 10:29:19 -0800131 fPtsToUnit.getType(); // Precache so reads are threadsafe.
reed@google.com437d6eb2013-05-23 19:03:05 +0000132 SkASSERT(desc.fCount > 1);
rileya@google.com589708b2012-07-26 20:04:23 +0000133
fmalita6d7e4e82016-09-20 06:55:16 -0700134 fGradFlags = static_cast<uint8_t>(desc.fGradFlags);
rileya@google.com589708b2012-07-26 20:04:23 +0000135
Mike Reedfae8fce2019-04-03 10:27:45 -0400136 SkASSERT((unsigned)desc.fTileMode < kSkTileModeCount);
reed@google.com437d6eb2013-05-23 19:03:05 +0000137 fTileMode = desc.fTileMode;
rileya@google.com589708b2012-07-26 20:04:23 +0000138
rileya@google.com589708b2012-07-26 20:04:23 +0000139 /* Note: we let the caller skip the first and/or last position.
140 i.e. pos[0] = 0.3, pos[1] = 0.7
141 In these cases, we insert dummy entries to ensure that the final data
142 will be bracketed by [0, 1].
143 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
144
145 Thus colorCount (the caller's value, and fColorCount (our value) may
146 differ by up to 2. In the above example:
147 colorCount = 2
148 fColorCount = 4
149 */
reed@google.com437d6eb2013-05-23 19:03:05 +0000150 fColorCount = desc.fCount;
rileya@google.com589708b2012-07-26 20:04:23 +0000151 // check if we need to add in dummy start and/or end position/colors
152 bool dummyFirst = false;
153 bool dummyLast = false;
reed@google.com437d6eb2013-05-23 19:03:05 +0000154 if (desc.fPos) {
155 dummyFirst = desc.fPos[0] != 0;
156 dummyLast = desc.fPos[desc.fCount - 1] != SK_Scalar1;
rileya@google.com589708b2012-07-26 20:04:23 +0000157 fColorCount += dummyFirst + dummyLast;
158 }
159
Mike Reed62ce2ca2018-02-19 14:20:15 -0500160 size_t storageSize = fColorCount * (sizeof(SkColor4f) + (desc.fPos ? sizeof(SkScalar) : 0));
Florin Malita89ab2402017-11-01 10:14:57 -0400161 fOrigColors4f = reinterpret_cast<SkColor4f*>(fStorage.reset(storageSize));
Mike Reed62ce2ca2018-02-19 14:20:15 -0500162 fOrigPos = desc.fPos ? reinterpret_cast<SkScalar*>(fOrigColors4f + fColorCount)
163 : nullptr;
rileya@google.com589708b2012-07-26 20:04:23 +0000164
brianosmane25d71c2016-09-28 11:27:28 -0700165 // Now copy over the colors, adding the dummies as needed
166 SkColor4f* origColors = fOrigColors4f;
167 if (dummyFirst) {
168 *origColors++ = desc.fColors[0];
169 }
Florin Malita39d71de2017-10-31 11:33:49 -0400170 for (int i = 0; i < desc.fCount; ++i) {
Mike Reed62ce2ca2018-02-19 14:20:15 -0500171 origColors[i] = desc.fColors[i];
Florin Malita39d71de2017-10-31 11:33:49 -0400172 fColorsAreOpaque = fColorsAreOpaque && (desc.fColors[i].fA == 1);
173 }
brianosmane25d71c2016-09-28 11:27:28 -0700174 if (dummyLast) {
Mike Reed62ce2ca2018-02-19 14:20:15 -0500175 origColors += desc.fCount;
176 *origColors = desc.fColors[desc.fCount - 1];
brianosmane25d71c2016-09-28 11:27:28 -0700177 }
brianosmanb9c51372016-09-15 11:09:45 -0700178
Florin Malita89ab2402017-11-01 10:14:57 -0400179 if (desc.fPos) {
Florin Malita64bb78e2017-11-03 12:54:07 -0400180 SkScalar prev = 0;
Mike Reed62ce2ca2018-02-19 14:20:15 -0500181 SkScalar* origPosPtr = fOrigPos;
Florin Malita64bb78e2017-11-03 12:54:07 -0400182 *origPosPtr++ = prev; // force the first pos to 0
reed9fa60da2014-08-21 07:59:51 -0700183
Florin Malita89ab2402017-11-01 10:14:57 -0400184 int startIndex = dummyFirst ? 0 : 1;
185 int count = desc.fCount + dummyLast;
Florin Malita64bb78e2017-11-03 12:54:07 -0400186
187 bool uniformStops = true;
188 const SkScalar uniformStep = desc.fPos[startIndex] - prev;
Florin Malita89ab2402017-11-01 10:14:57 -0400189 for (int i = startIndex; i < count; i++) {
Florin Malita3e20d022017-11-03 12:11:38 -0400190 // Pin the last value to 1.0, and make sure pos is monotonic.
Brian Osmanaba642c2020-02-06 12:52:25 -0500191 auto curr = (i == desc.fCount) ? 1 : SkTPin(desc.fPos[i], prev, 1.0f);
Florin Malita64bb78e2017-11-03 12:54:07 -0400192 uniformStops &= SkScalarNearlyEqual(uniformStep, curr - prev);
193
194 *origPosPtr++ = prev = curr;
reed9fa60da2014-08-21 07:59:51 -0700195 }
Florin Malita64bb78e2017-11-03 12:54:07 -0400196
Florin Malita64bb78e2017-11-03 12:54:07 -0400197 // If the stops are uniform, treat them as implicit.
Mike Reed62ce2ca2018-02-19 14:20:15 -0500198 if (uniformStops) {
Florin Malita64bb78e2017-11-03 12:54:07 -0400199 fOrigPos = nullptr;
200 }
rileya@google.com589708b2012-07-26 20:04:23 +0000201 }
rileya@google.com589708b2012-07-26 20:04:23 +0000202}
203
Florin Malita89ab2402017-11-01 10:14:57 -0400204SkGradientShaderBase::~SkGradientShaderBase() {}
rileya@google.com589708b2012-07-26 20:04:23 +0000205
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000206void SkGradientShaderBase::flatten(SkWriteBuffer& buffer) const {
reed9fa60da2014-08-21 07:59:51 -0700207 Descriptor desc;
brianosmane25d71c2016-09-28 11:27:28 -0700208 desc.fColors = fOrigColors4f;
brianosmanb9c51372016-09-15 11:09:45 -0700209 desc.fColorSpace = fColorSpace;
reed9fa60da2014-08-21 07:59:51 -0700210 desc.fPos = fOrigPos;
211 desc.fCount = fColorCount;
212 desc.fTileMode = fTileMode;
213 desc.fGradFlags = fGradFlags;
214
215 const SkMatrix& m = this->getLocalMatrix();
halcanary96fcdcc2015-08-27 07:41:13 -0700216 desc.fLocalMatrix = m.isIdentity() ? nullptr : &m;
reed9fa60da2014-08-21 07:59:51 -0700217 desc.flatten(buffer);
rileya@google.com589708b2012-07-26 20:04:23 +0000218}
219
Mike Kleinb11ab572018-10-24 06:42:14 -0400220static void add_stop_color(SkRasterPipeline_GradientCtx* ctx, size_t stop, SkPMColor4f Fs, SkPMColor4f Bs) {
Brian Osman781e3502018-10-03 15:42:47 -0400221 (ctx->fs[0])[stop] = Fs.fR;
222 (ctx->fs[1])[stop] = Fs.fG;
223 (ctx->fs[2])[stop] = Fs.fB;
224 (ctx->fs[3])[stop] = Fs.fA;
Mike Klein85754d52020-01-22 10:04:11 -0600225
Brian Osman781e3502018-10-03 15:42:47 -0400226 (ctx->bs[0])[stop] = Bs.fR;
227 (ctx->bs[1])[stop] = Bs.fG;
228 (ctx->bs[2])[stop] = Bs.fB;
229 (ctx->bs[3])[stop] = Bs.fA;
Mike Kleinf945cbb2017-05-17 09:30:58 -0400230}
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400231
Mike Kleinb11ab572018-10-24 06:42:14 -0400232static void add_const_color(SkRasterPipeline_GradientCtx* ctx, size_t stop, SkPMColor4f color) {
Brian Osman781e3502018-10-03 15:42:47 -0400233 add_stop_color(ctx, stop, { 0, 0, 0, 0 }, color);
Mike Kleinf945cbb2017-05-17 09:30:58 -0400234}
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400235
236// Calculate a factor F and a bias B so that color = F*t + B when t is in range of
237// the stop. Assume that the distance between stops is 1/gapCount.
238static void init_stop_evenly(
Mike Kleinb11ab572018-10-24 06:42:14 -0400239 SkRasterPipeline_GradientCtx* ctx, float gapCount, size_t stop, SkPMColor4f c_l, SkPMColor4f c_r) {
Mike Klein68768172017-05-17 09:54:36 -0400240 // Clankium's GCC 4.9 targeting ARMv7 is barfing when we use Sk4f math here, so go scalar...
Brian Osman781e3502018-10-03 15:42:47 -0400241 SkPMColor4f Fs = {
242 (c_r.fR - c_l.fR) * gapCount,
243 (c_r.fG - c_l.fG) * gapCount,
244 (c_r.fB - c_l.fB) * gapCount,
245 (c_r.fA - c_l.fA) * gapCount,
246 };
247 SkPMColor4f Bs = {
248 c_l.fR - Fs.fR*(stop/gapCount),
249 c_l.fG - Fs.fG*(stop/gapCount),
250 c_l.fB - Fs.fB*(stop/gapCount),
251 c_l.fA - Fs.fA*(stop/gapCount),
252 };
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400253 add_stop_color(ctx, stop, Fs, Bs);
Mike Kleinf945cbb2017-05-17 09:30:58 -0400254}
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400255
256// For each stop we calculate a bias B and a scale factor F, such that
257// for any t between stops n and n+1, the color we want is B[n] + F[n]*t.
258static void init_stop_pos(
Mike Kleinb11ab572018-10-24 06:42:14 -0400259 SkRasterPipeline_GradientCtx* ctx, size_t stop, float t_l, float t_r, SkPMColor4f c_l, SkPMColor4f c_r) {
Mike Klein68768172017-05-17 09:54:36 -0400260 // See note about Clankium's old compiler in init_stop_evenly().
Brian Osman781e3502018-10-03 15:42:47 -0400261 SkPMColor4f Fs = {
262 (c_r.fR - c_l.fR) / (t_r - t_l),
263 (c_r.fG - c_l.fG) / (t_r - t_l),
264 (c_r.fB - c_l.fB) / (t_r - t_l),
265 (c_r.fA - c_l.fA) / (t_r - t_l),
266 };
267 SkPMColor4f Bs = {
268 c_l.fR - Fs.fR*t_l,
269 c_l.fG - Fs.fG*t_l,
270 c_l.fB - Fs.fB*t_l,
271 c_l.fA - Fs.fA*t_l,
272 };
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400273 ctx->ts[stop] = t_l;
274 add_stop_color(ctx, stop, Fs, Bs);
Mike Kleinf945cbb2017-05-17 09:30:58 -0400275}
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400276
Mike Reed1386b2d2019-03-13 21:15:05 -0400277bool SkGradientShaderBase::onAppendStages(const SkStageRec& rec) const {
Mike Reed1d8c42e2017-08-29 14:58:19 -0400278 SkRasterPipeline* p = rec.fPipeline;
279 SkArenaAlloc* alloc = rec.fAlloc;
Mike Kleinb11ab572018-10-24 06:42:14 -0400280 SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr;
Mike Reed1d8c42e2017-08-29 14:58:19 -0400281
Mike Kleina3771842017-05-04 19:38:48 -0400282 SkMatrix matrix;
Brian Osman9aaec362020-05-08 14:54:37 -0400283 if (!this->computeTotalInverse(rec.fMatrixProvider.localToDevice(), rec.fLocalM, &matrix)) {
Mike Kleina3771842017-05-04 19:38:48 -0400284 return false;
285 }
Florin Malita50b20842017-07-29 19:08:28 -0400286 matrix.postConcat(fPtsToUnit);
Mike Kleina3771842017-05-04 19:38:48 -0400287
Florin Malita2e409002017-06-28 14:46:54 -0400288 SkRasterPipeline_<256> postPipeline;
Mike Kleina3771842017-05-04 19:38:48 -0400289
Mike Kleine8de0242018-03-10 12:37:11 -0500290 p->append(SkRasterPipeline::seed_shader);
Mike Reed6b59bf42017-07-03 21:26:44 -0400291 p->append_matrix(alloc, matrix);
Florin Malita50b20842017-07-29 19:08:28 -0400292 this->appendGradientStages(alloc, p, &postPipeline);
Mike Kleine7598532017-05-11 11:29:29 -0400293
Mike Reed62ce2ca2018-02-19 14:20:15 -0500294 switch(fTileMode) {
Mike Reedfae8fce2019-04-03 10:27:45 -0400295 case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_x_1); break;
296 case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_x_1); break;
297 case SkTileMode::kDecal:
Mike Kleinb11ab572018-10-24 06:42:14 -0400298 decal_ctx = alloc->make<SkRasterPipeline_DecalTileCtx>();
Mike Reed62ce2ca2018-02-19 14:20:15 -0500299 decal_ctx->limit_x = SkBits2Float(SkFloat2Bits(1.0f) + 1);
300 // reuse mask + limit_x stage, or create a custom decal_1 that just stores the mask
301 p->append(SkRasterPipeline::decal_x, decal_ctx);
302 // fall-through to clamp
Mike Reedfae8fce2019-04-03 10:27:45 -0400303 case SkTileMode::kClamp:
Mike Kleine7598532017-05-11 11:29:29 -0400304 if (!fOrigPos) {
305 // We clamp only when the stops are evenly spaced.
306 // If not, there may be hard stops, and clamping ruins hard stops at 0 and/or 1.
Mike Klein5c7960b2017-05-11 10:59:22 -0400307 // In that case, we must make sure we're using the general "gradient" stage,
Mike Kleine7598532017-05-11 11:29:29 -0400308 // which is the only stage that will correctly handle unclamped t.
Mike Klein9f85d682017-05-23 07:52:01 -0400309 p->append(SkRasterPipeline::clamp_x_1);
Mike Kleine7598532017-05-11 11:29:29 -0400310 }
Mike Reed62ce2ca2018-02-19 14:20:15 -0500311 break;
Mike Kleine7598532017-05-11 11:29:29 -0400312 }
Mike Kleina3771842017-05-04 19:38:48 -0400313
314 const bool premulGrad = fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag;
Brian Osman6667fb12018-07-03 16:44:02 -0400315
316 // Transform all of the colors to destination color space
317 SkColor4fXformer xformedColors(fOrigColors4f, fColorCount, fColorSpace.get(), rec.fDstCS);
318
319 auto prepareColor = [premulGrad, &xformedColors](int i) {
320 SkColor4f c = xformedColors.fColors[i];
Brian Osman781e3502018-10-03 15:42:47 -0400321 return premulGrad ? c.premul()
322 : SkPMColor4f{ c.fR, c.fG, c.fB, c.fA };
Mike Kleina3771842017-05-04 19:38:48 -0400323 };
324
325 // The two-stop case with stops at 0 and 1.
326 if (fColorCount == 2 && fOrigPos == nullptr) {
Brian Osman781e3502018-10-03 15:42:47 -0400327 const SkPMColor4f c_l = prepareColor(0),
328 c_r = prepareColor(1);
Mike Kleina3771842017-05-04 19:38:48 -0400329
330 // See F and B below.
Mike Kleinb11ab572018-10-24 06:42:14 -0400331 auto ctx = alloc->make<SkRasterPipeline_EvenlySpaced2StopGradientCtx>();
Brian Osman781e3502018-10-03 15:42:47 -0400332 (Sk4f::Load(c_r.vec()) - Sk4f::Load(c_l.vec())).store(ctx->f);
333 ( Sk4f::Load(c_l.vec())).store(ctx->b);
Mike Klein24de6482018-09-07 12:05:29 -0400334 ctx->interpolatedInPremul = premulGrad;
Mike Kleina3771842017-05-04 19:38:48 -0400335
Mike Klein24de6482018-09-07 12:05:29 -0400336 p->append(SkRasterPipeline::evenly_spaced_2_stop_gradient, ctx);
Mike Kleina3771842017-05-04 19:38:48 -0400337 } else {
Mike Kleinb11ab572018-10-24 06:42:14 -0400338 auto* ctx = alloc->make<SkRasterPipeline_GradientCtx>();
Mike Klein24de6482018-09-07 12:05:29 -0400339 ctx->interpolatedInPremul = premulGrad;
Herb Derby4de13042017-05-15 10:49:39 -0400340
341 // Note: In order to handle clamps in search, the search assumes a stop conceptully placed
342 // at -inf. Therefore, the max number of stops is fColorCount+1.
343 for (int i = 0; i < 4; i++) {
344 // Allocate at least at for the AVX2 gather from a YMM register.
345 ctx->fs[i] = alloc->makeArray<float>(std::max(fColorCount+1, 8));
346 ctx->bs[i] = alloc->makeArray<float>(std::max(fColorCount+1, 8));
347 }
348
Mike Kleina3771842017-05-04 19:38:48 -0400349 if (fOrigPos == nullptr) {
350 // Handle evenly distributed stops.
351
Herb Derby4de13042017-05-15 10:49:39 -0400352 size_t stopCount = fColorCount;
353 float gapCount = stopCount - 1;
Mike Kleina3771842017-05-04 19:38:48 -0400354
Brian Osman781e3502018-10-03 15:42:47 -0400355 SkPMColor4f c_l = prepareColor(0);
Herb Derby4de13042017-05-15 10:49:39 -0400356 for (size_t i = 0; i < stopCount - 1; i++) {
Brian Osman781e3502018-10-03 15:42:47 -0400357 SkPMColor4f c_r = prepareColor(i + 1);
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400358 init_stop_evenly(ctx, gapCount, i, c_l, c_r);
Mike Kleina3771842017-05-04 19:38:48 -0400359 c_l = c_r;
360 }
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400361 add_const_color(ctx, stopCount - 1, c_l);
Mike Kleina3771842017-05-04 19:38:48 -0400362
Herb Derby4de13042017-05-15 10:49:39 -0400363 ctx->stopCount = stopCount;
364 p->append(SkRasterPipeline::evenly_spaced_gradient, ctx);
Mike Kleina3771842017-05-04 19:38:48 -0400365 } else {
366 // Handle arbitrary stops.
367
Herb Derby4de13042017-05-15 10:49:39 -0400368 ctx->ts = alloc->makeArray<float>(fColorCount+1);
369
Mike Kleina3771842017-05-04 19:38:48 -0400370 // Remove the dummy stops inserted by SkGradientShaderBase::SkGradientShaderBase
371 // because they are naturally handled by the search method.
372 int firstStop;
373 int lastStop;
374 if (fColorCount > 2) {
375 firstStop = fOrigColors4f[0] != fOrigColors4f[1] ? 0 : 1;
376 lastStop = fOrigColors4f[fColorCount - 2] != fOrigColors4f[fColorCount - 1]
377 ? fColorCount - 1 : fColorCount - 2;
378 } else {
379 firstStop = 0;
380 lastStop = 1;
381 }
Mike Kleina3771842017-05-04 19:38:48 -0400382
Mike Kleina3771842017-05-04 19:38:48 -0400383 size_t stopCount = 0;
384 float t_l = fOrigPos[firstStop];
Brian Osman781e3502018-10-03 15:42:47 -0400385 SkPMColor4f c_l = prepareColor(firstStop);
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400386 add_const_color(ctx, stopCount++, c_l);
Mike Kleina3771842017-05-04 19:38:48 -0400387 // N.B. lastStop is the index of the last stop, not one after.
388 for (int i = firstStop; i < lastStop; i++) {
389 float t_r = fOrigPos[i + 1];
Brian Osman781e3502018-10-03 15:42:47 -0400390 SkPMColor4f c_r = prepareColor(i + 1);
Florin Malita3e20d022017-11-03 12:11:38 -0400391 SkASSERT(t_l <= t_r);
Mike Kleina3771842017-05-04 19:38:48 -0400392 if (t_l < t_r) {
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400393 init_stop_pos(ctx, stopCount, t_l, t_r, c_l, c_r);
Mike Kleina3771842017-05-04 19:38:48 -0400394 stopCount += 1;
395 }
396 t_l = t_r;
397 c_l = c_r;
398 }
399
Herb Derby4de13042017-05-15 10:49:39 -0400400 ctx->ts[stopCount] = t_l;
Herb Derbyeb99bfd2017-05-16 10:51:26 -0400401 add_const_color(ctx, stopCount++, c_l);
Mike Kleina3771842017-05-04 19:38:48 -0400402
Herb Derby4de13042017-05-15 10:49:39 -0400403 ctx->stopCount = stopCount;
404 p->append(SkRasterPipeline::gradient, ctx);
Mike Kleina3771842017-05-04 19:38:48 -0400405 }
Mike Kleina3771842017-05-04 19:38:48 -0400406 }
407
Mike Reed62ce2ca2018-02-19 14:20:15 -0500408 if (decal_ctx) {
409 p->append(SkRasterPipeline::check_decal_mask, decal_ctx);
410 }
411
Mike Kleina3771842017-05-04 19:38:48 -0400412 if (!premulGrad && !this->colorsAreOpaque()) {
Mike Kleine7598532017-05-11 11:29:29 -0400413 p->append(SkRasterPipeline::premul);
Mike Kleina3771842017-05-04 19:38:48 -0400414 }
415
Florin Malita2e409002017-06-28 14:46:54 -0400416 p->extend(postPipeline);
417
Mike Kleina3771842017-05-04 19:38:48 -0400418 return true;
419}
420
Mike Kleina434e0f2020-03-23 09:33:48 -0500421skvm::Color SkGradientShaderBase::onProgram(skvm::Builder* p,
422 skvm::F32 x, skvm::F32 y, skvm::Color /*paint*/,
Mike Reed6352f002020-03-14 23:30:10 -0400423 const SkMatrix& ctm, const SkMatrix* localM,
Mike Kleina434e0f2020-03-23 09:33:48 -0500424 SkFilterQuality quality, const SkColorInfo& dstInfo,
Mike Klein276a7852020-03-15 08:46:09 -0500425 skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
Mike Klein85754d52020-01-22 10:04:11 -0600426 SkMatrix inv;
427 if (!this->computeTotalInverse(ctm, localM, &inv)) {
Mike Reed6352f002020-03-14 23:30:10 -0400428 return {};
Mike Klein85754d52020-01-22 10:04:11 -0600429 }
430 inv.postConcat(fPtsToUnit);
431 inv.normalizePerspective();
432
Mike Klein85754d52020-01-22 10:04:11 -0600433 SkShaderBase::ApplyMatrix(p, inv, &x,&y,uniforms);
Mike Klein85754d52020-01-22 10:04:11 -0600434
Mike Kleince9e0602020-01-29 09:47:44 -0600435 skvm::I32 mask = p->splat(~0);
436 skvm::F32 t = this->transformT(p,uniforms, x,y, &mask);
Mike Kleincaf5ee42020-01-28 16:11:34 -0600437
438 // Perhaps unexpectedly, clamping is handled naturally by our search, so we
439 // don't explicitly clamp t to [0,1]. That clamp would break hard stops
440 // right at 0 or 1 boundaries in kClamp mode. (kRepeat and kMirror always
441 // produce values in [0,1].)
Mike Klein85754d52020-01-22 10:04:11 -0600442 switch(fTileMode) {
Mike Kleincaf5ee42020-01-28 16:11:34 -0600443 case SkTileMode::kClamp:
444 break;
445
446 case SkTileMode::kDecal:
Mike Reedb6e7ef12020-04-03 13:34:26 -0400447 mask &= (t == clamp01(t));
Mike Kleincaf5ee42020-01-28 16:11:34 -0600448 break;
449
450 case SkTileMode::kRepeat:
Mike Reedb6e7ef12020-04-03 13:34:26 -0400451 t = fract(t);
Mike Kleincaf5ee42020-01-28 16:11:34 -0600452 break;
453
Mike Klein85754d52020-01-22 10:04:11 -0600454 case SkTileMode::kMirror: {
455 // t = | (t-1) - 2*(floor( (t-1)*0.5 )) - 1 |
456 // {-A-} {--------B-------}
Mike Reedb6e7ef12020-04-03 13:34:26 -0400457 skvm::F32 A = t - 1.0f,
458 B = floor(A * 0.5f);
459 t = abs(A - (B + B) - 1.0f);
Mike Klein85754d52020-01-22 10:04:11 -0600460 } break;
461 }
462
463 // Transform our colors as we want them interpolated, in dst color space, possibly premul.
464 SkImageInfo common = SkImageInfo::Make(fColorCount,1, kRGBA_F32_SkColorType
465 , kUnpremul_SkAlphaType),
Mike Kleina434e0f2020-03-23 09:33:48 -0500466 src = common.makeColorSpace(fColorSpace),
467 dst = common.makeColorSpace(dstInfo.refColorSpace());
Mike Klein85754d52020-01-22 10:04:11 -0600468 if (fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag) {
469 dst = dst.makeAlphaType(kPremul_SkAlphaType);
470 }
471
472 std::vector<float> rgba(4*fColorCount); // TODO: SkSTArray?
473 SkConvertPixels(dst, rgba.data(), dst.minRowBytes(),
474 src, fOrigColors4f, src.minRowBytes());
475
476 // Transform our colors into a scale factor f and bias b such that for
477 // any t between stops i and i+1, the color we want is mad(t, f[i], b[i]).
478 using F4 = skvx::Vec<4,float>;
479 struct FB { F4 f,b; };
Mike Reed6352f002020-03-14 23:30:10 -0400480 skvm::Color color;
Mike Klein85754d52020-01-22 10:04:11 -0600481
Mike Reedb6e7ef12020-04-03 13:34:26 -0400482 auto uniformF = [&](float x) { return p->uniformF(uniforms->pushF(x)); };
483
Mike Klein85754d52020-01-22 10:04:11 -0600484 if (fColorCount == 2) {
485 // 2-stop gradients have colors at 0 and 1, and so must be evenly spaced.
486 SkASSERT(fOrigPos == nullptr);
487
488 // With 2 stops, we upload the single FB as uniforms and interpolate directly with t.
489 F4 lo = F4::Load(rgba.data() + 0),
490 hi = F4::Load(rgba.data() + 4);
491 F4 F = hi - lo,
492 B = lo;
493
Mike Reedb6e7ef12020-04-03 13:34:26 -0400494 auto T = clamp01(t);
Mike Reed6352f002020-03-14 23:30:10 -0400495 color = {
Mike Reedb6e7ef12020-04-03 13:34:26 -0400496 T * uniformF(F[0]) + uniformF(B[0]),
497 T * uniformF(F[1]) + uniformF(B[1]),
498 T * uniformF(F[2]) + uniformF(B[2]),
499 T * uniformF(F[3]) + uniformF(B[3]),
Mike Reed6352f002020-03-14 23:30:10 -0400500 };
Mike Klein85754d52020-01-22 10:04:11 -0600501 } else {
502 // To handle clamps in search we add a conceptual stop at t=-inf, so we
503 // may need up to fColorCount+1 FBs and fColorCount t stops between them:
504 //
505 // FBs: [color 0] [color 0->1] [color 1->2] [color 2->3] ...
506 // stops: (-inf) t0 t1 t2 ...
507 //
508 // Both these arrays could end up shorter if any hard stops share the same t.
509 FB* fb = alloc->makeArrayDefault<FB>(fColorCount+1);
510 std::vector<float> stops; // TODO: SkSTArray?
511 stops.reserve(fColorCount);
512
513 // Here's our conceptual stop at t=-inf covering all t<=0, clamping to our first color.
514 float t_lo = this->getPos(0);
515 F4 color_lo = F4::Load(rgba.data());
516 fb[0] = { 0.0f, color_lo };
517 // N.B. No stops[] entry for this implicit -inf.
518
519 // Now the non-edge cases, calculating scale and bias between adjacent normal stops.
520 for (int i = 1; i < fColorCount; i++) {
521 float t_hi = this->getPos(i);
522 F4 color_hi = F4::Load(rgba.data() + 4*i);
523
524 // If t_lo == t_hi, we're on a hard stop, and transition immediately to the next color.
525 SkASSERT(t_lo <= t_hi);
526 if (t_lo < t_hi) {
527 F4 f = (color_hi - color_lo) / (t_hi - t_lo),
528 b = color_lo - f*t_lo;
529 stops.push_back(t_lo);
530 fb[stops.size()] = {f,b};
531 }
532
533 t_lo = t_hi;
534 color_lo = color_hi;
535 }
536 // Anything >= our final t clamps to our final color.
537 stops.push_back(t_lo);
538 fb[stops.size()] = { 0.0f, color_lo };
539
540 // We'll gather FBs from that array we just created.
Mike Klein8b99b9e2020-03-31 12:28:41 -0500541 skvm::Uniform fbs = uniforms->pushPtr(fb);
Mike Klein85754d52020-01-22 10:04:11 -0600542
543 // Find the two stops we need to interpolate.
544 skvm::I32 ix;
545 if (fOrigPos == nullptr) {
546 // Evenly spaced stops... we can calculate ix directly.
547 // Of note: we need to clamp t and skip over that conceptual -inf stop we made up.
Mike Reedb6e7ef12020-04-03 13:34:26 -0400548 ix = trunc(clamp01(t) * uniformF(stops.size() - 1) + 1.0f);
Mike Klein85754d52020-01-22 10:04:11 -0600549 } else {
550 // Starting ix at 0 bakes in our conceptual first stop at -inf.
551 // TODO: good place to experiment with a loop in skvm.... stops.size() can be huge.
552 ix = p->splat(0);
553 for (float stop : stops) {
554 // ix += (t >= stop) ? +1 : 0 ~~>
555 // ix -= (t >= stop) ? -1 : 0
Mike Reedb6e7ef12020-04-03 13:34:26 -0400556 ix -= (t >= uniformF(stop));
Mike Klein85754d52020-01-22 10:04:11 -0600557 }
558 // TODO: we could skip any of the dummy stops GradientShaderBase's ctor added
559 // to ensure the full [0,1] span is covered. This linear search doesn't need
560 // them for correctness, and it'd be up to two fewer stops to check.
561 // N.B. we do still need those stops for the fOrigPos == nullptr direct math path.
562 }
563
564 // A scale factor and bias for each lane, 8 total.
565 // TODO: simpler, faster, tidier to push 8 uniform pointers, one for each struct lane?
Mike Reedb6e7ef12020-04-03 13:34:26 -0400566 ix = shl(ix, 3);
567 skvm::F32 Fr = gatherF(fbs, ix + 0);
568 skvm::F32 Fg = gatherF(fbs, ix + 1);
569 skvm::F32 Fb = gatherF(fbs, ix + 2);
570 skvm::F32 Fa = gatherF(fbs, ix + 3);
Mike Klein85754d52020-01-22 10:04:11 -0600571
Mike Reedb6e7ef12020-04-03 13:34:26 -0400572 skvm::F32 Br = gatherF(fbs, ix + 4);
573 skvm::F32 Bg = gatherF(fbs, ix + 5);
574 skvm::F32 Bb = gatherF(fbs, ix + 6);
575 skvm::F32 Ba = gatherF(fbs, ix + 7);
Mike Klein85754d52020-01-22 10:04:11 -0600576
577 // This is what we've been building towards!
Mike Reed6352f002020-03-14 23:30:10 -0400578 color = {
Mike Reedb6e7ef12020-04-03 13:34:26 -0400579 t * Fr + Br,
580 t * Fg + Bg,
581 t * Fb + Bb,
582 t * Fa + Ba,
Mike Reed6352f002020-03-14 23:30:10 -0400583 };
Mike Klein85754d52020-01-22 10:04:11 -0600584 }
585
586 // If we interpolated unpremul, premul now to match our output convention.
587 if (0 == (fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag)
588 && !fColorsAreOpaque) {
Mike Reedb6e7ef12020-04-03 13:34:26 -0400589 color = premul(color);
Mike Klein85754d52020-01-22 10:04:11 -0600590 }
591
Mike Reed6352f002020-03-14 23:30:10 -0400592 return {
Mike Reedb6e7ef12020-04-03 13:34:26 -0400593 bit_cast(mask & bit_cast(color.r)),
594 bit_cast(mask & bit_cast(color.g)),
595 bit_cast(mask & bit_cast(color.b)),
596 bit_cast(mask & bit_cast(color.a)),
Mike Reed6352f002020-03-14 23:30:10 -0400597 };
Mike Klein85754d52020-01-22 10:04:11 -0600598}
599
Mike Kleina3771842017-05-04 19:38:48 -0400600
rileya@google.com589708b2012-07-26 20:04:23 +0000601bool SkGradientShaderBase::isOpaque() const {
Mike Reedfae8fce2019-04-03 10:27:45 -0400602 return fColorsAreOpaque && (this->getTileMode() != SkTileMode::kDecal);
Mike Reed62ce2ca2018-02-19 14:20:15 -0500603}
604
reed8367b8c2014-08-22 08:30:20 -0700605static unsigned rounded_divide(unsigned numer, unsigned denom) {
606 return (numer + (denom >> 1)) / denom;
607}
608
609bool SkGradientShaderBase::onAsLuminanceColor(SkColor* lum) const {
610 // we just compute an average color.
611 // possibly we could weight this based on the proportional width for each color
612 // assuming they are not evenly distributed in the fPos array.
613 int r = 0;
614 int g = 0;
615 int b = 0;
616 const int n = fColorCount;
Florin Malita39d71de2017-10-31 11:33:49 -0400617 // TODO: use linear colors?
reed8367b8c2014-08-22 08:30:20 -0700618 for (int i = 0; i < n; ++i) {
Florin Malita39d71de2017-10-31 11:33:49 -0400619 SkColor c = this->getLegacyColor(i);
reed8367b8c2014-08-22 08:30:20 -0700620 r += SkColorGetR(c);
621 g += SkColorGetG(c);
622 b += SkColorGetB(c);
623 }
624 *lum = SkColorSetRGB(rounded_divide(r, n), rounded_divide(g, n), rounded_divide(b, n));
625 return true;
626}
627
Brian Osman6667fb12018-07-03 16:44:02 -0400628SkColor4fXformer::SkColor4fXformer(const SkColor4f* colors, int colorCount,
629 SkColorSpace* src, SkColorSpace* dst) {
Brian Osman6667fb12018-07-03 16:44:02 -0400630 fColors = colors;
Brian Osmanccd39952018-07-06 16:16:43 -0400631
Mike Kleinf9f68ff2018-10-12 14:23:06 -0400632 if (dst && !SkColorSpace::Equals(src, dst)) {
Brian Osman6667fb12018-07-03 16:44:02 -0400633 fStorage.reset(colorCount);
Brian Salomon5dfcf132018-10-12 14:39:32 +0000634
635 auto info = SkImageInfo::Make(colorCount,1, kRGBA_F32_SkColorType, kUnpremul_SkAlphaType);
636
637 SkConvertPixels(info.makeColorSpace(sk_ref_sp(dst)), fStorage.begin(), info.minRowBytes(),
638 info.makeColorSpace(sk_ref_sp(src)), fColors , info.minRowBytes());
639
Brian Osman6667fb12018-07-03 16:44:02 -0400640 fColors = fStorage.begin();
641 }
642}
643
Florin Malita5f379a82017-10-18 16:22:35 -0400644void SkGradientShaderBase::commonAsAGradient(GradientInfo* info) const {
rileya@google.com589708b2012-07-26 20:04:23 +0000645 if (info) {
646 if (info->fColorCount >= fColorCount) {
647 if (info->fColors) {
Florin Malita39d71de2017-10-31 11:33:49 -0400648 for (int i = 0; i < fColorCount; ++i) {
649 info->fColors[i] = this->getLegacyColor(i);
650 }
rileya@google.com589708b2012-07-26 20:04:23 +0000651 }
652 if (info->fColorOffsets) {
Florin Malitaed6ae562017-10-28 11:06:48 -0400653 for (int i = 0; i < fColorCount; ++i) {
654 info->fColorOffsets[i] = this->getPos(i);
rileya@google.com589708b2012-07-26 20:04:23 +0000655 }
656 }
657 }
658 info->fColorCount = fColorCount;
659 info->fTileMode = fTileMode;
reed@google.com3d3a8602013-05-24 14:58:44 +0000660 info->fGradientFlags = fGradFlags;
rileya@google.com589708b2012-07-26 20:04:23 +0000661 }
662}
663
664///////////////////////////////////////////////////////////////////////////////
665///////////////////////////////////////////////////////////////////////////////
666
reed1b747302015-01-06 07:13:19 -0800667// Return true if these parameters are valid/legal/safe to construct a gradient
668//
brianosmane25d71c2016-09-28 11:27:28 -0700669static bool valid_grad(const SkColor4f colors[], const SkScalar pos[], int count,
Mike Reedfae8fce2019-04-03 10:27:45 -0400670 SkTileMode tileMode) {
671 return nullptr != colors && count >= 1 && (unsigned)tileMode < kSkTileModeCount;
reed1b747302015-01-06 07:13:19 -0800672}
673
reed@google.com437d6eb2013-05-23 19:03:05 +0000674static void desc_init(SkGradientShaderBase::Descriptor* desc,
brianosmane25d71c2016-09-28 11:27:28 -0700675 const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
676 const SkScalar pos[], int colorCount,
Mike Reedfae8fce2019-04-03 10:27:45 -0400677 SkTileMode mode, uint32_t flags, const SkMatrix* localMatrix) {
fmalita748d6202016-05-11 11:39:58 -0700678 SkASSERT(colorCount > 1);
679
commit-bot@chromium.org6c5aea22014-04-22 16:25:15 +0000680 desc->fColors = colors;
brianosmane25d71c2016-09-28 11:27:28 -0700681 desc->fColorSpace = std::move(colorSpace);
commit-bot@chromium.org6c5aea22014-04-22 16:25:15 +0000682 desc->fPos = pos;
683 desc->fCount = colorCount;
684 desc->fTileMode = mode;
commit-bot@chromium.org6c5aea22014-04-22 16:25:15 +0000685 desc->fGradFlags = flags;
reedaddf2ed2014-08-11 08:28:24 -0700686 desc->fLocalMatrix = localMatrix;
reed@google.com437d6eb2013-05-23 19:03:05 +0000687}
688
Mike Klein024072a2018-11-11 00:26:30 +0000689static SkColor4f average_gradient_color(const SkColor4f colors[], const SkScalar pos[],
690 int colorCount) {
691 // The gradient is a piecewise linear interpolation between colors. For a given interval,
692 // the integral between the two endpoints is 0.5 * (ci + cj) * (pj - pi), which provides that
693 // intervals average color. The overall average color is thus the sum of each piece. The thing
694 // to keep in mind is that the provided gradient definition may implicitly use p=0 and p=1.
695 Sk4f blend(0.0);
696 // Bake 1/(colorCount - 1) uniform stop difference into this scale factor
697 SkScalar wScale = pos ? 0.5 : 0.5 / (colorCount - 1);
698 for (int i = 0; i < colorCount - 1; ++i) {
699 // Calculate the average color for the interval between pos(i) and pos(i+1)
700 Sk4f c0 = Sk4f::Load(&colors[i]);
701 Sk4f c1 = Sk4f::Load(&colors[i + 1]);
702 // when pos == null, there are colorCount uniformly distributed stops, going from 0 to 1,
703 // so pos[i + 1] - pos[i] = 1/(colorCount-1)
704 SkScalar w = pos ? (pos[i + 1] - pos[i]) : SK_Scalar1;
705 blend += wScale * w * (c1 + c0);
706 }
707
708 // Now account for any implicit intervals at the start or end of the stop definitions
709 if (pos) {
710 if (pos[0] > 0.0) {
711 // The first color is fixed between p = 0 to pos[0], so 0.5 * (ci + cj) * (pj - pi)
712 // becomes 0.5 * (c + c) * (pj - 0) = c * pj
713 Sk4f c = Sk4f::Load(&colors[0]);
714 blend += pos[0] * c;
715 }
716 if (pos[colorCount - 1] < SK_Scalar1) {
717 // The last color is fixed between pos[n-1] to p = 1, so 0.5 * (ci + cj) * (pj - pi)
718 // becomes 0.5 * (c + c) * (1 - pi) = c * (1 - pi)
719 Sk4f c = Sk4f::Load(&colors[colorCount - 1]);
720 blend += (1 - pos[colorCount - 1]) * c;
721 }
722 }
723
724 SkColor4f avg;
725 blend.store(&avg);
726 return avg;
727}
728
Michael Ludwigd431c722018-11-16 10:00:24 -0500729// The default SkScalarNearlyZero threshold of .0024 is too big and causes regressions for svg
730// gradients defined in the wild.
731static constexpr SkScalar kDegenerateThreshold = SK_Scalar1 / (1 << 15);
732
Mike Klein024072a2018-11-11 00:26:30 +0000733// Except for special circumstances of clamped gradients, every gradient shape--when degenerate--
734// can be mapped to the same fallbacks. The specific shape factories must account for special
735// clamped conditions separately, this will always return the last color for clamped gradients.
736static sk_sp<SkShader> make_degenerate_gradient(const SkColor4f colors[], const SkScalar pos[],
737 int colorCount, sk_sp<SkColorSpace> colorSpace,
Mike Reedfae8fce2019-04-03 10:27:45 -0400738 SkTileMode mode) {
Mike Klein024072a2018-11-11 00:26:30 +0000739 switch(mode) {
Mike Reedfae8fce2019-04-03 10:27:45 -0400740 case SkTileMode::kDecal:
Mike Klein024072a2018-11-11 00:26:30 +0000741 // normally this would reject the area outside of the interpolation region, so since
742 // inside region is empty when the radii are equal, the entire draw region is empty
Mike Reedc8bea7d2019-04-09 13:55:36 -0400743 return SkShaders::Empty();
Mike Reedfae8fce2019-04-03 10:27:45 -0400744 case SkTileMode::kRepeat:
745 case SkTileMode::kMirror:
Mike Klein024072a2018-11-11 00:26:30 +0000746 // repeat and mirror are treated the same: the border colors are never visible,
747 // but approximate the final color as infinite repetitions of the colors, so
748 // it can be represented as the average color of the gradient.
Mike Reedc8bea7d2019-04-09 13:55:36 -0400749 return SkShaders::Color(
Mike Klein024072a2018-11-11 00:26:30 +0000750 average_gradient_color(colors, pos, colorCount), std::move(colorSpace));
Mike Reedfae8fce2019-04-03 10:27:45 -0400751 case SkTileMode::kClamp:
Mike Klein024072a2018-11-11 00:26:30 +0000752 // Depending on how the gradient shape degenerates, there may be a more specialized
753 // fallback representation for the factories to use, but this is a reasonable default.
Mike Reedc8bea7d2019-04-09 13:55:36 -0400754 return SkShaders::Color(colors[colorCount - 1], std::move(colorSpace));
Mike Klein024072a2018-11-11 00:26:30 +0000755 }
Mike Reedfae8fce2019-04-03 10:27:45 -0400756 SkDEBUGFAIL("Should not be reached");
757 return nullptr;
Mike Klein024072a2018-11-11 00:26:30 +0000758}
759
brianosmane25d71c2016-09-28 11:27:28 -0700760// assumes colors is SkColor4f* and pos is SkScalar*
fmenozzie9fd0f82016-08-19 07:50:57 -0700761#define EXPAND_1_COLOR(count) \
brianosmane25d71c2016-09-28 11:27:28 -0700762 SkColor4f tmp[2]; \
fmenozzie9fd0f82016-08-19 07:50:57 -0700763 do { \
764 if (1 == count) { \
765 tmp[0] = tmp[1] = colors[0]; \
766 colors = tmp; \
767 pos = nullptr; \
768 count = 2; \
769 } \
770 } while (0)
771
fmenozzi68d952c2016-08-19 08:56:56 -0700772struct ColorStopOptimizer {
Mike Reedfae8fce2019-04-03 10:27:45 -0400773 ColorStopOptimizer(const SkColor4f* colors, const SkScalar* pos, int count, SkTileMode mode)
fmenozzi68d952c2016-08-19 08:56:56 -0700774 : fColors(colors)
775 , fPos(pos)
776 , fCount(count) {
777
778 if (!pos || count != 3) {
779 return;
780 }
781
782 if (SkScalarNearlyEqual(pos[0], 0.0f) &&
783 SkScalarNearlyEqual(pos[1], 0.0f) &&
784 SkScalarNearlyEqual(pos[2], 1.0f)) {
785
Mike Reedfae8fce2019-04-03 10:27:45 -0400786 if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode ||
fmenozzi68d952c2016-08-19 08:56:56 -0700787 colors[0] == colors[1]) {
788
fmalita582a6562016-08-22 06:28:57 -0700789 // Ignore the leftmost color/pos.
790 fColors += 1;
791 fPos += 1;
792 fCount = 2;
fmenozzi68d952c2016-08-19 08:56:56 -0700793 }
794 } else if (SkScalarNearlyEqual(pos[0], 0.0f) &&
795 SkScalarNearlyEqual(pos[1], 1.0f) &&
796 SkScalarNearlyEqual(pos[2], 1.0f)) {
797
Mike Reedfae8fce2019-04-03 10:27:45 -0400798 if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode ||
fmenozzi68d952c2016-08-19 08:56:56 -0700799 colors[1] == colors[2]) {
800
fmalita582a6562016-08-22 06:28:57 -0700801 // Ignore the rightmost color/pos.
fmenozzi68d952c2016-08-19 08:56:56 -0700802 fCount = 2;
803 }
804 }
805 }
806
brianosmane25d71c2016-09-28 11:27:28 -0700807 const SkColor4f* fColors;
808 const SkScalar* fPos;
809 int fCount;
810};
811
812struct ColorConverter {
813 ColorConverter(const SkColor* colors, int count) {
Brian Osman6667fb12018-07-03 16:44:02 -0400814 const float ONE_OVER_255 = 1.f / 255;
brianosmane25d71c2016-09-28 11:27:28 -0700815 for (int i = 0; i < count; ++i) {
Brian Osman6667fb12018-07-03 16:44:02 -0400816 fColors4f.push_back({
817 SkColorGetR(colors[i]) * ONE_OVER_255,
818 SkColorGetG(colors[i]) * ONE_OVER_255,
819 SkColorGetB(colors[i]) * ONE_OVER_255,
820 SkColorGetA(colors[i]) * ONE_OVER_255 });
brianosmane25d71c2016-09-28 11:27:28 -0700821 }
822 }
823
824 SkSTArray<2, SkColor4f, true> fColors4f;
fmenozzi68d952c2016-08-19 08:56:56 -0700825};
826
reed8a21c9f2016-03-08 18:50:00 -0800827sk_sp<SkShader> SkGradientShader::MakeLinear(const SkPoint pts[2],
fmenozzi68d952c2016-08-19 08:56:56 -0700828 const SkColor colors[],
829 const SkScalar pos[], int colorCount,
Mike Reedfae8fce2019-04-03 10:27:45 -0400830 SkTileMode mode,
fmenozzi68d952c2016-08-19 08:56:56 -0700831 uint32_t flags,
832 const SkMatrix* localMatrix) {
brianosmane25d71c2016-09-28 11:27:28 -0700833 ColorConverter converter(colors, colorCount);
834 return MakeLinear(pts, converter.fColors4f.begin(), nullptr, pos, colorCount, mode, flags,
835 localMatrix);
836}
837
838sk_sp<SkShader> SkGradientShader::MakeLinear(const SkPoint pts[2],
839 const SkColor4f colors[],
840 sk_sp<SkColorSpace> colorSpace,
841 const SkScalar pos[], int colorCount,
Mike Reedfae8fce2019-04-03 10:27:45 -0400842 SkTileMode mode,
brianosmane25d71c2016-09-28 11:27:28 -0700843 uint32_t flags,
844 const SkMatrix* localMatrix) {
fmalitac5231042016-08-10 05:45:50 -0700845 if (!pts || !SkScalarIsFinite((pts[1] - pts[0]).length())) {
halcanary96fcdcc2015-08-27 07:41:13 -0700846 return nullptr;
reed1b747302015-01-06 07:13:19 -0800847 }
848 if (!valid_grad(colors, pos, colorCount, mode)) {
halcanary96fcdcc2015-08-27 07:41:13 -0700849 return nullptr;
rileya@google.com589708b2012-07-26 20:04:23 +0000850 }
fmenozzie9fd0f82016-08-19 07:50:57 -0700851 if (1 == colorCount) {
Mike Reedc8bea7d2019-04-09 13:55:36 -0400852 return SkShaders::Color(colors[0], std::move(colorSpace));
fmenozzie9fd0f82016-08-19 07:50:57 -0700853 }
Florin Malita8d3ffad2017-02-03 18:21:17 +0000854 if (localMatrix && !localMatrix->invert(nullptr)) {
855 return nullptr;
856 }
rileya@google.com589708b2012-07-26 20:04:23 +0000857
Michael Ludwigd431c722018-11-16 10:00:24 -0500858 if (SkScalarNearlyZero((pts[1] - pts[0]).length(), kDegenerateThreshold)) {
Mike Klein024072a2018-11-11 00:26:30 +0000859 // Degenerate gradient, the only tricky complication is when in clamp mode, the limit of
860 // the gradient approaches two half planes of solid color (first and last). However, they
861 // are divided by the line perpendicular to the start and end point, which becomes undefined
862 // once start and end are exactly the same, so just use the end color for a stable solution.
863 return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode);
864 }
865
fmenozzi68d952c2016-08-19 08:56:56 -0700866 ColorStopOptimizer opt(colors, pos, colorCount, mode);
867
reed@google.com437d6eb2013-05-23 19:03:05 +0000868 SkGradientShaderBase::Descriptor desc;
brianosmane25d71c2016-09-28 11:27:28 -0700869 desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags,
870 localMatrix);
reed8a21c9f2016-03-08 18:50:00 -0800871 return sk_make_sp<SkLinearGradient>(pts, desc);
rileya@google.com589708b2012-07-26 20:04:23 +0000872}
873
reed8a21c9f2016-03-08 18:50:00 -0800874sk_sp<SkShader> SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius,
brianosmane25d71c2016-09-28 11:27:28 -0700875 const SkColor colors[],
876 const SkScalar pos[], int colorCount,
Mike Reedfae8fce2019-04-03 10:27:45 -0400877 SkTileMode mode,
brianosmane25d71c2016-09-28 11:27:28 -0700878 uint32_t flags,
879 const SkMatrix* localMatrix) {
880 ColorConverter converter(colors, colorCount);
881 return MakeRadial(center, radius, converter.fColors4f.begin(), nullptr, pos, colorCount, mode,
882 flags, localMatrix);
883}
884
885sk_sp<SkShader> SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius,
886 const SkColor4f colors[],
887 sk_sp<SkColorSpace> colorSpace,
888 const SkScalar pos[], int colorCount,
Mike Reedfae8fce2019-04-03 10:27:45 -0400889 SkTileMode mode,
brianosmane25d71c2016-09-28 11:27:28 -0700890 uint32_t flags,
891 const SkMatrix* localMatrix) {
Mike Klein024072a2018-11-11 00:26:30 +0000892 if (radius < 0) {
halcanary96fcdcc2015-08-27 07:41:13 -0700893 return nullptr;
reed1b747302015-01-06 07:13:19 -0800894 }
895 if (!valid_grad(colors, pos, colorCount, mode)) {
halcanary96fcdcc2015-08-27 07:41:13 -0700896 return nullptr;
rileya@google.com589708b2012-07-26 20:04:23 +0000897 }
fmenozzie9fd0f82016-08-19 07:50:57 -0700898 if (1 == colorCount) {
Mike Reedc8bea7d2019-04-09 13:55:36 -0400899 return SkShaders::Color(colors[0], std::move(colorSpace));
fmenozzie9fd0f82016-08-19 07:50:57 -0700900 }
Florin Malita8d3ffad2017-02-03 18:21:17 +0000901 if (localMatrix && !localMatrix->invert(nullptr)) {
902 return nullptr;
903 }
rileya@google.com589708b2012-07-26 20:04:23 +0000904
Michael Ludwigd431c722018-11-16 10:00:24 -0500905 if (SkScalarNearlyZero(radius, kDegenerateThreshold)) {
Mike Klein024072a2018-11-11 00:26:30 +0000906 // Degenerate gradient optimization, and no special logic needed for clamped radial gradient
907 return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode);
908 }
909
fmenozzi68d952c2016-08-19 08:56:56 -0700910 ColorStopOptimizer opt(colors, pos, colorCount, mode);
911
reed@google.com437d6eb2013-05-23 19:03:05 +0000912 SkGradientShaderBase::Descriptor desc;
brianosmane25d71c2016-09-28 11:27:28 -0700913 desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags,
914 localMatrix);
reed8a21c9f2016-03-08 18:50:00 -0800915 return sk_make_sp<SkRadialGradient>(center, radius, desc);
rileya@google.com589708b2012-07-26 20:04:23 +0000916}
917
reed8a21c9f2016-03-08 18:50:00 -0800918sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
brianosmane25d71c2016-09-28 11:27:28 -0700919 SkScalar startRadius,
920 const SkPoint& end,
921 SkScalar endRadius,
922 const SkColor colors[],
923 const SkScalar pos[],
924 int colorCount,
Mike Reedfae8fce2019-04-03 10:27:45 -0400925 SkTileMode mode,
brianosmane25d71c2016-09-28 11:27:28 -0700926 uint32_t flags,
927 const SkMatrix* localMatrix) {
928 ColorConverter converter(colors, colorCount);
929 return MakeTwoPointConical(start, startRadius, end, endRadius, converter.fColors4f.begin(),
930 nullptr, pos, colorCount, mode, flags, localMatrix);
931}
932
933sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
934 SkScalar startRadius,
935 const SkPoint& end,
936 SkScalar endRadius,
937 const SkColor4f colors[],
938 sk_sp<SkColorSpace> colorSpace,
939 const SkScalar pos[],
940 int colorCount,
Mike Reedfae8fce2019-04-03 10:27:45 -0400941 SkTileMode mode,
brianosmane25d71c2016-09-28 11:27:28 -0700942 uint32_t flags,
943 const SkMatrix* localMatrix) {
reed1b747302015-01-06 07:13:19 -0800944 if (startRadius < 0 || endRadius < 0) {
halcanary96fcdcc2015-08-27 07:41:13 -0700945 return nullptr;
reed1b747302015-01-06 07:13:19 -0800946 }
947 if (!valid_grad(colors, pos, colorCount, mode)) {
halcanary96fcdcc2015-08-27 07:41:13 -0700948 return nullptr;
rileya@google.com589708b2012-07-26 20:04:23 +0000949 }
Michael Ludwigd431c722018-11-16 10:00:24 -0500950 if (SkScalarNearlyZero((start - end).length(), kDegenerateThreshold)) {
Mike Klein024072a2018-11-11 00:26:30 +0000951 // If the center positions are the same, then the gradient is the radial variant of a 2 pt
952 // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate
953 // (startRadius == endRadius).
Michael Ludwigd431c722018-11-16 10:00:24 -0500954 if (SkScalarNearlyEqual(startRadius, endRadius, kDegenerateThreshold)) {
Mike Klein024072a2018-11-11 00:26:30 +0000955 // Degenerate case, where the interpolation region area approaches zero. The proper
956 // behavior depends on the tile mode, which is consistent with the default degenerate
957 // gradient behavior, except when mode = clamp and the radii > 0.
Mike Reedfae8fce2019-04-03 10:27:45 -0400958 if (mode == SkTileMode::kClamp && endRadius > kDegenerateThreshold) {
Mike Klein024072a2018-11-11 00:26:30 +0000959 // The interpolation region becomes an infinitely thin ring at the radius, so the
960 // final gradient will be the first color repeated from p=0 to 1, and then a hard
961 // stop switching to the last color at p=1.
962 static constexpr SkScalar circlePos[3] = {0, 1, 1};
963 SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]};
964 return MakeRadial(start, endRadius, reColors, std::move(colorSpace),
965 circlePos, 3, mode, flags, localMatrix);
966 } else {
967 // Otherwise use the default degenerate case
968 return make_degenerate_gradient(
969 colors, pos, colorCount, std::move(colorSpace), mode);
970 }
Michael Ludwigd431c722018-11-16 10:00:24 -0500971 } else if (SkScalarNearlyZero(startRadius, kDegenerateThreshold)) {
Mike Klein024072a2018-11-11 00:26:30 +0000972 // We can treat this gradient as radial, which is faster. If we got here, we know
973 // that endRadius is not equal to 0, so this produces a meaningful gradient
974 return MakeRadial(start, endRadius, colors, std::move(colorSpace), pos, colorCount,
975 mode, flags, localMatrix);
Brian Osman2dfab272018-11-06 00:41:40 +0000976 }
Mike Klein024072a2018-11-11 00:26:30 +0000977 // Else it's the 2pt conical radial variant with no degenerate radii, so fall through to the
978 // regular 2pt constructor.
Brian Osman2dfab272018-11-06 00:41:40 +0000979 }
Mike Klein024072a2018-11-11 00:26:30 +0000980
Florin Malita8d3ffad2017-02-03 18:21:17 +0000981 if (localMatrix && !localMatrix->invert(nullptr)) {
982 return nullptr;
983 }
reed6b7a6c72016-08-18 16:13:50 -0700984 EXPAND_1_COLOR(colorCount);
rileya@google.com589708b2012-07-26 20:04:23 +0000985
fmenozzi68d952c2016-08-19 08:56:56 -0700986 ColorStopOptimizer opt(colors, pos, colorCount, mode);
987
reed@google.com437d6eb2013-05-23 19:03:05 +0000988 SkGradientShaderBase::Descriptor desc;
Florin Malita5f379a82017-10-18 16:22:35 -0400989 desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags,
990 localMatrix);
991 return SkTwoPointConicalGradient::Create(start, startRadius, end, endRadius, desc);
rileya@google.com589708b2012-07-26 20:04:23 +0000992}
993
reed8a21c9f2016-03-08 18:50:00 -0800994sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
brianosmane25d71c2016-09-28 11:27:28 -0700995 const SkColor colors[],
996 const SkScalar pos[],
997 int colorCount,
Mike Reedfae8fce2019-04-03 10:27:45 -0400998 SkTileMode mode,
Florin Malita5a9a9812017-08-01 16:38:08 -0400999 SkScalar startAngle,
1000 SkScalar endAngle,
brianosmane25d71c2016-09-28 11:27:28 -07001001 uint32_t flags,
1002 const SkMatrix* localMatrix) {
1003 ColorConverter converter(colors, colorCount);
Florin Malita5a9a9812017-08-01 16:38:08 -04001004 return MakeSweep(cx, cy, converter.fColors4f.begin(), nullptr, pos, colorCount,
1005 mode, startAngle, endAngle, flags, localMatrix);
brianosmane25d71c2016-09-28 11:27:28 -07001006}
1007
1008sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
1009 const SkColor4f colors[],
1010 sk_sp<SkColorSpace> colorSpace,
1011 const SkScalar pos[],
1012 int colorCount,
Mike Reedfae8fce2019-04-03 10:27:45 -04001013 SkTileMode mode,
Florin Malita5a9a9812017-08-01 16:38:08 -04001014 SkScalar startAngle,
1015 SkScalar endAngle,
brianosmane25d71c2016-09-28 11:27:28 -07001016 uint32_t flags,
1017 const SkMatrix* localMatrix) {
Florin Malita5a9a9812017-08-01 16:38:08 -04001018 if (!valid_grad(colors, pos, colorCount, mode)) {
halcanary96fcdcc2015-08-27 07:41:13 -07001019 return nullptr;
rileya@google.com589708b2012-07-26 20:04:23 +00001020 }
fmenozzie9fd0f82016-08-19 07:50:57 -07001021 if (1 == colorCount) {
Mike Reedc8bea7d2019-04-09 13:55:36 -04001022 return SkShaders::Color(colors[0], std::move(colorSpace));
fmenozzie9fd0f82016-08-19 07:50:57 -07001023 }
Mike Klein024072a2018-11-11 00:26:30 +00001024 if (!SkScalarIsFinite(startAngle) || !SkScalarIsFinite(endAngle) || startAngle > endAngle) {
Florin Malita5a9a9812017-08-01 16:38:08 -04001025 return nullptr;
1026 }
Florin Malita8d3ffad2017-02-03 18:21:17 +00001027 if (localMatrix && !localMatrix->invert(nullptr)) {
1028 return nullptr;
1029 }
rileya@google.com589708b2012-07-26 20:04:23 +00001030
Michael Ludwigd431c722018-11-16 10:00:24 -05001031 if (SkScalarNearlyEqual(startAngle, endAngle, kDegenerateThreshold)) {
Mike Klein024072a2018-11-11 00:26:30 +00001032 // Degenerate gradient, which should follow default degenerate behavior unless it is
1033 // clamped and the angle is greater than 0.
Mike Reedfae8fce2019-04-03 10:27:45 -04001034 if (mode == SkTileMode::kClamp && endAngle > kDegenerateThreshold) {
Mike Klein024072a2018-11-11 00:26:30 +00001035 // In this case, the first color is repeated from 0 to the angle, then a hardstop
1036 // switches to the last color (all other colors are compressed to the infinitely thin
1037 // interpolation region).
1038 static constexpr SkScalar clampPos[3] = {0, 1, 1};
1039 SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]};
1040 return MakeSweep(cx, cy, reColors, std::move(colorSpace), clampPos, 3, mode, 0,
1041 endAngle, flags, localMatrix);
1042 } else {
1043 return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode);
1044 }
1045 }
1046
Florin Malita5a9a9812017-08-01 16:38:08 -04001047 if (startAngle <= 0 && endAngle >= 360) {
1048 // If the t-range includes [0,1], then we can always use clamping (presumably faster).
Mike Reedfae8fce2019-04-03 10:27:45 -04001049 mode = SkTileMode::kClamp;
Florin Malita5a9a9812017-08-01 16:38:08 -04001050 }
fmenozzi68d952c2016-08-19 08:56:56 -07001051
1052 ColorStopOptimizer opt(colors, pos, colorCount, mode);
1053
reed@google.com437d6eb2013-05-23 19:03:05 +00001054 SkGradientShaderBase::Descriptor desc;
brianosmane25d71c2016-09-28 11:27:28 -07001055 desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags,
1056 localMatrix);
Florin Malita5a9a9812017-08-01 16:38:08 -04001057
1058 const SkScalar t0 = startAngle / 360,
1059 t1 = endAngle / 360;
1060
1061 return sk_make_sp<SkSweepGradient>(SkPoint::Make(cx, cy), t0, t1, desc);
rileya@google.com589708b2012-07-26 20:04:23 +00001062}
1063
Mike Kleinfa5f6ce2018-10-20 08:21:31 -04001064void SkGradientShader::RegisterFlattenables() {
Brian Salomon23356442018-11-30 15:33:19 -05001065 SK_REGISTER_FLATTENABLE(SkLinearGradient);
1066 SK_REGISTER_FLATTENABLE(SkRadialGradient);
1067 SK_REGISTER_FLATTENABLE(SkSweepGradient);
1068 SK_REGISTER_FLATTENABLE(SkTwoPointConicalGradient);
Mike Klein12956722018-10-19 10:00:21 -04001069}