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