blob: bf7f4df48354ae5b2c36b75a5d5246fc5742d23f [file] [log] [blame]
reed@google.com0e734bd2011-12-08 17:24:44 +00001
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkGradientShader.h"
tomhudson@google.come8c984d2012-01-09 13:45:36 +000011#include "SkClampRange.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000013#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000014#include "SkUnitMapper.h"
15#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000016#include "SkTemplates.h"
17#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000018
reed@google.com0e734bd2011-12-08 17:24:44 +000019#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
20 #define USE_DITHER_32BIT_GRADIENT
21#endif
22
reed@google.com5eb158d2011-04-15 15:50:34 +000023static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
24 int count) {
25 if (count > 0) {
26 if (v0 == v1) {
27 sk_memset32(dst, v0, count);
28 } else {
29 int pairs = count >> 1;
30 for (int i = 0; i < pairs; i++) {
31 *dst++ = v0;
32 *dst++ = v1;
33 }
34 if (count & 1) {
35 *dst = v0;
36 }
37 }
38 }
39}
40
reed@google.com61eb0402011-04-15 12:11:12 +000041///////////////////////////////////////////////////////////////////////////////
tomhudson@google.com9ce767c2011-04-25 20:49:39 +000042// Can't use a two-argument function with side effects like this in a
43// constructor's initializer's argument list because the order of
44// evaluations in that context is undefined (and backwards on linux/gcc).
45static SkPoint unflatten_point(SkReader32& buffer) {
46 SkPoint retval;
47 retval.fX = buffer.readScalar();
48 retval.fY = buffer.readScalar();
49 return retval;
50}
51
52///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000053
54typedef SkFixed (*TileProc)(SkFixed);
55
reed@android.com41bccf52009-04-03 13:33:51 +000056static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 return SkClampMax(x, 0xFFFF);
58}
59
reed@android.com41bccf52009-04-03 13:33:51 +000060static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 return x & 0xFFFF;
62}
63
reed@android.com41bccf52009-04-03 13:33:51 +000064static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 int s = x << 15 >> 31;
66 return (x ^ s) & 0xFFFF;
67}
68
69static const TileProc gTileProcs[] = {
70 clamp_tileproc,
71 repeat_tileproc,
72 mirror_tileproc
73};
74
reed@google.com61eb0402011-04-15 12:11:12 +000075///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000076
reed@android.com200645d2009-12-14 16:41:57 +000077static inline int repeat_bits(int x, const int bits) {
78 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000079}
80
reed@android.com200645d2009-12-14 16:41:57 +000081static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000082#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000083 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000085 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000086#else
reed@android.com200645d2009-12-14 16:41:57 +000087 int s = x << (31 - bits) >> 31;
88 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000089#endif
90}
91
reed@android.com41bccf52009-04-03 13:33:51 +000092static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000093 return x & 0xFF;
94}
95
reed@android.com41bccf52009-04-03 13:33:51 +000096static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000097#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000098 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +0000100 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 return x & 255;
102#else
103 int s = x << 23 >> 31;
104 return (x ^ s) & 0xFF;
105#endif
106}
107
reed@google.com61eb0402011-04-15 12:11:12 +0000108///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109
110class Gradient_Shader : public SkShader {
111public:
112 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000113 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 virtual ~Gradient_Shader();
115
116 // overrides
reed@google.com7716afb2011-12-07 15:17:50 +0000117 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
118 virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000119 virtual bool isOpaque() const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000121 enum {
122 kCache16Bits = 8, // seems like enough for visual accuracy
123 kCache16Count = 1 << kCache16Bits,
124 kCache16Mask = kCache16Count - 1,
125 kCache16Shift = 16 - kCache16Bits,
126 kSqrt16Shift = 8 - kCache16Bits,
127
128 kCache32Bits = 8, // pretty much should always be 8
129 kCache32Count = 1 << kCache32Bits,
130 kCache32Mask = kCache32Count - 1,
131 kCache32Shift = 16 - kCache32Bits,
132 kSqrt32Shift = 8 - kCache32Bits,
133#ifdef USE_DITHER_32BIT_GRADIENT
134 kToggleMask32 = kCache32Count,
135#else
136 kToggleMask32 = 0,
137#endif
138 kToggleMask16 = kCache16Count
139 };
140
141
reed@android.com8a1c16f2008-12-17 15:59:43 +0000142protected:
143 Gradient_Shader(SkFlattenableReadBuffer& );
144 SkUnitMapper* fMapper;
145 SkMatrix fPtsToUnit; // set by subclass
146 SkMatrix fDstToIndex;
147 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148 TileMode fTileMode;
149 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000150 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151 uint8_t fDstToIndexClass;
152 uint8_t fFlags;
153
154 struct Rec {
155 SkFixed fPos; // 0...1
156 uint32_t fScale; // (1 << 24) / range
157 };
158 Rec* fRecs;
159
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000161 const uint16_t* getCache16() const;
162 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163
reed@google.com7c2f27d2011-03-07 19:29:00 +0000164 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000165 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000166
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167private:
168 enum {
169 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
170
reed@android.com1c12abe2009-07-02 15:01:02 +0000171 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 };
173 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000174 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
175 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176
reed@google.com7c2f27d2011-03-07 19:29:00 +0000177 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
178 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179
reed@google.com7c2f27d2011-03-07 19:29:00 +0000180 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
181 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000182 mutable unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183
reed@android.com512a8762009-12-14 15:25:36 +0000184 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000185 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
186 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000187 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000188 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000189
reed@android.com8a1c16f2008-12-17 15:59:43 +0000190 typedef SkShader INHERITED;
191};
192
reed@android.com41bccf52009-04-03 13:33:51 +0000193static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194 SkASSERT(x >= 0 && x <= SK_Scalar1);
195
196#ifdef SK_SCALAR_IS_FLOAT
197 return (unsigned)(x * 0xFFFF);
198#else
199 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
200#endif
201}
202
reed@android.com41bccf52009-04-03 13:33:51 +0000203Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
204 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000205 SkASSERT(colorCount > 1);
206
207 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
208
209 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000210 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000211
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
213 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
214 fTileMode = mode;
215 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000216
reed@android.com41bccf52009-04-03 13:33:51 +0000217 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000218 fCache32 = NULL;
219 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000220
reed@android.com41bccf52009-04-03 13:33:51 +0000221 /* Note: we let the caller skip the first and/or last position.
222 i.e. pos[0] = 0.3, pos[1] = 0.7
223 In these cases, we insert dummy entries to ensure that the final data
224 will be bracketed by [0, 1].
225 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
226
227 Thus colorCount (the caller's value, and fColorCount (our value) may
228 differ by up to 2. In the above example:
229 colorCount = 2
230 fColorCount = 4
231 */
232 fColorCount = colorCount;
233 // check if we need to add in dummy start and/or end position/colors
234 bool dummyFirst = false;
235 bool dummyLast = false;
236 if (pos) {
237 dummyFirst = pos[0] != 0;
238 dummyLast = pos[colorCount - 1] != SK_Scalar1;
239 fColorCount += dummyFirst + dummyLast;
240 }
241
242 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000243 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000244 fOrigColors = reinterpret_cast<SkColor*>(
245 sk_malloc_throw(size * fColorCount));
246 }
247 else {
248 fOrigColors = fStorage;
249 }
250
251 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000252 {
reed@android.com41bccf52009-04-03 13:33:51 +0000253 SkColor* origColors = fOrigColors;
254 if (dummyFirst) {
255 *origColors++ = colors[0];
256 }
257 memcpy(origColors, colors, colorCount * sizeof(SkColor));
258 if (dummyLast) {
259 origColors += colorCount;
260 *origColors = colors[colorCount - 1];
261 }
262 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263
reed@android.com1c12abe2009-07-02 15:01:02 +0000264 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000265 if (fColorCount > 2) {
266 Rec* recs = fRecs;
267 recs->fPos = 0;
268 // recs->fScale = 0; // unused;
269 recs += 1;
270 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000271 /* We need to convert the user's array of relative positions into
272 fixed-point positions and scale factors. We need these results
273 to be strictly monotonic (no two values equal or out of order).
274 Hence this complex loop that just jams a zero for the scale
275 value if it sees a segment out of order, and it assures that
276 we start at 0 and end at 1.0
277 */
278 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000279 int startIndex = dummyFirst ? 0 : 1;
280 int count = colorCount + dummyLast;
281 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282 // force the last value to be 1.0
283 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000284 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000286 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000287 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000288 }
reed@android.com41bccf52009-04-03 13:33:51 +0000289 // pin curr withing range
290 if (curr < 0) {
291 curr = 0;
292 } else if (curr > SK_Fixed1) {
293 curr = SK_Fixed1;
294 }
295 recs->fPos = curr;
296 if (curr > prev) {
297 recs->fScale = (1 << 24) / (curr - prev);
298 } else {
299 recs->fScale = 0; // ignore this segment
300 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 // get ready for the next value
302 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000303 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 }
reed@android.com41bccf52009-04-03 13:33:51 +0000305 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306 SkFixed dp = SK_Fixed1 / (colorCount - 1);
307 SkFixed p = dp;
308 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000309 for (int i = 1; i < colorCount; i++) {
310 recs->fPos = p;
311 recs->fScale = scale;
312 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313 p += dp;
314 }
315 }
316 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000317 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318}
319
320Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000321 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 fCacheAlpha = 256;
323
324 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
325
326 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000327 fCache32 = NULL;
328 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329
reed@android.com41bccf52009-04-03 13:33:51 +0000330 int colorCount = fColorCount = buffer.readU32();
331 if (colorCount > kColorStorageCount) {
332 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
333 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
334 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000336 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338
339 fTileMode = (TileMode)buffer.readU8();
340 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000341 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342 if (colorCount > 2) {
343 Rec* recs = fRecs;
344 recs[0].fPos = 0;
345 for (int i = 1; i < colorCount; i++) {
346 recs[i].fPos = buffer.readS32();
347 recs[i].fScale = buffer.readU32();
348 }
349 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000350 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000351 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352}
353
reed@android.com41bccf52009-04-03 13:33:51 +0000354Gradient_Shader::~Gradient_Shader() {
355 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000357 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000358 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000359 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000361 }
reed@google.com82065d62011-02-07 15:30:46 +0000362 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363}
364
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000365void Gradient_Shader::initCommon() {
366 fFlags = 0;
367 unsigned colorAlpha = 0xFF;
368 for (int i = 0; i < fColorCount; i++) {
369 colorAlpha &= SkColorGetA(fOrigColors[i]);
370 }
371 fColorsAreOpaque = colorAlpha == 0xFF;
372}
373
reed@android.com41bccf52009-04-03 13:33:51 +0000374void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000375 this->INHERITED::flatten(buffer);
376 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000377 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000378 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
379 buffer.write8(fTileMode);
380 if (fColorCount > 2) {
381 Rec* recs = fRecs;
382 for (int i = 1; i < fColorCount; i++) {
383 buffer.write32(recs[i].fPos);
384 buffer.write32(recs[i].fScale);
385 }
386 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000387 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000388}
389
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000390bool Gradient_Shader::isOpaque() const {
391 return fColorsAreOpaque;
392}
393
reed@android.com8a1c16f2008-12-17 15:59:43 +0000394bool Gradient_Shader::setContext(const SkBitmap& device,
395 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000396 const SkMatrix& matrix) {
397 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000398 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000399 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000400
401 const SkMatrix& inverse = this->getTotalInverse();
402
403 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
404 return false;
405 }
406
407 fDstToIndexProc = fDstToIndex.getMapXYProc();
408 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
409
410 // now convert our colors in to PMColors
411 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412
413 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000414 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000415 fFlags |= kOpaqueAlpha_Flag;
416 }
417 // we can do span16 as long as our individual colors are opaque,
418 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000419 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000420 fFlags |= kHasSpan16_Flag;
421 }
422
reed@google.com95eed982011-07-05 17:01:56 +0000423 this->setCacheAlpha(paintAlpha);
424 return true;
425}
426
427void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428 // if the new alpha differs from the previous time we were called, inval our cache
429 // this will trigger the cache to be rebuilt.
430 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000431 if (fCacheAlpha != alpha) {
432 fCache16 = NULL; // inval the cache
433 fCache32 = NULL; // inval the cache
434 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000435 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000436 if (fCache32PixelRef) {
437 fCache32PixelRef->notifyPixelsChanged();
438 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000440}
441
reed@android.com41bccf52009-04-03 13:33:51 +0000442static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000443 SkASSERT(a == SkToU8(a));
444 SkASSERT(b == SkToU8(b));
445 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000446 return a + ((b - a) * scale >> 8);
447}
448
reed@android.com41bccf52009-04-03 13:33:51 +0000449static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
450 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451#if 0
452 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
453 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
454 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
455 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
456
457 return SkPackARGB32(a, r, g, b);
458#else
459 int otherBlend = 256 - blend;
460
461#if 0
462 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
463 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
464 SkASSERT((t0 & t1) == 0);
465 return t0 | t1;
466#else
467 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
468 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
469#endif
470
471#endif
472}
473
474#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
475
reed@android.com41bccf52009-04-03 13:33:51 +0000476/** We take the original colors, not our premultiplied PMColors, since we can
477 build a 16bit table as long as the original colors are opaque, even if the
478 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000479*/
reed@android.com512a8762009-12-14 15:25:36 +0000480void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
481 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000482 SkASSERT(count > 1);
483 SkASSERT(SkColorGetA(c0) == 0xFF);
484 SkASSERT(SkColorGetA(c1) == 0xFF);
485
486 SkFixed r = SkColorGetR(c0);
487 SkFixed g = SkColorGetG(c0);
488 SkFixed b = SkColorGetB(c0);
489
490 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
491 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
492 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
493
494 r = SkIntToFixed(r) + 0x8000;
495 g = SkIntToFixed(g) + 0x8000;
496 b = SkIntToFixed(b) + 0x8000;
497
498 do {
499 unsigned rr = r >> 16;
500 unsigned gg = g >> 16;
501 unsigned bb = b >> 16;
502 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000503 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000504 cache += 1;
505 r += dr;
506 g += dg;
507 b += db;
508 } while (--count != 0);
509}
510
reed@google.com55b8e8c2011-01-13 16:22:35 +0000511/*
512 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
513 * semantics of how we 2x2 dither 32->16
514 */
515static inline U8CPU dither_fixed_to_8(SkFixed n) {
516 n >>= 8;
517 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
518}
519
520/*
521 * For dithering with premultiply, we want to ceiling the alpha component,
522 * to ensure that it is always >= any color component.
523 */
524static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
525 n >>= 8;
526 return ((n << 1) - (n | (n >> 8))) >> 8;
527}
528
529void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
530 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531 SkASSERT(count > 1);
532
reed@android.com1c12abe2009-07-02 15:01:02 +0000533 // need to apply paintAlpha to our two endpoints
534 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
535 SkFixed da;
536 {
537 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
538 da = SkIntToFixed(tmp - a) / (count - 1);
539 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000540
reed@android.com1c12abe2009-07-02 15:01:02 +0000541 SkFixed r = SkColorGetR(c0);
542 SkFixed g = SkColorGetG(c0);
543 SkFixed b = SkColorGetB(c0);
544 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
545 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
546 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000547
548 a = SkIntToFixed(a) + 0x8000;
549 r = SkIntToFixed(r) + 0x8000;
550 g = SkIntToFixed(g) + 0x8000;
551 b = SkIntToFixed(b) + 0x8000;
552
553 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000554 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
555 cache[kCache32Count] = SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
556 dither_fixed_to_8(r),
557 dither_fixed_to_8(g),
558 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000559 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000560 a += da;
561 r += dr;
562 g += dg;
563 b += db;
564 } while (--count != 0);
565}
566
reed@android.com41bccf52009-04-03 13:33:51 +0000567static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000568 SkASSERT((unsigned)x <= SK_Fixed1);
569 return x - (x >> 16);
570}
571
reed@android.com200645d2009-12-14 16:41:57 +0000572static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000573 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000574 if (6 == bits) {
575 return (x << 10) | (x << 4) | (x >> 2);
576 }
577 if (8 == bits) {
578 return (x << 8) | x;
579 }
580 sk_throw();
581 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000582}
583
reed@google.com7c2f27d2011-03-07 19:29:00 +0000584const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000585 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000586 // double the count for dither entries
587 const int entryCount = kCache16Count * 2;
588 const size_t allocSize = sizeof(uint16_t) * entryCount;
589
reed@android.com3c9b2a42009-08-27 19:28:37 +0000590 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000591 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000592 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000593 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000594 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000595 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000596 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597 Rec* rec = fRecs;
598 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000599 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000600 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601 SkASSERT(nextIndex < kCache16Count);
602
603 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000604 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000605 prevIndex = nextIndex;
606 }
607 SkASSERT(prevIndex == kCache16Count - 1);
608 }
609
reed@android.com41bccf52009-04-03 13:33:51 +0000610 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000611 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000612 uint16_t* linear = fCache16; // just computed linear data
613 uint16_t* mapped = fCache16Storage; // storage for mapped data
614 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000615 for (int i = 0; i < kCache16Count; i++) {
616 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000618 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000619 }
620 sk_free(fCache16);
621 fCache16 = fCache16Storage;
622 }
623 }
624 return fCache16;
625}
626
reed@google.com7c2f27d2011-03-07 19:29:00 +0000627const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000628 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000629 // double the count for dither entries
630 const int entryCount = kCache32Count * 2;
631 const size_t allocSize = sizeof(SkPMColor) * entryCount;
632
reed@google.comdc731fd2010-12-23 15:19:47 +0000633 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000634 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
635 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000636 }
637 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000638 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000639 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
640 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000641 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642 Rec* rec = fRecs;
643 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000644 for (int i = 1; i < fColorCount; i++) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000645 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646 SkASSERT(nextIndex < kCache32Count);
647
648 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000649 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
650 fOrigColors[i],
651 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 prevIndex = nextIndex;
653 }
654 SkASSERT(prevIndex == kCache32Count - 1);
655 }
656
reed@android.com41bccf52009-04-03 13:33:51 +0000657 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000658 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000659 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000661 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000663 for (int i = 0; i < kCache32Count; i++) {
664 int index = map->mapUnit16((i << 8) | i) >> 8;
665 mapped[i] = linear[index];
666 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000667 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000668 fCache32PixelRef->unref();
669 fCache32PixelRef = newPR;
670 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671 }
672 }
673 return fCache32;
674}
675
reed@google.comdc731fd2010-12-23 15:19:47 +0000676/*
677 * Because our caller might rebuild the same (logically the same) gradient
678 * over and over, we'd like to return exactly the same "bitmap" if possible,
679 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
680 * To do that, we maintain a private cache of built-bitmaps, based on our
681 * colors and positions. Note: we don't try to flatten the fMapper, so if one
682 * is present, we skip the cache for now.
683 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000684void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000685 // our caller assumes no external alpha, so we ensure that our cache is
686 // built with 0xFF
687 this->setCacheAlpha(0xFF);
688
reed@google.comdc731fd2010-12-23 15:19:47 +0000689 // don't have a way to put the mapper into our cache-key yet
690 if (fMapper) {
691 // force our cahce32pixelref to be built
692 (void)this->getCache32();
693 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
694 bitmap->setPixelRef(fCache32PixelRef);
695 return;
696 }
697
698 // build our key: [numColors + colors[] + {positions[]} ]
699 int count = 1 + fColorCount;
700 if (fColorCount > 2) {
701 count += fColorCount - 1; // fRecs[].fPos
702 }
703
704 SkAutoSTMalloc<16, int32_t> storage(count);
705 int32_t* buffer = storage.get();
706
707 *buffer++ = fColorCount;
708 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
709 buffer += fColorCount;
710 if (fColorCount > 2) {
711 for (int i = 1; i < fColorCount; i++) {
712 *buffer++ = fRecs[i].fPos;
713 }
714 }
715 SkASSERT(buffer - storage.get() == count);
716
717 ///////////////////////////////////
718
719 static SkMutex gMutex;
720 static SkBitmapCache* gCache;
721 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
722 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
723 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000724
reed@google.comdc731fd2010-12-23 15:19:47 +0000725 if (NULL == gCache) {
726 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
727 }
728 size_t size = count * sizeof(int32_t);
729
730 if (!gCache->find(storage.get(), size, bitmap)) {
731 // force our cahce32pixelref to be built
732 (void)this->getCache32();
733 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
734 bitmap->setPixelRef(fCache32PixelRef);
735
736 gCache->add(storage.get(), size, *bitmap);
737 }
738}
739
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000740void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
741 if (info) {
742 if (info->fColorCount >= fColorCount) {
743 if (info->fColors) {
744 memcpy(info->fColors, fOrigColors,
745 fColorCount * sizeof(SkColor));
746 }
747 if (info->fColorOffsets) {
748 if (fColorCount == 2) {
749 info->fColorOffsets[0] = 0;
750 info->fColorOffsets[1] = SK_Scalar1;
751 } else if (fColorCount > 2) {
752 for (int i = 0; i < fColorCount; i++)
753 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
754 }
755 }
756 }
757 info->fColorCount = fColorCount;
758 info->fTileMode = fTileMode;
759 }
760}
761
reed@google.com61eb0402011-04-15 12:11:12 +0000762///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763
reed@android.com41bccf52009-04-03 13:33:51 +0000764static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765 SkVector vec = pts[1] - pts[0];
766 SkScalar mag = vec.length();
767 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
768
769 vec.scale(inv);
770 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
771 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
772 matrix->postScale(inv, inv);
773}
774
775///////////////////////////////////////////////////////////////////////////////
776
777class Linear_Gradient : public Gradient_Shader {
778public:
779 Linear_Gradient(const SkPoint pts[2],
780 const SkColor colors[], const SkScalar pos[], int colorCount,
781 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000782 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
783 fStart(pts[0]),
784 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785 {
786 pts_to_unit_matrix(pts, &fPtsToUnit);
787 }
reed@android.com9b46e772009-06-05 12:24:41 +0000788
reed@google.com7716afb2011-12-07 15:17:50 +0000789 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
790 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
791 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
792 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
793 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
794 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795
reed@google.com55b8e8c2011-01-13 16:22:35 +0000796 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 return SkNEW_ARGS(Linear_Gradient, (buffer));
798 }
799
reed@google.com7716afb2011-12-07 15:17:50 +0000800 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000801 this->INHERITED::flatten(buffer);
802 buffer.writeScalar(fStart.fX);
803 buffer.writeScalar(fStart.fY);
804 buffer.writeScalar(fEnd.fX);
805 buffer.writeScalar(fEnd.fY);
806 }
807
caryclark@google.comd26147a2011-12-15 14:16:43 +0000808 SK_DECLARE_FLATTENABLE_REGISTRAR()
809
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000811 Linear_Gradient(SkFlattenableReadBuffer& buffer)
812 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000813 fStart(unflatten_point(buffer)),
814 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000815 }
reed@google.com7716afb2011-12-07 15:17:50 +0000816 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817
818private:
819 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000820 const SkPoint fStart;
821 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822};
823
reed@android.com5119bdb2009-06-12 21:27:03 +0000824bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
825 const SkMatrix& matrix) {
826 if (!this->INHERITED::setContext(device, paint, matrix)) {
827 return false;
828 }
829
830 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
831 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000832 fFlags |= SkShader::kConstInY32_Flag;
833 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
834 // only claim this if we do have a 16bit mode (i.e. none of our
835 // colors have alpha), and if we are not dithering (which obviously
836 // is not const in Y).
837 fFlags |= SkShader::kConstInY16_Flag;
838 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000839 }
840 return true;
841}
842
reed@google.com5eb158d2011-04-15 15:50:34 +0000843#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000844 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000845 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000846 SkASSERT(fi <= 0xFF); \
847 fx += dx; \
848 *dstC++ = cache[toggle + fi]; \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000849 toggle ^= Gradient_Shader::kToggleMask32; \
reed@google.com13659f12011-04-18 19:59:38 +0000850 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000851
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000852namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000853
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000854typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
855 SkPMColor* SK_RESTRICT dstC,
856 const SkPMColor* SK_RESTRICT cache,
857 int toggle, int count);
858
859void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
860 SkPMColor* SK_RESTRICT dstC,
861 const SkPMColor* SK_RESTRICT cache,
862 int toggle, int count) {
863 // we're a vertical gradient, so no change in a span
864 unsigned fi = proc(fx) >> Gradient_Shader::kCache32Shift;
865 sk_memset32_dither(dstC, cache[toggle + fi],
866 cache[(toggle ^ Gradient_Shader::kToggleMask32) + fi], count);
867
868}
869
870void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
871 SkPMColor* SK_RESTRICT dstC,
872 const SkPMColor* SK_RESTRICT cache,
873 int toggle, int count) {
874 SkClampRange range;
875 range.init(fx, dx, count, 0, 0xFF);
876
877 if ((count = range.fCount0) > 0) {
878 sk_memset32_dither(dstC,
879 cache[toggle + range.fV0],
880 cache[(toggle ^ Gradient_Shader::kToggleMask32) + range.fV0],
881 count);
882 dstC += count;
883 }
884 if ((count = range.fCount1) > 0) {
885 int unroll = count >> 3;
886 fx = range.fFx1;
887 for (int i = 0; i < unroll; i++) {
888 NO_CHECK_ITER; NO_CHECK_ITER;
889 NO_CHECK_ITER; NO_CHECK_ITER;
890 NO_CHECK_ITER; NO_CHECK_ITER;
891 NO_CHECK_ITER; NO_CHECK_ITER;
892 }
893 if ((count &= 7) > 0) {
894 do {
895 NO_CHECK_ITER;
896 } while (--count != 0);
897 }
898 }
899 if ((count = range.fCount2) > 0) {
900 sk_memset32_dither(dstC,
901 cache[toggle + range.fV1],
902 cache[(toggle ^ Gradient_Shader::kToggleMask32) + range.fV1],
903 count);
904 }
905}
906
907// TODO: we could merge mirror and repeat if we passed in a pointer to the
908// *_8bits proc, but that'd lose inlining, which might be significant here.
909void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
910 SkPMColor* SK_RESTRICT dstC,
911 const SkPMColor* SK_RESTRICT cache,
912 int toggle, int count) {
913 do {
914 unsigned fi = mirror_8bits(fx >> 8);
915 SkASSERT(fi <= 0xFF);
916 fx += dx;
917 *dstC++ = cache[toggle + fi];
918 toggle ^= Gradient_Shader::kToggleMask32;
919 } while (--count != 0);
920}
921
922void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
923 SkPMColor* SK_RESTRICT dstC,
924 const SkPMColor* SK_RESTRICT cache,
925 int toggle, int count) {
926 do {
927 unsigned fi = repeat_8bits(fx >> 8);
928 SkASSERT(fi <= 0xFF);
929 fx += dx;
930 *dstC++ = cache[toggle + fi];
931 toggle ^= Gradient_Shader::kToggleMask32;
932 } while (--count != 0);
933}
934}
935
936void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
937 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938 SkASSERT(count > 0);
939
940 SkPoint srcPt;
941 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
942 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +0000943 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +0000944#ifdef USE_DITHER_32BIT_GRADIENT
reed@google.com55b8e8c2011-01-13 16:22:35 +0000945 int toggle = ((x ^ y) & 1) << kCache32Bits;
reed@google.com0e734bd2011-12-08 17:24:44 +0000946#else
947 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +0000948#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000949
reed@android.comc552a432009-06-12 20:02:50 +0000950 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000951 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
952 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
954
reed@android.comc552a432009-06-12 20:02:50 +0000955 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000956 SkFixed dxStorage[1];
957 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
958 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000959 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000960 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
961 dx = SkScalarToFixed(fDstToIndex.getScaleX());
962 }
963
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000964 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +0000965 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000966 shadeProc = shadeSpan_linear_vertical;
reed@android.comc552a432009-06-12 20:02:50 +0000967 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000968 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +0000969 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000970 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +0000971 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000973 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000974 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +0000975 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 SkScalar dstX = SkIntToScalar(x);
977 SkScalar dstY = SkIntToScalar(y);
978 do {
979 dstProc(fDstToIndex, dstX, dstY, &srcPt);
980 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
981 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000982 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
983 toggle ^= Gradient_Shader::kToggleMask32;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984 dstX += SK_Scalar1;
985 } while (--count != 0);
986 }
987}
988
reed@google.com55b8e8c2011-01-13 16:22:35 +0000989SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000990 SkMatrix* matrix,
991 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000992 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000994 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995 }
996 if (matrix) {
997 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
998 matrix->preConcat(fPtsToUnit);
999 }
1000 if (xy) {
1001 xy[0] = fTileMode;
1002 xy[1] = kClamp_TileMode;
1003 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001004 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005}
1006
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001007SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1008 if (info) {
1009 commonAsAGradient(info);
1010 info->fPoint[0] = fStart;
1011 info->fPoint[1] = fEnd;
1012 }
1013 return kLinear_GradientType;
1014}
1015
reed@android.com3c9b2a42009-08-27 19:28:37 +00001016static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1017 int count) {
1018 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019 *dst++ = value;
1020 count -= 1;
1021 SkTSwap(value, other);
1022 }
1023
1024 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001025
reed@android.com3c9b2a42009-08-27 19:28:37 +00001026 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001027 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001028 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001029}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001030
reed@google.com5eb158d2011-04-15 15:50:34 +00001031#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001032 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001033 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
1034 SkASSERT(fi <= Gradient_Shader::kCache16Mask); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001035 fx += dx; \
1036 *dstC++ = cache[toggle + fi]; \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001037 toggle ^= Gradient_Shader::kToggleMask16; \
reed@google.com13659f12011-04-18 19:59:38 +00001038 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001039
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001040namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001041
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001042typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
1043 uint16_t* SK_RESTRICT dstC,
1044 const uint16_t* SK_RESTRICT cache,
1045 int toggle, int count);
1046
1047void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1048 uint16_t* SK_RESTRICT dstC,
1049 const uint16_t* SK_RESTRICT cache,
1050 int toggle, int count) {
1051 // we're a vertical gradient, so no change in a span
1052 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
1053 SkASSERT(fi <= Gradient_Shader::kCache16Mask);
1054 dither_memset16(dstC, cache[toggle + fi],
1055 cache[(toggle ^ Gradient_Shader::kToggleMask16) + fi], count);
1056
1057}
1058
1059void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1060 uint16_t* SK_RESTRICT dstC,
1061 const uint16_t* SK_RESTRICT cache,
1062 int toggle, int count) {
1063 SkClampRange range;
1064 range.init(fx, dx, count, 0, Gradient_Shader::kCache16Mask);
1065
1066 if ((count = range.fCount0) > 0) {
1067 dither_memset16(dstC,
1068 cache[toggle + range.fV0],
1069 cache[(toggle ^ Gradient_Shader::kToggleMask16) + range.fV0],
1070 count);
1071 dstC += count;
1072 }
1073 if ((count = range.fCount1) > 0) {
1074 int unroll = count >> 3;
1075 fx = range.fFx1;
1076 for (int i = 0; i < unroll; i++) {
1077 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1078 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1079 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1080 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1081 }
1082 if ((count &= 7) > 0) {
1083 do {
1084 NO_CHECK_ITER_16;
1085 } while (--count != 0);
1086 }
1087 }
1088 if ((count = range.fCount2) > 0) {
1089 dither_memset16(dstC,
1090 cache[toggle + range.fV1],
1091 cache[(toggle ^ Gradient_Shader::kToggleMask16) + range.fV1],
1092 count);
1093 }
1094}
1095
1096void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1097 uint16_t* SK_RESTRICT dstC,
1098 const uint16_t* SK_RESTRICT cache,
1099 int toggle, int count) {
1100 do {
1101 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1102 Gradient_Shader::kCache16Bits);
1103 SkASSERT(fi <= Gradient_Shader::kCache16Mask);
1104 fx += dx;
1105 *dstC++ = cache[toggle + fi];
1106 toggle ^= Gradient_Shader::kToggleMask16;
1107 } while (--count != 0);
1108}
1109
1110void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1111 uint16_t* SK_RESTRICT dstC,
1112 const uint16_t* SK_RESTRICT cache,
1113 int toggle, int count) {
1114 SkASSERT(proc == repeat_tileproc);
1115 do {
1116 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1117 Gradient_Shader::kCache16Bits);
1118 SkASSERT(fi <= Gradient_Shader::kCache16Mask);
1119 fx += dx;
1120 *dstC++ = cache[toggle + fi];
1121 toggle ^= Gradient_Shader::kToggleMask16;
1122 } while (--count != 0);
1123}
1124}
1125
1126void Linear_Gradient::shadeSpan16(int x, int y,
1127 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128 SkASSERT(count > 0);
1129
1130 SkPoint srcPt;
1131 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1132 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001133 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001134 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001135
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001136 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001137 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1138 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001139 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1140
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001141 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001142 SkFixed dxStorage[1];
1143 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1144 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001145 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001146 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1147 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1148 }
1149
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001150 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001151 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001152 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001153 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001154 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001155 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001156 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001157 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001158 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001159 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001160 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001161 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162 SkScalar dstX = SkIntToScalar(x);
1163 SkScalar dstY = SkIntToScalar(y);
1164 do {
1165 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1166 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1167 SkASSERT(fi <= 0xFFFF);
1168
reed@android.com512a8762009-12-14 15:25:36 +00001169 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001170 *dstC++ = cache[toggle + index];
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001171 toggle ^= Gradient_Shader::kToggleMask16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172
1173 dstX += SK_Scalar1;
1174 } while (--count != 0);
1175 }
1176}
1177
1178///////////////////////////////////////////////////////////////////////////////
1179
1180#define kSQRT_TABLE_BITS 11
1181#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1182
1183#include "SkRadialGradient_Table.h"
1184
1185#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1186
1187#include <stdio.h>
1188
reed@google.com61eb0402011-04-15 12:11:12 +00001189void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001190 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1191
1192 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1193 SkASSERT(file);
1194 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1195
reed@google.com61eb0402011-04-15 12:11:12 +00001196 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1197 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001198 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001199 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001200
1201 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1202
1203 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001204 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001206 }
1207 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001208 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001209 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001210 }
1211 ::fprintf(file, "};\n");
1212 ::fclose(file);
1213}
1214
1215#endif
1216
1217
reed@google.com61eb0402011-04-15 12:11:12 +00001218static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1219 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220 SkScalar inv = SkScalarInvert(radius);
1221
1222 matrix->setTranslate(-center.fX, -center.fY);
1223 matrix->postScale(inv, inv);
1224}
1225
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001226
1227namespace {
1228
1229typedef void (* RadialShade16Proc)(SkFixed fx, SkFixed dx,
1230 SkFixed fy, SkFixed dy,
1231 uint16_t* dstC, const uint16_t* SK_RESTRICT cache,
1232 int toggle, int count);
1233
1234void shadeSpan16_radial_clamp(SkFixed fx, SkFixed dx,
1235 SkFixed fy, SkFixed dy,
1236 uint16_t* dstC, const uint16_t* SK_RESTRICT cache,
1237 int toggle, int count) {
1238 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1239
1240 /* knock these down so we can pin against +- 0x7FFF, which is an
1241 immediate load, rather than 0xFFFF which is slower. This is a
1242 compromise, since it reduces our precision, but that appears
1243 to be visually OK. If we decide this is OK for all of our cases,
1244 we could (it seems) put this scale-down into fDstToIndex,
1245 to avoid having to do these extra shifts each time.
1246 */
1247 fx >>= 1;
1248 dx >>= 1;
1249 fy >>= 1;
1250 dy >>= 1;
1251 // might perform this check for the other modes,
1252 // but the win will be a smaller % of the total
1253 if (dy == 0) {
1254 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1255 fy *= fy;
1256 do {
1257 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1258 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1259 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1260 fx += dx;
1261 *dstC++ = cache[toggle +
1262 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
1263 toggle ^= Gradient_Shader::kToggleMask16;
1264 } while (--count != 0);
1265 } else {
1266 do {
1267 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1268 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1269 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1270 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1271 fx += dx;
1272 fy += dy;
1273 *dstC++ = cache[toggle +
1274 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
1275 toggle ^= Gradient_Shader::kToggleMask16;
1276 } while (--count != 0);
1277 }
1278}
1279
1280void shadeSpan16_radial_mirror(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1281 uint16_t* dstC, const uint16_t* SK_RESTRICT cache,
1282 int toggle, int count) {
1283 do {
1284 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1285 unsigned fi = mirror_tileproc(dist);
1286 SkASSERT(fi <= 0xFFFF);
1287 fx += dx;
1288 fy += dy;
1289 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
1290 toggle ^= Gradient_Shader::kToggleMask16;
1291 } while (--count != 0);
1292}
1293
1294void shadeSpan16_radial_repeat(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1295 uint16_t* dstC, const uint16_t* SK_RESTRICT cache,
1296 int toggle, int count) {
1297 do {
1298 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1299 unsigned fi = repeat_tileproc(dist);
1300 SkASSERT(fi <= 0xFFFF);
1301 fx += dx;
1302 fy += dy;
1303 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
1304 toggle ^= Gradient_Shader::kToggleMask16;
1305 } while (--count != 0);
1306}
1307
1308}
1309
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310class Radial_Gradient : public Gradient_Shader {
1311public:
1312 Radial_Gradient(const SkPoint& center, SkScalar radius,
1313 const SkColor colors[], const SkScalar pos[], int colorCount,
1314 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001315 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1316 fCenter(center),
1317 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318 {
1319 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1320 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1321
1322 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1323 }
reed@google.com61eb0402011-04-15 12:11:12 +00001324
reed@google.com8e6d9142011-12-07 15:30:34 +00001325 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count) SK_OVERRIDE;
reed@google.com7716afb2011-12-07 15:17:50 +00001326 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327 SkASSERT(count > 0);
1328
1329 SkPoint srcPt;
1330 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1331 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001332 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334
reed@android.com3c9b2a42009-08-27 19:28:37 +00001335 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001336 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1337 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1339 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1340
reed@android.com3c9b2a42009-08-27 19:28:37 +00001341 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 SkFixed storage[2];
1343 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1344 dx = storage[0];
1345 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001346 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001347 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1348 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1349 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1350 }
1351
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001352 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001353 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001354 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001355 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001356 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001357 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001358 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001360 (*shadeProc)(fx, dx, fy, dy, dstC, cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001361 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001362 SkScalar dstX = SkIntToScalar(x);
1363 SkScalar dstY = SkIntToScalar(y);
1364 do {
1365 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1366 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1367 SkASSERT(fi <= 0xFFFF);
1368
1369 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001370 *dstC++ = cache[toggle + index];
1371 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001372
1373 dstX += SK_Scalar1;
1374 } while (--count != 0);
1375 }
1376 }
1377
reed@google.com55b8e8c2011-01-13 16:22:35 +00001378 virtual BitmapType asABitmap(SkBitmap* bitmap,
1379 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001380 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001381 SkScalar* twoPointRadialParams)
1382 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001383 if (bitmap) {
1384 this->commonAsABitmap(bitmap);
1385 }
1386 if (matrix) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001387 matrix->setScale(SkIntToScalar(kCache32Count),
1388 SkIntToScalar(kCache32Count));
reed@google.comdc731fd2010-12-23 15:19:47 +00001389 matrix->preConcat(fPtsToUnit);
1390 }
1391 if (xy) {
1392 xy[0] = fTileMode;
1393 xy[1] = kClamp_TileMode;
1394 }
1395 return kRadial_BitmapType;
1396 }
reed@google.com7716afb2011-12-07 15:17:50 +00001397 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001398 if (info) {
1399 commonAsAGradient(info);
1400 info->fPoint[0] = fCenter;
1401 info->fRadius[0] = fRadius;
1402 }
1403 return kRadial_GradientType;
1404 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001405
reed@google.com8e6d9142011-12-07 15:30:34 +00001406 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 return SkNEW_ARGS(Radial_Gradient, (buffer));
1408 }
1409
reed@google.com7716afb2011-12-07 15:17:50 +00001410 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001411 this->INHERITED::flatten(buffer);
1412 buffer.writeScalar(fCenter.fX);
1413 buffer.writeScalar(fCenter.fY);
1414 buffer.writeScalar(fRadius);
1415 }
1416
reed@android.com8a1c16f2008-12-17 15:59:43 +00001417protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001418 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1419 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001420 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001421 fRadius(buffer.readScalar()) {
1422 }
reed@google.com7716afb2011-12-07 15:17:50 +00001423 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424
1425private:
1426 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001427 const SkPoint fCenter;
1428 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001429};
1430
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001431namespace {
1432
1433inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001434 // fast, overly-conservative test: checks unit square instead
1435 // of unit circle
1436 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1437 (fx <= -SK_FixedHalf && dx <= 0);
1438 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1439 (fy <= -SK_FixedHalf && dy <= 0);
1440
1441 return xClamped || yClamped;
1442}
1443
1444// Return true if (fx * fy) is always inside the unit circle
1445// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1446// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001447inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001448 int fy, int dy, int count) {
1449 SkASSERT(count > 0);
1450 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1451 return false;
1452 }
1453 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1454 return false;
1455 }
1456 fx += (count - 1) * dx;
1457 fy += (count - 1) * dy;
1458 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1459 return false;
1460 }
1461 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1462}
1463
1464#define UNPINNED_RADIAL_STEP \
1465 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001466 *dstC++ = cache[sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift]; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001467 fx += dx; \
1468 fy += dy;
1469
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001470typedef void (* RadialShadeProc)(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1471 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1472 int count, SkPoint& srcPt, float fdx, float fdy);
1473
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001474// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001475void shadeSpan_radial_clamp(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1476 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
1477 int count, SkPoint& srcPt, float fdx, float fdy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001478 // Floating point seems to be slower than fixed point,
1479 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001480 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001481 fx >>= 1;
1482 dx >>= 1;
1483 fy >>= 1;
1484 dy >>= 1;
1485 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001486 sk_memset32(dstC, cache[Gradient_Shader::kCache32Count - 1], count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001487 } else if ((count > 4) &&
1488 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1489 unsigned fi;
1490 // 4x unroll appears to be no faster than 2x unroll on Linux
1491 while (count > 1) {
1492 UNPINNED_RADIAL_STEP;
1493 UNPINNED_RADIAL_STEP;
1494 count -= 2;
1495 }
1496 if (count) {
1497 UNPINNED_RADIAL_STEP;
1498 }
1499 }
1500 else {
1501 do {
1502 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1503 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1504 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1505 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001506 *dstC++ = cache[sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001507 fx += dx;
1508 fy += dy;
1509 } while (--count != 0);
1510 }
1511}
1512
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001513void shadeSpan_radial_mirror(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1514 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
1515 int count, SkPoint& srcPt, float fdx, float fdy) {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001516#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001517 float ffx = srcPt.fX;
1518 float ffy = srcPt.fY;
1519 do {
1520 float fdist = sk_float_sqrt(ffx*ffx + ffy*ffy);
1521 unsigned fi = mirror_tileproc(SkFloatToFixed(fdist));
1522 SkASSERT(fi <= 0xFFFF);
1523 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
1524 ffx += fdx;
1525 ffy += fdy;
1526 } while (--count != 0);
1527#else
1528 do {
1529 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1530 SkFixedSquare(fy);
1531 if (magnitudeSquared < 0) // Overflow.
1532 magnitudeSquared = SK_FixedMax;
1533 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1534 unsigned fi = mirror_tileproc(dist);
1535 SkASSERT(fi <= 0xFFFF);
1536 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
1537 fx += dx;
1538 fy += dy;
1539 } while (--count != 0);
1540#endif
1541}
1542
1543void shadeSpan_radial_repeat(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1544 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
1545 int count, SkPoint& srcPt, float fdx, float fdy) {
1546 do {
1547 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1548 SkFixedSquare(fy);
1549 if (magnitudeSquared < 0) // Overflow.
1550 magnitudeSquared = SK_FixedMax;
1551 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1552 unsigned fi = repeat_tileproc(dist);
1553 SkASSERT(fi <= 0xFFFF);
1554 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
1555 fx += dx;
1556 fy += dy;
1557 } while (--count != 0);
1558}
1559}
1560
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001561void Radial_Gradient::shadeSpan(int x, int y,
1562 SkPMColor* SK_RESTRICT dstC, int count) {
1563 SkASSERT(count > 0);
1564
1565 SkPoint srcPt;
1566 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1567 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001568 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001569
1570 if (fDstToIndexClass != kPerspective_MatrixClass) {
1571 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1572 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1573 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1574 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001575 float fdx = 0;
1576 float fdy = 0;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001577
1578 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1579 SkFixed storage[2];
1580 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1581 dx = storage[0];
1582 dy = storage[1];
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001583#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001584 fdx = SkFixedToFloat(storage[0]);
1585 fdy = SkFixedToFloat(storage[1]);
1586#endif
1587 } else {
1588 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1589 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1590 dy = SkScalarToFixed(fDstToIndex.getSkewY());
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001591#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001592 fdx = fDstToIndex.getScaleX();
1593 fdy = fDstToIndex.getSkewY();
1594#endif
1595 }
1596
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001597 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001598 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001599 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001600 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001601 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001602 } else {
1603 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001604 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001605 (*shadeProc)(fx, dx, fy, dy, dstC, cache, count, srcPt, fdx, fdy);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001606 } else { // perspective case
1607 SkScalar dstX = SkIntToScalar(x);
1608 SkScalar dstY = SkIntToScalar(y);
1609 do {
1610 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1611 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1612 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001613 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001614 dstX += SK_Scalar1;
1615 } while (--count != 0);
1616 }
1617}
1618
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001619/* Two-point radial gradients are specified by two circles, each with a center
1620 point and radius. The gradient can be considered to be a series of
1621 concentric circles, with the color interpolated from the start circle
1622 (at t=0) to the end circle (at t=1).
1623
1624 For each point (x, y) in the span, we want to find the
1625 interpolated circle that intersects that point. The center
1626 of the desired circle (Cx, Cy) falls at some distance t
1627 along the line segment between the start point (Sx, Sy) and
1628 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001629
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001630 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1631 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001632
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001633 The radius of the desired circle (r) is also a linear interpolation t
1634 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001635
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001636 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001637
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001638 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001639
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001640 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001641
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001642 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001643
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001644 (x - ((1 - t) * Sx + t * Ex))^2
1645 + (y - ((1 - t) * Sy + t * Ey))^2
1646 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001647
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001648 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001649
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001650 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1651 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1652 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001653
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001654 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1655
1656 [Dx^2 + Dy^2 - Dr^2)] * t^2
1657 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1658 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001659
1660 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001661 possible circles on which the point may fall. Solving for t yields
1662 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001663
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001664 If a<0, the start circle is entirely contained in the
1665 end circle, and one of the roots will be <0 or >1 (off the line
1666 segment). If a>0, the start circle falls at least partially
1667 outside the end circle (or vice versa), and the gradient
1668 defines a "tube" where a point may be on one circle (on the
1669 inside of the tube) or the other (outside of the tube). We choose
1670 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001671
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001672 In order to keep the math to within the limits of fixed point,
1673 we divide the entire quadratic by Dr^2, and replace
1674 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001675
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001676 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1677 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1678 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001679
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001680 (x' and y' are computed by appending the subtract and scale to the
1681 fDstToIndex matrix in the constructor).
1682
1683 Since the 'A' component of the quadratic is independent of x' and y', it
1684 is precomputed in the constructor. Since the 'B' component is linear in
1685 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001686 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001687 a perspective projection), it must be computed in the loop.
1688
1689*/
1690
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001691namespace {
1692
1693inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1694 SkScalar sr2d2, SkScalar foura,
1695 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001696 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001697 if (0 == foura) {
1698 return SkScalarToFixed(SkScalarDiv(-c, b));
1699 }
1700
reed@google.com84e9c082011-04-13 17:44:24 +00001701 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001702 if (discrim < 0) {
1703 discrim = -discrim;
1704 }
reed@google.com84e9c082011-04-13 17:44:24 +00001705 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1706 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001707 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001708 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001709 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001710 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001711 }
reed@google.com84e9c082011-04-13 17:44:24 +00001712 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001713}
1714
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001715typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1716 SkScalar fy, SkScalar dy,
1717 SkScalar b, SkScalar db,
1718 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
1719 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1720 int count);
1721
1722void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1723 SkScalar fy, SkScalar dy,
1724 SkScalar b, SkScalar db,
1725 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
1726 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1727 int count) {
1728 for (; count > 0; --count) {
1729 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1730 fOneOverTwoA, posRoot);
1731 SkFixed index = SkClampMax(t, 0xFFFF);
1732 SkASSERT(index <= 0xFFFF);
1733 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1734 fx += dx;
1735 fy += dy;
1736 b += db;
1737 }
1738}
1739void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1740 SkScalar fy, SkScalar dy,
1741 SkScalar b, SkScalar db,
1742 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
1743 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1744 int count) {
1745 for (; count > 0; --count) {
1746 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1747 fOneOverTwoA, posRoot);
1748 SkFixed index = mirror_tileproc(t);
1749 SkASSERT(index <= 0xFFFF);
1750 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1751 fx += dx;
1752 fy += dy;
1753 b += db;
1754 }
1755}
1756
1757void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1758 SkScalar fy, SkScalar dy,
1759 SkScalar b, SkScalar db,
1760 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
1761 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1762 int count) {
1763 for (; count > 0; --count) {
1764 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1765 fOneOverTwoA, posRoot);
1766 SkFixed index = repeat_tileproc(t);
1767 SkASSERT(index <= 0xFFFF);
1768 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1769 fx += dx;
1770 fy += dy;
1771 b += db;
1772 }
1773}
1774
1775
1776
1777}
1778
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001779class Two_Point_Radial_Gradient : public Gradient_Shader {
1780public:
1781 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1782 const SkPoint& end, SkScalar endRadius,
1783 const SkColor colors[], const SkScalar pos[],
1784 int colorCount, SkShader::TileMode mode,
1785 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001786 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1787 fCenter1(start),
1788 fCenter2(end),
1789 fRadius1(startRadius),
1790 fRadius2(endRadius) {
1791 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001792 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001793
1794 virtual BitmapType asABitmap(SkBitmap* bitmap,
1795 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001796 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001797 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001798 if (bitmap) {
1799 this->commonAsABitmap(bitmap);
1800 }
1801 SkScalar diffL = 0; // just to avoid gcc warning
1802 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001803 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001804 SkScalarSquare(fDiff.fY));
1805 }
1806 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001807 if (diffL) {
1808 SkScalar invDiffL = SkScalarInvert(diffL);
1809 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1810 SkScalarMul(invDiffL, fDiff.fX));
1811 } else {
1812 matrix->reset();
1813 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001814 matrix->preConcat(fPtsToUnit);
1815 }
1816 if (xy) {
1817 xy[0] = fTileMode;
1818 xy[1] = kClamp_TileMode;
1819 }
1820 if (NULL != twoPointRadialParams) {
1821 twoPointRadialParams[0] = diffL;
1822 twoPointRadialParams[1] = fStartRadius;
1823 twoPointRadialParams[2] = fDiffRadius;
1824 }
1825 return kTwoPointRadial_BitmapType;
1826 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001827
reed@google.com8e6d9142011-12-07 15:30:34 +00001828 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001829 if (info) {
1830 commonAsAGradient(info);
1831 info->fPoint[0] = fCenter1;
1832 info->fPoint[1] = fCenter2;
1833 info->fRadius[0] = fRadius1;
1834 info->fRadius[1] = fRadius2;
1835 }
1836 return kRadial2_GradientType;
1837 }
1838
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001839 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1840 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001841 SkASSERT(count > 0);
1842
1843 // Zero difference between radii: fill with transparent black.
1844 if (fDiffRadius == 0) {
1845 sk_bzero(dstC, count * sizeof(*dstC));
1846 return;
1847 }
1848 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1849 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001850 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001851
1852 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001853 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001854 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001855 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001856 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1857 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001858 SkScalar dx, fx = srcPt.fX;
1859 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001860
reed@google.com61eb0402011-04-15 12:11:12 +00001861 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001862 SkFixed fixedX, fixedY;
1863 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1864 dx = SkFixedToScalar(fixedX);
1865 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001866 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001867 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001868 dx = fDstToIndex.getScaleX();
1869 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001870 }
reed@google.com84e9c082011-04-13 17:44:24 +00001871 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1872 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1873 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1874 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001875
1876 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00001877 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001878 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00001879 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001880 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00001881 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001882 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001883 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001884 (*shadeProc)(fx, dx, fy, dy, b, db,
1885 fSr2D2, foura, fOneOverTwoA, posRoot,
1886 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00001887 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001888 SkScalar dstX = SkIntToScalar(x);
1889 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001890 for (; count > 0; --count) {
1891 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001892 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001893 SkScalar fx = srcPt.fX;
1894 SkScalar fy = srcPt.fY;
1895 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1896 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001897 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1898 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001899 SkFixed index = proc(t);
1900 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001901 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00001902 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001903 }
1904 }
1905 }
1906
reed@android.com6c59a172009-09-22 20:24:05 +00001907 virtual bool setContext(const SkBitmap& device,
1908 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00001909 const SkMatrix& matrix) SK_OVERRIDE {
1910 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00001911 return false;
1912 }
1913
1914 // we don't have a span16 proc
1915 fFlags &= ~kHasSpan16_Flag;
1916 return true;
1917 }
1918
reed@google.com8e6d9142011-12-07 15:30:34 +00001919 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001920 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1921 }
1922
reed@google.com7716afb2011-12-07 15:17:50 +00001923 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00001924 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001925 buffer.writeScalar(fCenter1.fX);
1926 buffer.writeScalar(fCenter1.fY);
1927 buffer.writeScalar(fCenter2.fX);
1928 buffer.writeScalar(fCenter2.fY);
1929 buffer.writeScalar(fRadius1);
1930 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001931 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001932
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001933protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001934 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001935 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001936 fCenter1(unflatten_point(buffer)),
1937 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001938 fRadius1(buffer.readScalar()),
1939 fRadius2(buffer.readScalar()) {
1940 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001941 };
reed@google.com7716afb2011-12-07 15:17:50 +00001942 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001943
1944private:
1945 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001946 const SkPoint fCenter1;
1947 const SkPoint fCenter2;
1948 const SkScalar fRadius1;
1949 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001950 SkPoint fDiff;
1951 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001952
1953 void init() {
1954 fDiff = fCenter1 - fCenter2;
1955 fDiffRadius = fRadius2 - fRadius1;
1956 SkScalar inv = SkScalarInvert(fDiffRadius);
1957 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1958 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1959 fStartRadius = SkScalarMul(fRadius1, inv);
1960 fSr2D2 = SkScalarSquare(fStartRadius);
1961 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00001962 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001963
1964 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1965 fPtsToUnit.postScale(inv, inv);
1966 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001967};
1968
reed@android.com8a1c16f2008-12-17 15:59:43 +00001969///////////////////////////////////////////////////////////////////////////////
1970
1971class Sweep_Gradient : public Gradient_Shader {
1972public:
1973 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1974 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001975 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1976 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 {
1978 fPtsToUnit.setTranslate(-cx, -cy);
1979 }
reed@google.com7716afb2011-12-07 15:17:50 +00001980 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
1981 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001982
1983 virtual BitmapType asABitmap(SkBitmap* bitmap,
1984 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001985 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00001986 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001987 if (bitmap) {
1988 this->commonAsABitmap(bitmap);
1989 }
1990 if (matrix) {
1991 *matrix = fPtsToUnit;
1992 }
1993 if (xy) {
1994 xy[0] = fTileMode;
1995 xy[1] = kClamp_TileMode;
1996 }
1997 return kSweep_BitmapType;
1998 }
1999
reed@google.com7716afb2011-12-07 15:17:50 +00002000 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002001 if (info) {
2002 commonAsAGradient(info);
2003 info->fPoint[0] = fCenter;
2004 }
2005 return kSweep_GradientType;
2006 }
2007
reed@google.com8e6d9142011-12-07 15:30:34 +00002008 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002009 return SkNEW_ARGS(Sweep_Gradient, (buffer));
2010 }
2011
reed@google.com7716afb2011-12-07 15:17:50 +00002012 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002013 this->INHERITED::flatten(buffer);
2014 buffer.writeScalar(fCenter.fX);
2015 buffer.writeScalar(fCenter.fY);
2016 }
2017
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002019 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
2020 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002021 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002022 }
2023
reed@google.com7716afb2011-12-07 15:17:50 +00002024 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002025
2026private:
2027 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002028 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029};
2030
2031#ifdef COMPUTE_SWEEP_TABLE
2032#define PI 3.14159265
2033static bool gSweepTableReady;
2034static uint8_t gSweepTable[65];
2035
2036/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2037 We scale the results to [0..32]
2038*/
reed@google.com61eb0402011-04-15 12:11:12 +00002039static const uint8_t* build_sweep_table() {
2040 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041 const int N = 65;
2042 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002043
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 for (int i = 0; i < N; i++)
2045 {
2046 double arg = i / DENOM;
2047 double v = atan(arg);
2048 int iv = (int)round(v * DENOM * 2 / PI);
2049// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2050 printf("%d, ", iv);
2051 gSweepTable[i] = iv;
2052 }
2053 gSweepTableReady = true;
2054 }
2055 return gSweepTable;
2056}
2057#else
2058static const uint8_t gSweepTable[] = {
2059 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2060 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2061 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2062 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2063 32
2064};
2065static const uint8_t* build_sweep_table() { return gSweepTable; }
2066#endif
2067
2068// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2069// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2070// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2071
2072//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002073static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002074 SkASSERT(numer <= denom);
2075 SkASSERT(numer > 0);
2076 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002077
reed@android.com8a1c16f2008-12-17 15:59:43 +00002078 int nbits = SkCLZ(numer);
2079 int dbits = SkCLZ(denom);
2080 int bits = 6 - nbits + dbits;
2081 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002082
reed@google.com61eb0402011-04-15 12:11:12 +00002083 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002084 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002085 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002086
2087 denom <<= dbits - 1;
2088 numer <<= nbits - 1;
2089
2090 unsigned result = 0;
2091
2092 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002093 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002094 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002095 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002097 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002098
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002100 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002101 // make room for the rest of the answer bits
2102 result <<= bits;
2103 switch (bits) {
2104 case 6:
2105 if ((numer = (numer << 1) - denom) >= 0)
2106 result |= 32;
2107 else
2108 numer += denom;
2109 case 5:
2110 if ((numer = (numer << 1) - denom) >= 0)
2111 result |= 16;
2112 else
2113 numer += denom;
2114 case 4:
2115 if ((numer = (numer << 1) - denom) >= 0)
2116 result |= 8;
2117 else
2118 numer += denom;
2119 case 3:
2120 if ((numer = (numer << 1) - denom) >= 0)
2121 result |= 4;
2122 else
2123 numer += denom;
2124 case 2:
2125 if ((numer = (numer << 1) - denom) >= 0)
2126 result |= 2;
2127 else
2128 numer += denom;
2129 case 1:
2130 default: // not strictly need, but makes GCC make better ARM code
2131 if ((numer = (numer << 1) - denom) >= 0)
2132 result |= 1;
2133 else
2134 numer += denom;
2135 }
2136 }
2137 return result;
2138}
2139
2140// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002141static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142#ifdef SK_DEBUG
2143 {
2144 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002145 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 gOnce = true;
2147 SkASSERT(div_64(55, 55) == 64);
2148 SkASSERT(div_64(128, 256) == 32);
2149 SkASSERT(div_64(2326528, 4685824) == 31);
2150 SkASSERT(div_64(753664, 5210112) == 9);
2151 SkASSERT(div_64(229376, 4882432) == 3);
2152 SkASSERT(div_64(2, 64) == 2);
2153 SkASSERT(div_64(1, 64) == 1);
2154 // test that we handle underflow correctly
2155 SkASSERT(div_64(12345, 0x54321234) == 0);
2156 }
2157 }
2158#endif
2159
2160 SkASSERT(y > 0 && x > 0);
2161 const uint8_t* table = build_sweep_table();
2162
2163 unsigned result;
2164 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002165 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 // first part of the atan(v) = PI/2 - atan(1/v) identity
2167 // since our div_64 and table want v <= 1, where v = y/x
2168 SkTSwap<SkFixed>(x, y);
2169 }
2170
2171 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002172
reed@android.com8a1c16f2008-12-17 15:59:43 +00002173#ifdef SK_DEBUG
2174 {
2175 unsigned result2 = SkDivBits(y, x, 6);
2176 SkASSERT(result2 == result ||
2177 (result == 1 && result2 == 0));
2178 }
2179#endif
2180
2181 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2182 result = table[result];
2183
reed@google.com61eb0402011-04-15 12:11:12 +00002184 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002185 // complete the atan(v) = PI/2 - atan(1/v) identity
2186 result = 64 - result;
2187 // pin to 63
2188 result -= result >> 6;
2189 }
2190
2191 SkASSERT(result <= 63);
2192 return result;
2193}
2194
2195// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002196#ifdef SK_SCALAR_IS_FLOAT
2197static unsigned SkATan2_255(float y, float x) {
2198 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2199 static const float g255Over2PI = 40.584510488433314f;
2200
2201 float result = sk_float_atan2(y, x);
2202 if (result < 0) {
2203 result += 2 * SK_ScalarPI;
2204 }
2205 SkASSERT(result >= 0);
2206 // since our value is always >= 0, we can cast to int, which is faster than
2207 // calling floorf()
2208 int ir = (int)(result * g255Over2PI);
2209 SkASSERT(ir >= 0 && ir <= 255);
2210 return ir;
2211}
2212#else
reed@google.com61eb0402011-04-15 12:11:12 +00002213static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2214 if (x == 0) {
2215 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002216 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002217 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002218 return y < 0 ? 192 : 64;
2219 }
reed@google.com61eb0402011-04-15 12:11:12 +00002220 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002221 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002222 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002223
reed@android.com8a1c16f2008-12-17 15:59:43 +00002224 /* Find the right quadrant for x,y
2225 Since atan_0_90 only handles the first quadrant, we rotate x,y
2226 appropriately before calling it, and then add the right amount
2227 to account for the real quadrant.
2228 quadrant 0 : add 0 | x > 0 && y > 0
2229 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2230 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2231 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002232
reed@android.com8a1c16f2008-12-17 15:59:43 +00002233 map x<0 to (1 << 6)
2234 map y<0 to (3 << 6)
2235 add = map_x ^ map_y
2236 */
2237 int xsign = x >> 31;
2238 int ysign = y >> 31;
2239 int add = ((-xsign) ^ (ysign & 3)) << 6;
2240
2241#ifdef SK_DEBUG
2242 if (0 == add)
2243 SkASSERT(x > 0 && y > 0);
2244 else if (64 == add)
2245 SkASSERT(x < 0 && y > 0);
2246 else if (128 == add)
2247 SkASSERT(x < 0 && y < 0);
2248 else if (192 == add)
2249 SkASSERT(x > 0 && y < 0);
2250 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002251 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002252#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002253
reed@android.com8a1c16f2008-12-17 15:59:43 +00002254 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2255 where we need to rotate x,y by 90 or -90
2256 */
2257 x = (x ^ xsign) - xsign;
2258 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002259 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002260 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002261 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002262
2263 unsigned result = add + atan_0_90(y, x);
2264 SkASSERT(result < 256);
2265 return result;
2266}
reed@google.com51baf5a2011-09-21 13:38:36 +00002267#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002268
reed@google.comdd5bd672011-09-20 15:56:13 +00002269void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002270 SkMatrix::MapXYProc proc = fDstToIndexProc;
2271 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002272 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002273 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002274
reed@google.com61eb0402011-04-15 12:11:12 +00002275 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002276 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2277 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002278 SkScalar dx, fx = srcPt.fX;
2279 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002280
reed@google.com61eb0402011-04-15 12:11:12 +00002281 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002282 SkFixed storage[2];
2283 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2284 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002285 dx = SkFixedToScalar(storage[0]);
2286 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002287 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002288 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002289 dx = matrix.getScaleX();
2290 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002291 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002292
reed@google.com61eb0402011-04-15 12:11:12 +00002293 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002294 *dstC++ = cache[SkATan2_255(fy, fx)];
2295 fx += dx;
2296 fy += dy;
2297 }
reed@google.com61eb0402011-04-15 12:11:12 +00002298 } else { // perspective case
2299 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002300 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002301 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2302 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002303 }
2304 }
2305}
2306
reed@google.comdd5bd672011-09-20 15:56:13 +00002307void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002308 SkMatrix::MapXYProc proc = fDstToIndexProc;
2309 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002310 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002311 int toggle = ((x ^ y) & 1) << kCache16Bits;
2312 SkPoint srcPt;
2313
reed@google.com61eb0402011-04-15 12:11:12 +00002314 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002315 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2316 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002317 SkScalar dx, fx = srcPt.fX;
2318 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002319
reed@google.com61eb0402011-04-15 12:11:12 +00002320 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002321 SkFixed storage[2];
2322 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2323 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002324 dx = SkFixedToScalar(storage[0]);
2325 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002326 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002327 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002328 dx = matrix.getScaleX();
2329 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002330 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002331
reed@google.com61eb0402011-04-15 12:11:12 +00002332 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002333 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2334 *dstC++ = cache[toggle + index];
2335 toggle ^= (1 << kCache16Bits);
2336 fx += dx;
2337 fy += dy;
2338 }
reed@google.com61eb0402011-04-15 12:11:12 +00002339 } else { // perspective case
2340 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002341 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2342 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002343
reed@google.com51baf5a2011-09-21 13:38:36 +00002344 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002345 index >>= (8 - kCache16Bits);
2346 *dstC++ = cache[toggle + index];
2347 toggle ^= (1 << kCache16Bits);
2348 }
2349 }
2350}
2351
reed@google.com61eb0402011-04-15 12:11:12 +00002352///////////////////////////////////////////////////////////////////////////////
2353///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002354
2355// assumes colors is SkColor* and pos is SkScalar*
2356#define EXPAND_1_COLOR(count) \
2357 SkColor tmp[2]; \
2358 do { \
2359 if (1 == count) { \
2360 tmp[0] = tmp[1] = colors[0]; \
2361 colors = tmp; \
2362 pos = NULL; \
2363 count = 2; \
2364 } \
2365 } while (0)
2366
reed@google.com61eb0402011-04-15 12:11:12 +00002367SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2368 const SkColor colors[],
2369 const SkScalar pos[], int colorCount,
2370 SkShader::TileMode mode,
2371 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002372 if (NULL == pts || NULL == colors || colorCount < 1) {
2373 return NULL;
2374 }
2375 EXPAND_1_COLOR(colorCount);
2376
reed@android.comab840b82009-07-01 17:00:03 +00002377 return SkNEW_ARGS(Linear_Gradient,
2378 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002379}
2380
reed@google.com61eb0402011-04-15 12:11:12 +00002381SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2382 const SkColor colors[],
2383 const SkScalar pos[], int colorCount,
2384 SkShader::TileMode mode,
2385 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002386 if (radius <= 0 || NULL == colors || colorCount < 1) {
2387 return NULL;
2388 }
2389 EXPAND_1_COLOR(colorCount);
2390
reed@android.comab840b82009-07-01 17:00:03 +00002391 return SkNEW_ARGS(Radial_Gradient,
2392 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002393}
2394
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002395SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2396 SkScalar startRadius,
2397 const SkPoint& end,
2398 SkScalar endRadius,
2399 const SkColor colors[],
2400 const SkScalar pos[],
2401 int colorCount,
2402 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002403 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002404 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2405 return NULL;
2406 }
2407 EXPAND_1_COLOR(colorCount);
2408
2409 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002410 (start, startRadius, end, endRadius, colors, pos,
2411 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002412}
2413
reed@android.com8a1c16f2008-12-17 15:59:43 +00002414SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2415 const SkColor colors[],
2416 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002417 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002418 if (NULL == colors || count < 1) {
2419 return NULL;
2420 }
2421 EXPAND_1_COLOR(count);
2422
2423 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2424}
2425
caryclark@google.comd26147a2011-12-15 14:16:43 +00002426SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2427 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2428 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002429
caryclark@google.comd26147a2011-12-15 14:16:43 +00002430 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002431
caryclark@google.comd26147a2011-12-15 14:16:43 +00002432 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2433SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END