blob: a907d04a0d53fbbdb8dbec3f03558023ed2d63e1 [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.com51baf5a2011-09-21 13:38:36 +000019#if defined(SK_SCALAR_IS_FLOAT) && !defined(SK_DONT_USE_FLOAT_SQRT)
20 #define SK_USE_FLOAT_SQRT
21#endif
22
reed@google.com0e734bd2011-12-08 17:24:44 +000023#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
24 #define USE_DITHER_32BIT_GRADIENT
25#endif
26
reed@google.com5eb158d2011-04-15 15:50:34 +000027static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
28 int count) {
29 if (count > 0) {
30 if (v0 == v1) {
31 sk_memset32(dst, v0, count);
32 } else {
33 int pairs = count >> 1;
34 for (int i = 0; i < pairs; i++) {
35 *dst++ = v0;
36 *dst++ = v1;
37 }
38 if (count & 1) {
39 *dst = v0;
40 }
41 }
42 }
43}
44
reed@google.com61eb0402011-04-15 12:11:12 +000045///////////////////////////////////////////////////////////////////////////////
tomhudson@google.com9ce767c2011-04-25 20:49:39 +000046// Can't use a two-argument function with side effects like this in a
47// constructor's initializer's argument list because the order of
48// evaluations in that context is undefined (and backwards on linux/gcc).
49static SkPoint unflatten_point(SkReader32& buffer) {
50 SkPoint retval;
51 retval.fX = buffer.readScalar();
52 retval.fY = buffer.readScalar();
53 return retval;
54}
55
56///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000057
58typedef SkFixed (*TileProc)(SkFixed);
59
reed@android.com41bccf52009-04-03 13:33:51 +000060static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 return SkClampMax(x, 0xFFFF);
62}
63
reed@android.com41bccf52009-04-03 13:33:51 +000064static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 return x & 0xFFFF;
66}
67
reed@android.com41bccf52009-04-03 13:33:51 +000068static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 int s = x << 15 >> 31;
70 return (x ^ s) & 0xFFFF;
71}
72
73static const TileProc gTileProcs[] = {
74 clamp_tileproc,
75 repeat_tileproc,
76 mirror_tileproc
77};
78
reed@google.com61eb0402011-04-15 12:11:12 +000079///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000080
reed@android.com200645d2009-12-14 16:41:57 +000081static inline int repeat_bits(int x, const int bits) {
82 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000083}
84
reed@android.com200645d2009-12-14 16:41:57 +000085static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000086#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000087 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000089 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000090#else
reed@android.com200645d2009-12-14 16:41:57 +000091 int s = x << (31 - bits) >> 31;
92 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000093#endif
94}
95
reed@android.com41bccf52009-04-03 13:33:51 +000096static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000097 return x & 0xFF;
98}
99
reed@android.com41bccf52009-04-03 13:33:51 +0000100static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +0000102 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +0000104 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 return x & 255;
106#else
107 int s = x << 23 >> 31;
108 return (x ^ s) & 0xFF;
109#endif
110}
111
reed@google.com61eb0402011-04-15 12:11:12 +0000112///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113
114class Gradient_Shader : public SkShader {
115public:
116 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000117 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118 virtual ~Gradient_Shader();
119
120 // overrides
reed@google.com7716afb2011-12-07 15:17:50 +0000121 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
122 virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000123 virtual bool isOpaque() const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000124
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000125 enum {
126 kCache16Bits = 8, // seems like enough for visual accuracy
127 kCache16Count = 1 << kCache16Bits,
128 kCache16Mask = kCache16Count - 1,
129 kCache16Shift = 16 - kCache16Bits,
130 kSqrt16Shift = 8 - kCache16Bits,
131
132 kCache32Bits = 8, // pretty much should always be 8
133 kCache32Count = 1 << kCache32Bits,
134 kCache32Mask = kCache32Count - 1,
135 kCache32Shift = 16 - kCache32Bits,
136 kSqrt32Shift = 8 - kCache32Bits,
137#ifdef USE_DITHER_32BIT_GRADIENT
138 kToggleMask32 = kCache32Count,
139#else
140 kToggleMask32 = 0,
141#endif
142 kToggleMask16 = kCache16Count
143 };
144
145
reed@android.com8a1c16f2008-12-17 15:59:43 +0000146protected:
147 Gradient_Shader(SkFlattenableReadBuffer& );
148 SkUnitMapper* fMapper;
149 SkMatrix fPtsToUnit; // set by subclass
150 SkMatrix fDstToIndex;
151 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152 TileMode fTileMode;
153 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000154 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155 uint8_t fDstToIndexClass;
156 uint8_t fFlags;
157
158 struct Rec {
159 SkFixed fPos; // 0...1
160 uint32_t fScale; // (1 << 24) / range
161 };
162 Rec* fRecs;
163
reed@android.com8a1c16f2008-12-17 15:59:43 +0000164 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000165 const uint16_t* getCache16() const;
166 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167
reed@google.com7c2f27d2011-03-07 19:29:00 +0000168 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000169 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000170
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171private:
172 enum {
173 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
174
reed@android.com1c12abe2009-07-02 15:01:02 +0000175 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176 };
177 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000178 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
179 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180
reed@google.com7c2f27d2011-03-07 19:29:00 +0000181 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
182 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183
reed@google.com7c2f27d2011-03-07 19:29:00 +0000184 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
185 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000186 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 +0000187
reed@android.com512a8762009-12-14 15:25:36 +0000188 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000189 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
190 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000191 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000192 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000193
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194 typedef SkShader INHERITED;
195};
196
reed@android.com41bccf52009-04-03 13:33:51 +0000197static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000198 SkASSERT(x >= 0 && x <= SK_Scalar1);
199
200#ifdef SK_SCALAR_IS_FLOAT
201 return (unsigned)(x * 0xFFFF);
202#else
203 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
204#endif
205}
206
reed@android.com41bccf52009-04-03 13:33:51 +0000207Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
208 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209 SkASSERT(colorCount > 1);
210
211 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
212
213 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000214 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
217 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
218 fTileMode = mode;
219 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000220
reed@android.com41bccf52009-04-03 13:33:51 +0000221 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000222 fCache32 = NULL;
223 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000224
reed@android.com41bccf52009-04-03 13:33:51 +0000225 /* Note: we let the caller skip the first and/or last position.
226 i.e. pos[0] = 0.3, pos[1] = 0.7
227 In these cases, we insert dummy entries to ensure that the final data
228 will be bracketed by [0, 1].
229 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
230
231 Thus colorCount (the caller's value, and fColorCount (our value) may
232 differ by up to 2. In the above example:
233 colorCount = 2
234 fColorCount = 4
235 */
236 fColorCount = colorCount;
237 // check if we need to add in dummy start and/or end position/colors
238 bool dummyFirst = false;
239 bool dummyLast = false;
240 if (pos) {
241 dummyFirst = pos[0] != 0;
242 dummyLast = pos[colorCount - 1] != SK_Scalar1;
243 fColorCount += dummyFirst + dummyLast;
244 }
245
246 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000247 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000248 fOrigColors = reinterpret_cast<SkColor*>(
249 sk_malloc_throw(size * fColorCount));
250 }
251 else {
252 fOrigColors = fStorage;
253 }
254
255 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256 {
reed@android.com41bccf52009-04-03 13:33:51 +0000257 SkColor* origColors = fOrigColors;
258 if (dummyFirst) {
259 *origColors++ = colors[0];
260 }
261 memcpy(origColors, colors, colorCount * sizeof(SkColor));
262 if (dummyLast) {
263 origColors += colorCount;
264 *origColors = colors[colorCount - 1];
265 }
266 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000267
reed@android.com1c12abe2009-07-02 15:01:02 +0000268 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000269 if (fColorCount > 2) {
270 Rec* recs = fRecs;
271 recs->fPos = 0;
272 // recs->fScale = 0; // unused;
273 recs += 1;
274 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000275 /* We need to convert the user's array of relative positions into
276 fixed-point positions and scale factors. We need these results
277 to be strictly monotonic (no two values equal or out of order).
278 Hence this complex loop that just jams a zero for the scale
279 value if it sees a segment out of order, and it assures that
280 we start at 0 and end at 1.0
281 */
282 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000283 int startIndex = dummyFirst ? 0 : 1;
284 int count = colorCount + dummyLast;
285 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 // force the last value to be 1.0
287 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000288 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000290 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000291 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000292 }
reed@android.com41bccf52009-04-03 13:33:51 +0000293 // pin curr withing range
294 if (curr < 0) {
295 curr = 0;
296 } else if (curr > SK_Fixed1) {
297 curr = SK_Fixed1;
298 }
299 recs->fPos = curr;
300 if (curr > prev) {
301 recs->fScale = (1 << 24) / (curr - prev);
302 } else {
303 recs->fScale = 0; // ignore this segment
304 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305 // get ready for the next value
306 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000307 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000308 }
reed@android.com41bccf52009-04-03 13:33:51 +0000309 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 SkFixed dp = SK_Fixed1 / (colorCount - 1);
311 SkFixed p = dp;
312 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000313 for (int i = 1; i < colorCount; i++) {
314 recs->fPos = p;
315 recs->fScale = scale;
316 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317 p += dp;
318 }
319 }
320 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000321 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322}
323
324Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000325 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 fCacheAlpha = 256;
327
328 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
329
330 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000331 fCache32 = NULL;
332 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333
reed@android.com41bccf52009-04-03 13:33:51 +0000334 int colorCount = fColorCount = buffer.readU32();
335 if (colorCount > kColorStorageCount) {
336 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
337 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
338 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000340 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000341 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342
343 fTileMode = (TileMode)buffer.readU8();
344 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000345 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346 if (colorCount > 2) {
347 Rec* recs = fRecs;
348 recs[0].fPos = 0;
349 for (int i = 1; i < colorCount; i++) {
350 recs[i].fPos = buffer.readS32();
351 recs[i].fScale = buffer.readU32();
352 }
353 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000354 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000355 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356}
357
reed@android.com41bccf52009-04-03 13:33:51 +0000358Gradient_Shader::~Gradient_Shader() {
359 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000361 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000362 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000363 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000364 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000365 }
reed@google.com82065d62011-02-07 15:30:46 +0000366 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367}
368
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000369void Gradient_Shader::initCommon() {
370 fFlags = 0;
371 unsigned colorAlpha = 0xFF;
372 for (int i = 0; i < fColorCount; i++) {
373 colorAlpha &= SkColorGetA(fOrigColors[i]);
374 }
375 fColorsAreOpaque = colorAlpha == 0xFF;
376}
377
reed@android.com41bccf52009-04-03 13:33:51 +0000378void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379 this->INHERITED::flatten(buffer);
380 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000381 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000382 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
383 buffer.write8(fTileMode);
384 if (fColorCount > 2) {
385 Rec* recs = fRecs;
386 for (int i = 1; i < fColorCount; i++) {
387 buffer.write32(recs[i].fPos);
388 buffer.write32(recs[i].fScale);
389 }
390 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000391 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000392}
393
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000394bool Gradient_Shader::isOpaque() const {
395 return fColorsAreOpaque;
396}
397
reed@android.com8a1c16f2008-12-17 15:59:43 +0000398bool Gradient_Shader::setContext(const SkBitmap& device,
399 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000400 const SkMatrix& matrix) {
401 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000402 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000403 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000404
405 const SkMatrix& inverse = this->getTotalInverse();
406
407 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
408 return false;
409 }
410
411 fDstToIndexProc = fDstToIndex.getMapXYProc();
412 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
413
414 // now convert our colors in to PMColors
415 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000416
417 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000418 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419 fFlags |= kOpaqueAlpha_Flag;
420 }
421 // we can do span16 as long as our individual colors are opaque,
422 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000423 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424 fFlags |= kHasSpan16_Flag;
425 }
426
reed@google.com95eed982011-07-05 17:01:56 +0000427 this->setCacheAlpha(paintAlpha);
428 return true;
429}
430
431void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432 // if the new alpha differs from the previous time we were called, inval our cache
433 // this will trigger the cache to be rebuilt.
434 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000435 if (fCacheAlpha != alpha) {
436 fCache16 = NULL; // inval the cache
437 fCache32 = NULL; // inval the cache
438 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000439 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000440 if (fCache32PixelRef) {
441 fCache32PixelRef->notifyPixelsChanged();
442 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000443 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000444}
445
reed@android.com41bccf52009-04-03 13:33:51 +0000446static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000447 SkASSERT(a == SkToU8(a));
448 SkASSERT(b == SkToU8(b));
449 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000450 return a + ((b - a) * scale >> 8);
451}
452
reed@android.com41bccf52009-04-03 13:33:51 +0000453static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
454 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000455#if 0
456 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
457 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
458 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
459 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
460
461 return SkPackARGB32(a, r, g, b);
462#else
463 int otherBlend = 256 - blend;
464
465#if 0
466 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
467 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
468 SkASSERT((t0 & t1) == 0);
469 return t0 | t1;
470#else
471 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
472 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
473#endif
474
475#endif
476}
477
478#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
479
reed@android.com41bccf52009-04-03 13:33:51 +0000480/** We take the original colors, not our premultiplied PMColors, since we can
481 build a 16bit table as long as the original colors are opaque, even if the
482 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000483*/
reed@android.com512a8762009-12-14 15:25:36 +0000484void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
485 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000486 SkASSERT(count > 1);
487 SkASSERT(SkColorGetA(c0) == 0xFF);
488 SkASSERT(SkColorGetA(c1) == 0xFF);
489
490 SkFixed r = SkColorGetR(c0);
491 SkFixed g = SkColorGetG(c0);
492 SkFixed b = SkColorGetB(c0);
493
494 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
495 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
496 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
497
498 r = SkIntToFixed(r) + 0x8000;
499 g = SkIntToFixed(g) + 0x8000;
500 b = SkIntToFixed(b) + 0x8000;
501
502 do {
503 unsigned rr = r >> 16;
504 unsigned gg = g >> 16;
505 unsigned bb = b >> 16;
506 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000507 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000508 cache += 1;
509 r += dr;
510 g += dg;
511 b += db;
512 } while (--count != 0);
513}
514
reed@google.com55b8e8c2011-01-13 16:22:35 +0000515/*
516 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
517 * semantics of how we 2x2 dither 32->16
518 */
519static inline U8CPU dither_fixed_to_8(SkFixed n) {
520 n >>= 8;
521 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
522}
523
524/*
525 * For dithering with premultiply, we want to ceiling the alpha component,
526 * to ensure that it is always >= any color component.
527 */
528static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
529 n >>= 8;
530 return ((n << 1) - (n | (n >> 8))) >> 8;
531}
532
533void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
534 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000535 SkASSERT(count > 1);
536
reed@android.com1c12abe2009-07-02 15:01:02 +0000537 // need to apply paintAlpha to our two endpoints
538 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
539 SkFixed da;
540 {
541 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
542 da = SkIntToFixed(tmp - a) / (count - 1);
543 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000544
reed@android.com1c12abe2009-07-02 15:01:02 +0000545 SkFixed r = SkColorGetR(c0);
546 SkFixed g = SkColorGetG(c0);
547 SkFixed b = SkColorGetB(c0);
548 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
549 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
550 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000551
552 a = SkIntToFixed(a) + 0x8000;
553 r = SkIntToFixed(r) + 0x8000;
554 g = SkIntToFixed(g) + 0x8000;
555 b = SkIntToFixed(b) + 0x8000;
556
557 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000558 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
559 cache[kCache32Count] = SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
560 dither_fixed_to_8(r),
561 dither_fixed_to_8(g),
562 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000563 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000564 a += da;
565 r += dr;
566 g += dg;
567 b += db;
568 } while (--count != 0);
569}
570
reed@android.com41bccf52009-04-03 13:33:51 +0000571static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000572 SkASSERT((unsigned)x <= SK_Fixed1);
573 return x - (x >> 16);
574}
575
reed@android.com200645d2009-12-14 16:41:57 +0000576static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000577 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000578 if (6 == bits) {
579 return (x << 10) | (x << 4) | (x >> 2);
580 }
581 if (8 == bits) {
582 return (x << 8) | x;
583 }
584 sk_throw();
585 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000586}
587
reed@google.com7c2f27d2011-03-07 19:29:00 +0000588const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000589 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000590 // double the count for dither entries
591 const int entryCount = kCache16Count * 2;
592 const size_t allocSize = sizeof(uint16_t) * entryCount;
593
reed@android.com3c9b2a42009-08-27 19:28:37 +0000594 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000595 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000596 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000598 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000599 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000600 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601 Rec* rec = fRecs;
602 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000603 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000604 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000605 SkASSERT(nextIndex < kCache16Count);
606
607 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000608 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000609 prevIndex = nextIndex;
610 }
611 SkASSERT(prevIndex == kCache16Count - 1);
612 }
613
reed@android.com41bccf52009-04-03 13:33:51 +0000614 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000615 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616 uint16_t* linear = fCache16; // just computed linear data
617 uint16_t* mapped = fCache16Storage; // storage for mapped data
618 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000619 for (int i = 0; i < kCache16Count; i++) {
620 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000621 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000622 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623 }
624 sk_free(fCache16);
625 fCache16 = fCache16Storage;
626 }
627 }
628 return fCache16;
629}
630
reed@google.com7c2f27d2011-03-07 19:29:00 +0000631const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000632 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000633 // double the count for dither entries
634 const int entryCount = kCache32Count * 2;
635 const size_t allocSize = sizeof(SkPMColor) * entryCount;
636
reed@google.comdc731fd2010-12-23 15:19:47 +0000637 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000638 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
639 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000640 }
641 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000642 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000643 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
644 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000645 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646 Rec* rec = fRecs;
647 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000648 for (int i = 1; i < fColorCount; i++) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000649 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650 SkASSERT(nextIndex < kCache32Count);
651
652 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000653 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
654 fOrigColors[i],
655 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 prevIndex = nextIndex;
657 }
658 SkASSERT(prevIndex == kCache32Count - 1);
659 }
660
reed@android.com41bccf52009-04-03 13:33:51 +0000661 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000662 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000663 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000665 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000666 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000667 for (int i = 0; i < kCache32Count; i++) {
668 int index = map->mapUnit16((i << 8) | i) >> 8;
669 mapped[i] = linear[index];
670 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000671 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000672 fCache32PixelRef->unref();
673 fCache32PixelRef = newPR;
674 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000675 }
676 }
677 return fCache32;
678}
679
reed@google.comdc731fd2010-12-23 15:19:47 +0000680/*
681 * Because our caller might rebuild the same (logically the same) gradient
682 * over and over, we'd like to return exactly the same "bitmap" if possible,
683 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
684 * To do that, we maintain a private cache of built-bitmaps, based on our
685 * colors and positions. Note: we don't try to flatten the fMapper, so if one
686 * is present, we skip the cache for now.
687 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000688void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000689 // our caller assumes no external alpha, so we ensure that our cache is
690 // built with 0xFF
691 this->setCacheAlpha(0xFF);
692
reed@google.comdc731fd2010-12-23 15:19:47 +0000693 // don't have a way to put the mapper into our cache-key yet
694 if (fMapper) {
695 // force our cahce32pixelref to be built
696 (void)this->getCache32();
697 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
698 bitmap->setPixelRef(fCache32PixelRef);
699 return;
700 }
701
702 // build our key: [numColors + colors[] + {positions[]} ]
703 int count = 1 + fColorCount;
704 if (fColorCount > 2) {
705 count += fColorCount - 1; // fRecs[].fPos
706 }
707
708 SkAutoSTMalloc<16, int32_t> storage(count);
709 int32_t* buffer = storage.get();
710
711 *buffer++ = fColorCount;
712 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
713 buffer += fColorCount;
714 if (fColorCount > 2) {
715 for (int i = 1; i < fColorCount; i++) {
716 *buffer++ = fRecs[i].fPos;
717 }
718 }
719 SkASSERT(buffer - storage.get() == count);
720
721 ///////////////////////////////////
722
723 static SkMutex gMutex;
724 static SkBitmapCache* gCache;
725 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
726 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
727 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000728
reed@google.comdc731fd2010-12-23 15:19:47 +0000729 if (NULL == gCache) {
730 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
731 }
732 size_t size = count * sizeof(int32_t);
733
734 if (!gCache->find(storage.get(), size, bitmap)) {
735 // force our cahce32pixelref to be built
736 (void)this->getCache32();
737 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
738 bitmap->setPixelRef(fCache32PixelRef);
739
740 gCache->add(storage.get(), size, *bitmap);
741 }
742}
743
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000744void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
745 if (info) {
746 if (info->fColorCount >= fColorCount) {
747 if (info->fColors) {
748 memcpy(info->fColors, fOrigColors,
749 fColorCount * sizeof(SkColor));
750 }
751 if (info->fColorOffsets) {
752 if (fColorCount == 2) {
753 info->fColorOffsets[0] = 0;
754 info->fColorOffsets[1] = SK_Scalar1;
755 } else if (fColorCount > 2) {
756 for (int i = 0; i < fColorCount; i++)
757 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
758 }
759 }
760 }
761 info->fColorCount = fColorCount;
762 info->fTileMode = fTileMode;
763 }
764}
765
reed@google.com61eb0402011-04-15 12:11:12 +0000766///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767
reed@android.com41bccf52009-04-03 13:33:51 +0000768static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769 SkVector vec = pts[1] - pts[0];
770 SkScalar mag = vec.length();
771 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
772
773 vec.scale(inv);
774 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
775 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
776 matrix->postScale(inv, inv);
777}
778
779///////////////////////////////////////////////////////////////////////////////
780
781class Linear_Gradient : public Gradient_Shader {
782public:
783 Linear_Gradient(const SkPoint pts[2],
784 const SkColor colors[], const SkScalar pos[], int colorCount,
785 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000786 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
787 fStart(pts[0]),
788 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 {
790 pts_to_unit_matrix(pts, &fPtsToUnit);
791 }
reed@android.com9b46e772009-06-05 12:24:41 +0000792
reed@google.com7716afb2011-12-07 15:17:50 +0000793 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
794 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
795 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
796 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
797 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
798 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799
reed@google.com55b8e8c2011-01-13 16:22:35 +0000800 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 return SkNEW_ARGS(Linear_Gradient, (buffer));
802 }
803
reed@google.com7716afb2011-12-07 15:17:50 +0000804 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000805 this->INHERITED::flatten(buffer);
806 buffer.writeScalar(fStart.fX);
807 buffer.writeScalar(fStart.fY);
808 buffer.writeScalar(fEnd.fX);
809 buffer.writeScalar(fEnd.fY);
810 }
811
caryclark@google.comd26147a2011-12-15 14:16:43 +0000812 SK_DECLARE_FLATTENABLE_REGISTRAR()
813
reed@android.com8a1c16f2008-12-17 15:59:43 +0000814protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000815 Linear_Gradient(SkFlattenableReadBuffer& buffer)
816 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000817 fStart(unflatten_point(buffer)),
818 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000819 }
reed@google.com7716afb2011-12-07 15:17:50 +0000820 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000821
822private:
823 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000824 const SkPoint fStart;
825 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826};
827
reed@android.com5119bdb2009-06-12 21:27:03 +0000828bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
829 const SkMatrix& matrix) {
830 if (!this->INHERITED::setContext(device, paint, matrix)) {
831 return false;
832 }
833
834 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
835 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000836 fFlags |= SkShader::kConstInY32_Flag;
837 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
838 // only claim this if we do have a 16bit mode (i.e. none of our
839 // colors have alpha), and if we are not dithering (which obviously
840 // is not const in Y).
841 fFlags |= SkShader::kConstInY16_Flag;
842 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000843 }
844 return true;
845}
846
reed@google.com5eb158d2011-04-15 15:50:34 +0000847#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000848 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000849 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000850 SkASSERT(fi <= 0xFF); \
851 fx += dx; \
852 *dstC++ = cache[toggle + fi]; \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000853 toggle ^= Gradient_Shader::kToggleMask32; \
reed@google.com13659f12011-04-18 19:59:38 +0000854 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000855
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000856namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000857
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000858typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
859 SkPMColor* SK_RESTRICT dstC,
860 const SkPMColor* SK_RESTRICT cache,
861 int toggle, int count);
862
863void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
864 SkPMColor* SK_RESTRICT dstC,
865 const SkPMColor* SK_RESTRICT cache,
866 int toggle, int count) {
867 // we're a vertical gradient, so no change in a span
868 unsigned fi = proc(fx) >> Gradient_Shader::kCache32Shift;
869 sk_memset32_dither(dstC, cache[toggle + fi],
870 cache[(toggle ^ Gradient_Shader::kToggleMask32) + fi], count);
871
872}
873
874void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
875 SkPMColor* SK_RESTRICT dstC,
876 const SkPMColor* SK_RESTRICT cache,
877 int toggle, int count) {
878 SkClampRange range;
879 range.init(fx, dx, count, 0, 0xFF);
880
881 if ((count = range.fCount0) > 0) {
882 sk_memset32_dither(dstC,
883 cache[toggle + range.fV0],
884 cache[(toggle ^ Gradient_Shader::kToggleMask32) + range.fV0],
885 count);
886 dstC += count;
887 }
888 if ((count = range.fCount1) > 0) {
889 int unroll = count >> 3;
890 fx = range.fFx1;
891 for (int i = 0; i < unroll; i++) {
892 NO_CHECK_ITER; NO_CHECK_ITER;
893 NO_CHECK_ITER; NO_CHECK_ITER;
894 NO_CHECK_ITER; NO_CHECK_ITER;
895 NO_CHECK_ITER; NO_CHECK_ITER;
896 }
897 if ((count &= 7) > 0) {
898 do {
899 NO_CHECK_ITER;
900 } while (--count != 0);
901 }
902 }
903 if ((count = range.fCount2) > 0) {
904 sk_memset32_dither(dstC,
905 cache[toggle + range.fV1],
906 cache[(toggle ^ Gradient_Shader::kToggleMask32) + range.fV1],
907 count);
908 }
909}
910
911// TODO: we could merge mirror and repeat if we passed in a pointer to the
912// *_8bits proc, but that'd lose inlining, which might be significant here.
913void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
914 SkPMColor* SK_RESTRICT dstC,
915 const SkPMColor* SK_RESTRICT cache,
916 int toggle, int count) {
917 do {
918 unsigned fi = mirror_8bits(fx >> 8);
919 SkASSERT(fi <= 0xFF);
920 fx += dx;
921 *dstC++ = cache[toggle + fi];
922 toggle ^= Gradient_Shader::kToggleMask32;
923 } while (--count != 0);
924}
925
926void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
927 SkPMColor* SK_RESTRICT dstC,
928 const SkPMColor* SK_RESTRICT cache,
929 int toggle, int count) {
930 do {
931 unsigned fi = repeat_8bits(fx >> 8);
932 SkASSERT(fi <= 0xFF);
933 fx += dx;
934 *dstC++ = cache[toggle + fi];
935 toggle ^= Gradient_Shader::kToggleMask32;
936 } while (--count != 0);
937}
938}
939
940void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
941 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000942 SkASSERT(count > 0);
943
944 SkPoint srcPt;
945 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
946 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +0000947 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +0000948#ifdef USE_DITHER_32BIT_GRADIENT
reed@google.com55b8e8c2011-01-13 16:22:35 +0000949 int toggle = ((x ^ y) & 1) << kCache32Bits;
reed@google.com0e734bd2011-12-08 17:24:44 +0000950#else
951 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +0000952#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953
reed@android.comc552a432009-06-12 20:02:50 +0000954 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000955 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
956 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000957 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
958
reed@android.comc552a432009-06-12 20:02:50 +0000959 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000960 SkFixed dxStorage[1];
961 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
962 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000963 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
965 dx = SkScalarToFixed(fDstToIndex.getScaleX());
966 }
967
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000968 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +0000969 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000970 shadeProc = shadeSpan_linear_vertical;
reed@android.comc552a432009-06-12 20:02:50 +0000971 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000972 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +0000973 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000974 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +0000975 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000978 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +0000979 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980 SkScalar dstX = SkIntToScalar(x);
981 SkScalar dstY = SkIntToScalar(y);
982 do {
983 dstProc(fDstToIndex, dstX, dstY, &srcPt);
984 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
985 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000986 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
987 toggle ^= Gradient_Shader::kToggleMask32;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000988 dstX += SK_Scalar1;
989 } while (--count != 0);
990 }
991}
992
reed@google.com55b8e8c2011-01-13 16:22:35 +0000993SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000994 SkMatrix* matrix,
995 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000996 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000998 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000999 }
1000 if (matrix) {
1001 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
1002 matrix->preConcat(fPtsToUnit);
1003 }
1004 if (xy) {
1005 xy[0] = fTileMode;
1006 xy[1] = kClamp_TileMode;
1007 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001008 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009}
1010
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001011SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1012 if (info) {
1013 commonAsAGradient(info);
1014 info->fPoint[0] = fStart;
1015 info->fPoint[1] = fEnd;
1016 }
1017 return kLinear_GradientType;
1018}
1019
reed@android.com3c9b2a42009-08-27 19:28:37 +00001020static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1021 int count) {
1022 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023 *dst++ = value;
1024 count -= 1;
1025 SkTSwap(value, other);
1026 }
1027
1028 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001029
reed@android.com3c9b2a42009-08-27 19:28:37 +00001030 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001031 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001032 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001033}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001034
reed@google.com5eb158d2011-04-15 15:50:34 +00001035#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001036 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001037 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
1038 SkASSERT(fi <= Gradient_Shader::kCache16Mask); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001039 fx += dx; \
1040 *dstC++ = cache[toggle + fi]; \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001041 toggle ^= Gradient_Shader::kToggleMask16; \
reed@google.com13659f12011-04-18 19:59:38 +00001042 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001043
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001044namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001045
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001046typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
1047 uint16_t* SK_RESTRICT dstC,
1048 const uint16_t* SK_RESTRICT cache,
1049 int toggle, int count);
1050
1051void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1052 uint16_t* SK_RESTRICT dstC,
1053 const uint16_t* SK_RESTRICT cache,
1054 int toggle, int count) {
1055 // we're a vertical gradient, so no change in a span
1056 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
1057 SkASSERT(fi <= Gradient_Shader::kCache16Mask);
1058 dither_memset16(dstC, cache[toggle + fi],
1059 cache[(toggle ^ Gradient_Shader::kToggleMask16) + fi], count);
1060
1061}
1062
1063void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1064 uint16_t* SK_RESTRICT dstC,
1065 const uint16_t* SK_RESTRICT cache,
1066 int toggle, int count) {
1067 SkClampRange range;
1068 range.init(fx, dx, count, 0, Gradient_Shader::kCache16Mask);
1069
1070 if ((count = range.fCount0) > 0) {
1071 dither_memset16(dstC,
1072 cache[toggle + range.fV0],
1073 cache[(toggle ^ Gradient_Shader::kToggleMask16) + range.fV0],
1074 count);
1075 dstC += count;
1076 }
1077 if ((count = range.fCount1) > 0) {
1078 int unroll = count >> 3;
1079 fx = range.fFx1;
1080 for (int i = 0; i < unroll; i++) {
1081 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1082 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1083 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1084 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1085 }
1086 if ((count &= 7) > 0) {
1087 do {
1088 NO_CHECK_ITER_16;
1089 } while (--count != 0);
1090 }
1091 }
1092 if ((count = range.fCount2) > 0) {
1093 dither_memset16(dstC,
1094 cache[toggle + range.fV1],
1095 cache[(toggle ^ Gradient_Shader::kToggleMask16) + range.fV1],
1096 count);
1097 }
1098}
1099
1100void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1101 uint16_t* SK_RESTRICT dstC,
1102 const uint16_t* SK_RESTRICT cache,
1103 int toggle, int count) {
1104 do {
1105 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1106 Gradient_Shader::kCache16Bits);
1107 SkASSERT(fi <= Gradient_Shader::kCache16Mask);
1108 fx += dx;
1109 *dstC++ = cache[toggle + fi];
1110 toggle ^= Gradient_Shader::kToggleMask16;
1111 } while (--count != 0);
1112}
1113
1114void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1115 uint16_t* SK_RESTRICT dstC,
1116 const uint16_t* SK_RESTRICT cache,
1117 int toggle, int count) {
1118 SkASSERT(proc == repeat_tileproc);
1119 do {
1120 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1121 Gradient_Shader::kCache16Bits);
1122 SkASSERT(fi <= Gradient_Shader::kCache16Mask);
1123 fx += dx;
1124 *dstC++ = cache[toggle + fi];
1125 toggle ^= Gradient_Shader::kToggleMask16;
1126 } while (--count != 0);
1127}
1128}
1129
1130void Linear_Gradient::shadeSpan16(int x, int y,
1131 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001132 SkASSERT(count > 0);
1133
1134 SkPoint srcPt;
1135 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1136 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001137 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001138 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001139
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001140 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001141 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1142 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001143 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1144
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001145 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001146 SkFixed dxStorage[1];
1147 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1148 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001149 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001150 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1151 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1152 }
1153
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001154 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001155 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001156 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001157 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001158 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001159 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001160 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001161 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001163 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001164 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001165 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001166 SkScalar dstX = SkIntToScalar(x);
1167 SkScalar dstY = SkIntToScalar(y);
1168 do {
1169 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1170 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1171 SkASSERT(fi <= 0xFFFF);
1172
reed@android.com512a8762009-12-14 15:25:36 +00001173 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001174 *dstC++ = cache[toggle + index];
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001175 toggle ^= Gradient_Shader::kToggleMask16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176
1177 dstX += SK_Scalar1;
1178 } while (--count != 0);
1179 }
1180}
1181
1182///////////////////////////////////////////////////////////////////////////////
1183
1184#define kSQRT_TABLE_BITS 11
1185#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1186
1187#include "SkRadialGradient_Table.h"
1188
1189#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1190
1191#include <stdio.h>
1192
reed@google.com61eb0402011-04-15 12:11:12 +00001193void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1195
1196 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1197 SkASSERT(file);
1198 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1199
reed@google.com61eb0402011-04-15 12:11:12 +00001200 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1201 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001203 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204
1205 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1206
1207 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001208 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001209 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001210 }
1211 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001212 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001213 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 }
1215 ::fprintf(file, "};\n");
1216 ::fclose(file);
1217}
1218
1219#endif
1220
1221
reed@google.com61eb0402011-04-15 12:11:12 +00001222static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1223 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224 SkScalar inv = SkScalarInvert(radius);
1225
1226 matrix->setTranslate(-center.fX, -center.fY);
1227 matrix->postScale(inv, inv);
1228}
1229
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001230
1231namespace {
1232
1233typedef void (* RadialShade16Proc)(SkFixed fx, SkFixed dx,
1234 SkFixed fy, SkFixed dy,
1235 uint16_t* dstC, const uint16_t* SK_RESTRICT cache,
1236 int toggle, int count);
1237
1238void shadeSpan16_radial_clamp(SkFixed fx, SkFixed dx,
1239 SkFixed fy, SkFixed dy,
1240 uint16_t* dstC, const uint16_t* SK_RESTRICT cache,
1241 int toggle, int count) {
1242 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1243
1244 /* knock these down so we can pin against +- 0x7FFF, which is an
1245 immediate load, rather than 0xFFFF which is slower. This is a
1246 compromise, since it reduces our precision, but that appears
1247 to be visually OK. If we decide this is OK for all of our cases,
1248 we could (it seems) put this scale-down into fDstToIndex,
1249 to avoid having to do these extra shifts each time.
1250 */
1251 fx >>= 1;
1252 dx >>= 1;
1253 fy >>= 1;
1254 dy >>= 1;
1255 // might perform this check for the other modes,
1256 // but the win will be a smaller % of the total
1257 if (dy == 0) {
1258 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1259 fy *= fy;
1260 do {
1261 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1262 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1263 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1264 fx += dx;
1265 *dstC++ = cache[toggle +
1266 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
1267 toggle ^= Gradient_Shader::kToggleMask16;
1268 } while (--count != 0);
1269 } else {
1270 do {
1271 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1272 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1273 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1274 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1275 fx += dx;
1276 fy += dy;
1277 *dstC++ = cache[toggle +
1278 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
1279 toggle ^= Gradient_Shader::kToggleMask16;
1280 } while (--count != 0);
1281 }
1282}
1283
1284void shadeSpan16_radial_mirror(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1285 uint16_t* dstC, const uint16_t* SK_RESTRICT cache,
1286 int toggle, int count) {
1287 do {
1288 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1289 unsigned fi = mirror_tileproc(dist);
1290 SkASSERT(fi <= 0xFFFF);
1291 fx += dx;
1292 fy += dy;
1293 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
1294 toggle ^= Gradient_Shader::kToggleMask16;
1295 } while (--count != 0);
1296}
1297
1298void shadeSpan16_radial_repeat(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1299 uint16_t* dstC, const uint16_t* SK_RESTRICT cache,
1300 int toggle, int count) {
1301 do {
1302 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1303 unsigned fi = repeat_tileproc(dist);
1304 SkASSERT(fi <= 0xFFFF);
1305 fx += dx;
1306 fy += dy;
1307 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
1308 toggle ^= Gradient_Shader::kToggleMask16;
1309 } while (--count != 0);
1310}
1311
1312}
1313
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314class Radial_Gradient : public Gradient_Shader {
1315public:
1316 Radial_Gradient(const SkPoint& center, SkScalar radius,
1317 const SkColor colors[], const SkScalar pos[], int colorCount,
1318 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001319 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1320 fCenter(center),
1321 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322 {
1323 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1324 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1325
1326 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1327 }
reed@google.com61eb0402011-04-15 12:11:12 +00001328
reed@google.com8e6d9142011-12-07 15:30:34 +00001329 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count) SK_OVERRIDE;
reed@google.com7716afb2011-12-07 15:17:50 +00001330 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331 SkASSERT(count > 0);
1332
1333 SkPoint srcPt;
1334 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1335 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001336 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338
reed@android.com3c9b2a42009-08-27 19:28:37 +00001339 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001340 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1341 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1343 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1344
reed@android.com3c9b2a42009-08-27 19:28:37 +00001345 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001346 SkFixed storage[2];
1347 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1348 dx = storage[0];
1349 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001350 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001351 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1352 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1353 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1354 }
1355
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001356 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001357 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001358 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001359 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001360 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001361 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001362 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001364 (*shadeProc)(fx, dx, fy, dy, dstC, cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001365 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001366 SkScalar dstX = SkIntToScalar(x);
1367 SkScalar dstY = SkIntToScalar(y);
1368 do {
1369 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1370 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1371 SkASSERT(fi <= 0xFFFF);
1372
1373 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001374 *dstC++ = cache[toggle + index];
1375 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001376
1377 dstX += SK_Scalar1;
1378 } while (--count != 0);
1379 }
1380 }
1381
reed@google.com55b8e8c2011-01-13 16:22:35 +00001382 virtual BitmapType asABitmap(SkBitmap* bitmap,
1383 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001384 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001385 SkScalar* twoPointRadialParams)
1386 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001387 if (bitmap) {
1388 this->commonAsABitmap(bitmap);
1389 }
1390 if (matrix) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001391 matrix->setScale(SkIntToScalar(kCache32Count),
1392 SkIntToScalar(kCache32Count));
reed@google.comdc731fd2010-12-23 15:19:47 +00001393 matrix->preConcat(fPtsToUnit);
1394 }
1395 if (xy) {
1396 xy[0] = fTileMode;
1397 xy[1] = kClamp_TileMode;
1398 }
1399 return kRadial_BitmapType;
1400 }
reed@google.com7716afb2011-12-07 15:17:50 +00001401 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001402 if (info) {
1403 commonAsAGradient(info);
1404 info->fPoint[0] = fCenter;
1405 info->fRadius[0] = fRadius;
1406 }
1407 return kRadial_GradientType;
1408 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001409
reed@google.com8e6d9142011-12-07 15:30:34 +00001410 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 return SkNEW_ARGS(Radial_Gradient, (buffer));
1412 }
1413
reed@google.com7716afb2011-12-07 15:17:50 +00001414 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001415 this->INHERITED::flatten(buffer);
1416 buffer.writeScalar(fCenter.fX);
1417 buffer.writeScalar(fCenter.fY);
1418 buffer.writeScalar(fRadius);
1419 }
1420
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001422 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1423 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001424 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001425 fRadius(buffer.readScalar()) {
1426 }
reed@google.com7716afb2011-12-07 15:17:50 +00001427 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001428
1429private:
1430 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001431 const SkPoint fCenter;
1432 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433};
1434
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001435namespace {
1436
1437inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001438 // fast, overly-conservative test: checks unit square instead
1439 // of unit circle
1440 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1441 (fx <= -SK_FixedHalf && dx <= 0);
1442 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1443 (fy <= -SK_FixedHalf && dy <= 0);
1444
1445 return xClamped || yClamped;
1446}
1447
1448// Return true if (fx * fy) is always inside the unit circle
1449// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1450// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001451inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001452 int fy, int dy, int count) {
1453 SkASSERT(count > 0);
1454 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1455 return false;
1456 }
1457 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1458 return false;
1459 }
1460 fx += (count - 1) * dx;
1461 fy += (count - 1) * dy;
1462 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1463 return false;
1464 }
1465 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1466}
1467
1468#define UNPINNED_RADIAL_STEP \
1469 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001470 *dstC++ = cache[sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift]; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001471 fx += dx; \
1472 fy += dy;
1473
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001474typedef void (* RadialShadeProc)(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1475 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1476 int count, SkPoint& srcPt, float fdx, float fdy);
1477
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001478// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001479void shadeSpan_radial_clamp(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1480 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
1481 int count, SkPoint& srcPt, float fdx, float fdy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001482 // Floating point seems to be slower than fixed point,
1483 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001484 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001485 fx >>= 1;
1486 dx >>= 1;
1487 fy >>= 1;
1488 dy >>= 1;
1489 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001490 sk_memset32(dstC, cache[Gradient_Shader::kCache32Count - 1], count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001491 } else if ((count > 4) &&
1492 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1493 unsigned fi;
1494 // 4x unroll appears to be no faster than 2x unroll on Linux
1495 while (count > 1) {
1496 UNPINNED_RADIAL_STEP;
1497 UNPINNED_RADIAL_STEP;
1498 count -= 2;
1499 }
1500 if (count) {
1501 UNPINNED_RADIAL_STEP;
1502 }
1503 }
1504 else {
1505 do {
1506 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1507 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1508 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1509 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001510 *dstC++ = cache[sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001511 fx += dx;
1512 fy += dy;
1513 } while (--count != 0);
1514 }
1515}
1516
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001517void shadeSpan_radial_mirror(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1518 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
1519 int count, SkPoint& srcPt, float fdx, float fdy) {
1520#ifdef SK_USE_FLOAT_SQRT
1521 float ffx = srcPt.fX;
1522 float ffy = srcPt.fY;
1523 do {
1524 float fdist = sk_float_sqrt(ffx*ffx + ffy*ffy);
1525 unsigned fi = mirror_tileproc(SkFloatToFixed(fdist));
1526 SkASSERT(fi <= 0xFFFF);
1527 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
1528 ffx += fdx;
1529 ffy += fdy;
1530 } while (--count != 0);
1531#else
1532 do {
1533 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1534 SkFixedSquare(fy);
1535 if (magnitudeSquared < 0) // Overflow.
1536 magnitudeSquared = SK_FixedMax;
1537 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1538 unsigned fi = mirror_tileproc(dist);
1539 SkASSERT(fi <= 0xFFFF);
1540 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
1541 fx += dx;
1542 fy += dy;
1543 } while (--count != 0);
1544#endif
1545}
1546
1547void shadeSpan_radial_repeat(SkFixed fx, SkFixed dx, SkFixed fy, SkFixed dy,
1548 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
1549 int count, SkPoint& srcPt, float fdx, float fdy) {
1550 do {
1551 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1552 SkFixedSquare(fy);
1553 if (magnitudeSquared < 0) // Overflow.
1554 magnitudeSquared = SK_FixedMax;
1555 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1556 unsigned fi = repeat_tileproc(dist);
1557 SkASSERT(fi <= 0xFFFF);
1558 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
1559 fx += dx;
1560 fy += dy;
1561 } while (--count != 0);
1562}
1563}
1564
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001565void Radial_Gradient::shadeSpan(int x, int y,
1566 SkPMColor* SK_RESTRICT dstC, int count) {
1567 SkASSERT(count > 0);
1568
1569 SkPoint srcPt;
1570 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1571 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001572 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001573
1574 if (fDstToIndexClass != kPerspective_MatrixClass) {
1575 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1576 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1577 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1578 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001579 float fdx = 0;
1580 float fdy = 0;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001581
1582 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1583 SkFixed storage[2];
1584 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1585 dx = storage[0];
1586 dy = storage[1];
1587#ifdef SK_USE_FLOAT_SQRT
1588 fdx = SkFixedToFloat(storage[0]);
1589 fdy = SkFixedToFloat(storage[1]);
1590#endif
1591 } else {
1592 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1593 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1594 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1595#ifdef SK_USE_FLOAT_SQRT
1596 fdx = fDstToIndex.getScaleX();
1597 fdy = fDstToIndex.getSkewY();
1598#endif
1599 }
1600
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001601 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001602 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001603 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001604 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001605 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001606 } else {
1607 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001608 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001609 (*shadeProc)(fx, dx, fy, dy, dstC, cache, count, srcPt, fdx, fdy);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001610 } else { // perspective case
1611 SkScalar dstX = SkIntToScalar(x);
1612 SkScalar dstY = SkIntToScalar(y);
1613 do {
1614 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1615 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1616 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001617 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001618 dstX += SK_Scalar1;
1619 } while (--count != 0);
1620 }
1621}
1622
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001623/* Two-point radial gradients are specified by two circles, each with a center
1624 point and radius. The gradient can be considered to be a series of
1625 concentric circles, with the color interpolated from the start circle
1626 (at t=0) to the end circle (at t=1).
1627
1628 For each point (x, y) in the span, we want to find the
1629 interpolated circle that intersects that point. The center
1630 of the desired circle (Cx, Cy) falls at some distance t
1631 along the line segment between the start point (Sx, Sy) and
1632 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001633
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001634 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1635 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001636
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001637 The radius of the desired circle (r) is also a linear interpolation t
1638 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001639
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001640 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001641
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001642 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001643
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001644 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001645
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001646 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001647
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001648 (x - ((1 - t) * Sx + t * Ex))^2
1649 + (y - ((1 - t) * Sy + t * Ey))^2
1650 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001651
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001652 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001653
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001654 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1655 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1656 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001657
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001658 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1659
1660 [Dx^2 + Dy^2 - Dr^2)] * t^2
1661 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1662 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001663
1664 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001665 possible circles on which the point may fall. Solving for t yields
1666 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001667
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001668 If a<0, the start circle is entirely contained in the
1669 end circle, and one of the roots will be <0 or >1 (off the line
1670 segment). If a>0, the start circle falls at least partially
1671 outside the end circle (or vice versa), and the gradient
1672 defines a "tube" where a point may be on one circle (on the
1673 inside of the tube) or the other (outside of the tube). We choose
1674 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001675
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001676 In order to keep the math to within the limits of fixed point,
1677 we divide the entire quadratic by Dr^2, and replace
1678 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001679
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001680 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1681 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1682 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001683
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001684 (x' and y' are computed by appending the subtract and scale to the
1685 fDstToIndex matrix in the constructor).
1686
1687 Since the 'A' component of the quadratic is independent of x' and y', it
1688 is precomputed in the constructor. Since the 'B' component is linear in
1689 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001690 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001691 a perspective projection), it must be computed in the loop.
1692
1693*/
1694
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001695namespace {
1696
1697inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1698 SkScalar sr2d2, SkScalar foura,
1699 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001700 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001701 if (0 == foura) {
1702 return SkScalarToFixed(SkScalarDiv(-c, b));
1703 }
1704
reed@google.com84e9c082011-04-13 17:44:24 +00001705 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001706 if (discrim < 0) {
1707 discrim = -discrim;
1708 }
reed@google.com84e9c082011-04-13 17:44:24 +00001709 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1710 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001711 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001712 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001713 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001714 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001715 }
reed@google.com84e9c082011-04-13 17:44:24 +00001716 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001717}
1718
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001719typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1720 SkScalar fy, SkScalar dy,
1721 SkScalar b, SkScalar db,
1722 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
1723 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1724 int count);
1725
1726void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1727 SkScalar fy, SkScalar dy,
1728 SkScalar b, SkScalar db,
1729 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
1730 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1731 int count) {
1732 for (; count > 0; --count) {
1733 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1734 fOneOverTwoA, posRoot);
1735 SkFixed index = SkClampMax(t, 0xFFFF);
1736 SkASSERT(index <= 0xFFFF);
1737 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1738 fx += dx;
1739 fy += dy;
1740 b += db;
1741 }
1742}
1743void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1744 SkScalar fy, SkScalar dy,
1745 SkScalar b, SkScalar db,
1746 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
1747 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1748 int count) {
1749 for (; count > 0; --count) {
1750 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1751 fOneOverTwoA, posRoot);
1752 SkFixed index = mirror_tileproc(t);
1753 SkASSERT(index <= 0xFFFF);
1754 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1755 fx += dx;
1756 fy += dy;
1757 b += db;
1758 }
1759}
1760
1761void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1762 SkScalar fy, SkScalar dy,
1763 SkScalar b, SkScalar db,
1764 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
1765 SkPMColor* dstC, const SkPMColor* SK_RESTRICT cache,
1766 int count) {
1767 for (; count > 0; --count) {
1768 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1769 fOneOverTwoA, posRoot);
1770 SkFixed index = repeat_tileproc(t);
1771 SkASSERT(index <= 0xFFFF);
1772 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1773 fx += dx;
1774 fy += dy;
1775 b += db;
1776 }
1777}
1778
1779
1780
1781}
1782
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001783class Two_Point_Radial_Gradient : public Gradient_Shader {
1784public:
1785 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1786 const SkPoint& end, SkScalar endRadius,
1787 const SkColor colors[], const SkScalar pos[],
1788 int colorCount, SkShader::TileMode mode,
1789 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001790 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1791 fCenter1(start),
1792 fCenter2(end),
1793 fRadius1(startRadius),
1794 fRadius2(endRadius) {
1795 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001796 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001797
1798 virtual BitmapType asABitmap(SkBitmap* bitmap,
1799 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001800 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001801 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001802 if (bitmap) {
1803 this->commonAsABitmap(bitmap);
1804 }
1805 SkScalar diffL = 0; // just to avoid gcc warning
1806 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001807 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001808 SkScalarSquare(fDiff.fY));
1809 }
1810 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001811 if (diffL) {
1812 SkScalar invDiffL = SkScalarInvert(diffL);
1813 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1814 SkScalarMul(invDiffL, fDiff.fX));
1815 } else {
1816 matrix->reset();
1817 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001818 matrix->preConcat(fPtsToUnit);
1819 }
1820 if (xy) {
1821 xy[0] = fTileMode;
1822 xy[1] = kClamp_TileMode;
1823 }
1824 if (NULL != twoPointRadialParams) {
1825 twoPointRadialParams[0] = diffL;
1826 twoPointRadialParams[1] = fStartRadius;
1827 twoPointRadialParams[2] = fDiffRadius;
1828 }
1829 return kTwoPointRadial_BitmapType;
1830 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001831
reed@google.com8e6d9142011-12-07 15:30:34 +00001832 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001833 if (info) {
1834 commonAsAGradient(info);
1835 info->fPoint[0] = fCenter1;
1836 info->fPoint[1] = fCenter2;
1837 info->fRadius[0] = fRadius1;
1838 info->fRadius[1] = fRadius2;
1839 }
1840 return kRadial2_GradientType;
1841 }
1842
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001843 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1844 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001845 SkASSERT(count > 0);
1846
1847 // Zero difference between radii: fill with transparent black.
1848 if (fDiffRadius == 0) {
1849 sk_bzero(dstC, count * sizeof(*dstC));
1850 return;
1851 }
1852 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1853 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001854 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001855
1856 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001857 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001858 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001859 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001860 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1861 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001862 SkScalar dx, fx = srcPt.fX;
1863 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001864
reed@google.com61eb0402011-04-15 12:11:12 +00001865 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001866 SkFixed fixedX, fixedY;
1867 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1868 dx = SkFixedToScalar(fixedX);
1869 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001870 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001871 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001872 dx = fDstToIndex.getScaleX();
1873 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001874 }
reed@google.com84e9c082011-04-13 17:44:24 +00001875 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1876 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1877 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1878 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001879
1880 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00001881 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001882 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00001883 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001884 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00001885 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001886 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001887 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001888 (*shadeProc)(fx, dx, fy, dy, b, db,
1889 fSr2D2, foura, fOneOverTwoA, posRoot,
1890 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00001891 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001892 SkScalar dstX = SkIntToScalar(x);
1893 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001894 for (; count > 0; --count) {
1895 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001896 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001897 SkScalar fx = srcPt.fX;
1898 SkScalar fy = srcPt.fY;
1899 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1900 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001901 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1902 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001903 SkFixed index = proc(t);
1904 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001905 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00001906 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001907 }
1908 }
1909 }
1910
reed@android.com6c59a172009-09-22 20:24:05 +00001911 virtual bool setContext(const SkBitmap& device,
1912 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00001913 const SkMatrix& matrix) SK_OVERRIDE {
1914 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00001915 return false;
1916 }
1917
1918 // we don't have a span16 proc
1919 fFlags &= ~kHasSpan16_Flag;
1920 return true;
1921 }
1922
reed@google.com8e6d9142011-12-07 15:30:34 +00001923 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001924 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1925 }
1926
reed@google.com7716afb2011-12-07 15:17:50 +00001927 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00001928 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001929 buffer.writeScalar(fCenter1.fX);
1930 buffer.writeScalar(fCenter1.fY);
1931 buffer.writeScalar(fCenter2.fX);
1932 buffer.writeScalar(fCenter2.fY);
1933 buffer.writeScalar(fRadius1);
1934 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001935 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001936
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001937protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001938 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001939 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001940 fCenter1(unflatten_point(buffer)),
1941 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001942 fRadius1(buffer.readScalar()),
1943 fRadius2(buffer.readScalar()) {
1944 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001945 };
reed@google.com7716afb2011-12-07 15:17:50 +00001946 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001947
1948private:
1949 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001950 const SkPoint fCenter1;
1951 const SkPoint fCenter2;
1952 const SkScalar fRadius1;
1953 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001954 SkPoint fDiff;
1955 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001956
1957 void init() {
1958 fDiff = fCenter1 - fCenter2;
1959 fDiffRadius = fRadius2 - fRadius1;
1960 SkScalar inv = SkScalarInvert(fDiffRadius);
1961 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1962 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1963 fStartRadius = SkScalarMul(fRadius1, inv);
1964 fSr2D2 = SkScalarSquare(fStartRadius);
1965 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00001966 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001967
1968 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1969 fPtsToUnit.postScale(inv, inv);
1970 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001971};
1972
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973///////////////////////////////////////////////////////////////////////////////
1974
1975class Sweep_Gradient : public Gradient_Shader {
1976public:
1977 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1978 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001979 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1980 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 {
1982 fPtsToUnit.setTranslate(-cx, -cy);
1983 }
reed@google.com7716afb2011-12-07 15:17:50 +00001984 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
1985 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001986
1987 virtual BitmapType asABitmap(SkBitmap* bitmap,
1988 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001989 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00001990 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001991 if (bitmap) {
1992 this->commonAsABitmap(bitmap);
1993 }
1994 if (matrix) {
1995 *matrix = fPtsToUnit;
1996 }
1997 if (xy) {
1998 xy[0] = fTileMode;
1999 xy[1] = kClamp_TileMode;
2000 }
2001 return kSweep_BitmapType;
2002 }
2003
reed@google.com7716afb2011-12-07 15:17:50 +00002004 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002005 if (info) {
2006 commonAsAGradient(info);
2007 info->fPoint[0] = fCenter;
2008 }
2009 return kSweep_GradientType;
2010 }
2011
reed@google.com8e6d9142011-12-07 15:30:34 +00002012 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 return SkNEW_ARGS(Sweep_Gradient, (buffer));
2014 }
2015
reed@google.com7716afb2011-12-07 15:17:50 +00002016 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002017 this->INHERITED::flatten(buffer);
2018 buffer.writeScalar(fCenter.fX);
2019 buffer.writeScalar(fCenter.fY);
2020 }
2021
reed@android.com8a1c16f2008-12-17 15:59:43 +00002022protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002023 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
2024 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002025 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002026 }
2027
reed@google.com7716afb2011-12-07 15:17:50 +00002028 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029
2030private:
2031 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002032 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033};
2034
2035#ifdef COMPUTE_SWEEP_TABLE
2036#define PI 3.14159265
2037static bool gSweepTableReady;
2038static uint8_t gSweepTable[65];
2039
2040/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2041 We scale the results to [0..32]
2042*/
reed@google.com61eb0402011-04-15 12:11:12 +00002043static const uint8_t* build_sweep_table() {
2044 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045 const int N = 65;
2046 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002047
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048 for (int i = 0; i < N; i++)
2049 {
2050 double arg = i / DENOM;
2051 double v = atan(arg);
2052 int iv = (int)round(v * DENOM * 2 / PI);
2053// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2054 printf("%d, ", iv);
2055 gSweepTable[i] = iv;
2056 }
2057 gSweepTableReady = true;
2058 }
2059 return gSweepTable;
2060}
2061#else
2062static const uint8_t gSweepTable[] = {
2063 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2064 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2065 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2066 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2067 32
2068};
2069static const uint8_t* build_sweep_table() { return gSweepTable; }
2070#endif
2071
2072// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2073// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2074// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2075
2076//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002077static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002078 SkASSERT(numer <= denom);
2079 SkASSERT(numer > 0);
2080 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002081
reed@android.com8a1c16f2008-12-17 15:59:43 +00002082 int nbits = SkCLZ(numer);
2083 int dbits = SkCLZ(denom);
2084 int bits = 6 - nbits + dbits;
2085 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002086
reed@google.com61eb0402011-04-15 12:11:12 +00002087 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002089 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002090
2091 denom <<= dbits - 1;
2092 numer <<= nbits - 1;
2093
2094 unsigned result = 0;
2095
2096 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002097 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002098 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002099 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002100 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002101 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002102
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002104 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105 // make room for the rest of the answer bits
2106 result <<= bits;
2107 switch (bits) {
2108 case 6:
2109 if ((numer = (numer << 1) - denom) >= 0)
2110 result |= 32;
2111 else
2112 numer += denom;
2113 case 5:
2114 if ((numer = (numer << 1) - denom) >= 0)
2115 result |= 16;
2116 else
2117 numer += denom;
2118 case 4:
2119 if ((numer = (numer << 1) - denom) >= 0)
2120 result |= 8;
2121 else
2122 numer += denom;
2123 case 3:
2124 if ((numer = (numer << 1) - denom) >= 0)
2125 result |= 4;
2126 else
2127 numer += denom;
2128 case 2:
2129 if ((numer = (numer << 1) - denom) >= 0)
2130 result |= 2;
2131 else
2132 numer += denom;
2133 case 1:
2134 default: // not strictly need, but makes GCC make better ARM code
2135 if ((numer = (numer << 1) - denom) >= 0)
2136 result |= 1;
2137 else
2138 numer += denom;
2139 }
2140 }
2141 return result;
2142}
2143
2144// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002145static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146#ifdef SK_DEBUG
2147 {
2148 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002149 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 gOnce = true;
2151 SkASSERT(div_64(55, 55) == 64);
2152 SkASSERT(div_64(128, 256) == 32);
2153 SkASSERT(div_64(2326528, 4685824) == 31);
2154 SkASSERT(div_64(753664, 5210112) == 9);
2155 SkASSERT(div_64(229376, 4882432) == 3);
2156 SkASSERT(div_64(2, 64) == 2);
2157 SkASSERT(div_64(1, 64) == 1);
2158 // test that we handle underflow correctly
2159 SkASSERT(div_64(12345, 0x54321234) == 0);
2160 }
2161 }
2162#endif
2163
2164 SkASSERT(y > 0 && x > 0);
2165 const uint8_t* table = build_sweep_table();
2166
2167 unsigned result;
2168 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002169 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002170 // first part of the atan(v) = PI/2 - atan(1/v) identity
2171 // since our div_64 and table want v <= 1, where v = y/x
2172 SkTSwap<SkFixed>(x, y);
2173 }
2174
2175 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002176
reed@android.com8a1c16f2008-12-17 15:59:43 +00002177#ifdef SK_DEBUG
2178 {
2179 unsigned result2 = SkDivBits(y, x, 6);
2180 SkASSERT(result2 == result ||
2181 (result == 1 && result2 == 0));
2182 }
2183#endif
2184
2185 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2186 result = table[result];
2187
reed@google.com61eb0402011-04-15 12:11:12 +00002188 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002189 // complete the atan(v) = PI/2 - atan(1/v) identity
2190 result = 64 - result;
2191 // pin to 63
2192 result -= result >> 6;
2193 }
2194
2195 SkASSERT(result <= 63);
2196 return result;
2197}
2198
2199// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002200#ifdef SK_SCALAR_IS_FLOAT
2201static unsigned SkATan2_255(float y, float x) {
2202 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2203 static const float g255Over2PI = 40.584510488433314f;
2204
2205 float result = sk_float_atan2(y, x);
2206 if (result < 0) {
2207 result += 2 * SK_ScalarPI;
2208 }
2209 SkASSERT(result >= 0);
2210 // since our value is always >= 0, we can cast to int, which is faster than
2211 // calling floorf()
2212 int ir = (int)(result * g255Over2PI);
2213 SkASSERT(ir >= 0 && ir <= 255);
2214 return ir;
2215}
2216#else
reed@google.com61eb0402011-04-15 12:11:12 +00002217static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2218 if (x == 0) {
2219 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002220 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002221 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002222 return y < 0 ? 192 : 64;
2223 }
reed@google.com61eb0402011-04-15 12:11:12 +00002224 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002225 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002226 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002227
reed@android.com8a1c16f2008-12-17 15:59:43 +00002228 /* Find the right quadrant for x,y
2229 Since atan_0_90 only handles the first quadrant, we rotate x,y
2230 appropriately before calling it, and then add the right amount
2231 to account for the real quadrant.
2232 quadrant 0 : add 0 | x > 0 && y > 0
2233 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2234 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2235 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002236
reed@android.com8a1c16f2008-12-17 15:59:43 +00002237 map x<0 to (1 << 6)
2238 map y<0 to (3 << 6)
2239 add = map_x ^ map_y
2240 */
2241 int xsign = x >> 31;
2242 int ysign = y >> 31;
2243 int add = ((-xsign) ^ (ysign & 3)) << 6;
2244
2245#ifdef SK_DEBUG
2246 if (0 == add)
2247 SkASSERT(x > 0 && y > 0);
2248 else if (64 == add)
2249 SkASSERT(x < 0 && y > 0);
2250 else if (128 == add)
2251 SkASSERT(x < 0 && y < 0);
2252 else if (192 == add)
2253 SkASSERT(x > 0 && y < 0);
2254 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002255 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002256#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002257
reed@android.com8a1c16f2008-12-17 15:59:43 +00002258 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2259 where we need to rotate x,y by 90 or -90
2260 */
2261 x = (x ^ xsign) - xsign;
2262 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002263 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002264 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002265 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002266
2267 unsigned result = add + atan_0_90(y, x);
2268 SkASSERT(result < 256);
2269 return result;
2270}
reed@google.com51baf5a2011-09-21 13:38:36 +00002271#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002272
reed@google.comdd5bd672011-09-20 15:56:13 +00002273void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002274 SkMatrix::MapXYProc proc = fDstToIndexProc;
2275 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002276 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002277 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002278
reed@google.com61eb0402011-04-15 12:11:12 +00002279 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002280 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2281 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002282 SkScalar dx, fx = srcPt.fX;
2283 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002284
reed@google.com61eb0402011-04-15 12:11:12 +00002285 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002286 SkFixed storage[2];
2287 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2288 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002289 dx = SkFixedToScalar(storage[0]);
2290 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002291 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002292 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002293 dx = matrix.getScaleX();
2294 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002295 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002296
reed@google.com61eb0402011-04-15 12:11:12 +00002297 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002298 *dstC++ = cache[SkATan2_255(fy, fx)];
2299 fx += dx;
2300 fy += dy;
2301 }
reed@google.com61eb0402011-04-15 12:11:12 +00002302 } else { // perspective case
2303 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002304 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002305 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2306 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002307 }
2308 }
2309}
2310
reed@google.comdd5bd672011-09-20 15:56:13 +00002311void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002312 SkMatrix::MapXYProc proc = fDstToIndexProc;
2313 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002314 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002315 int toggle = ((x ^ y) & 1) << kCache16Bits;
2316 SkPoint srcPt;
2317
reed@google.com61eb0402011-04-15 12:11:12 +00002318 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002319 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2320 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002321 SkScalar dx, fx = srcPt.fX;
2322 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002323
reed@google.com61eb0402011-04-15 12:11:12 +00002324 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002325 SkFixed storage[2];
2326 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2327 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002328 dx = SkFixedToScalar(storage[0]);
2329 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002330 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002331 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002332 dx = matrix.getScaleX();
2333 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002334 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002335
reed@google.com61eb0402011-04-15 12:11:12 +00002336 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002337 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2338 *dstC++ = cache[toggle + index];
2339 toggle ^= (1 << kCache16Bits);
2340 fx += dx;
2341 fy += dy;
2342 }
reed@google.com61eb0402011-04-15 12:11:12 +00002343 } else { // perspective case
2344 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002345 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2346 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002347
reed@google.com51baf5a2011-09-21 13:38:36 +00002348 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002349 index >>= (8 - kCache16Bits);
2350 *dstC++ = cache[toggle + index];
2351 toggle ^= (1 << kCache16Bits);
2352 }
2353 }
2354}
2355
reed@google.com61eb0402011-04-15 12:11:12 +00002356///////////////////////////////////////////////////////////////////////////////
2357///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002358
2359// assumes colors is SkColor* and pos is SkScalar*
2360#define EXPAND_1_COLOR(count) \
2361 SkColor tmp[2]; \
2362 do { \
2363 if (1 == count) { \
2364 tmp[0] = tmp[1] = colors[0]; \
2365 colors = tmp; \
2366 pos = NULL; \
2367 count = 2; \
2368 } \
2369 } while (0)
2370
reed@google.com61eb0402011-04-15 12:11:12 +00002371SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2372 const SkColor colors[],
2373 const SkScalar pos[], int colorCount,
2374 SkShader::TileMode mode,
2375 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002376 if (NULL == pts || NULL == colors || colorCount < 1) {
2377 return NULL;
2378 }
2379 EXPAND_1_COLOR(colorCount);
2380
reed@android.comab840b82009-07-01 17:00:03 +00002381 return SkNEW_ARGS(Linear_Gradient,
2382 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002383}
2384
reed@google.com61eb0402011-04-15 12:11:12 +00002385SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2386 const SkColor colors[],
2387 const SkScalar pos[], int colorCount,
2388 SkShader::TileMode mode,
2389 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002390 if (radius <= 0 || NULL == colors || colorCount < 1) {
2391 return NULL;
2392 }
2393 EXPAND_1_COLOR(colorCount);
2394
reed@android.comab840b82009-07-01 17:00:03 +00002395 return SkNEW_ARGS(Radial_Gradient,
2396 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002397}
2398
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002399SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2400 SkScalar startRadius,
2401 const SkPoint& end,
2402 SkScalar endRadius,
2403 const SkColor colors[],
2404 const SkScalar pos[],
2405 int colorCount,
2406 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002407 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002408 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2409 return NULL;
2410 }
2411 EXPAND_1_COLOR(colorCount);
2412
2413 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002414 (start, startRadius, end, endRadius, colors, pos,
2415 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002416}
2417
reed@android.com8a1c16f2008-12-17 15:59:43 +00002418SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2419 const SkColor colors[],
2420 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002421 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002422 if (NULL == colors || count < 1) {
2423 return NULL;
2424 }
2425 EXPAND_1_COLOR(count);
2426
2427 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2428}
2429
caryclark@google.comd26147a2011-12-15 14:16:43 +00002430SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2431 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2432 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002433
caryclark@google.comd26147a2011-12-15 14:16:43 +00002434 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002435
caryclark@google.comd26147a2011-12-15 14:16:43 +00002436 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2437SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END