blob: 87d70c23b067e92570f0a315d8541e7815bc3739 [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/* libs/graphics/effects/SkGradientShader.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18#include "SkGradientShader.h"
19#include "SkColorPriv.h"
20#include "SkUnitMapper.h"
21#include "SkUtils.h"
22
23/*
24 ToDo
25
26 - not sure we still need the full Rec struct, now that we're using a cache
27 - detect const-alpha (but not opaque) in getFlags()
28*/
29
30/* dither seems to look better, but not stuningly yet, and it slows us down a little
31 so its not on by default yet.
32*/
33#define TEST_GRADIENT_DITHER
34
35///////////////////////////////////////////////////////////////////////////
36
37typedef SkFixed (*TileProc)(SkFixed);
38
reed@android.com41bccf52009-04-03 13:33:51 +000039static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000040 return SkClampMax(x, 0xFFFF);
41}
42
reed@android.com41bccf52009-04-03 13:33:51 +000043static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000044 return x & 0xFFFF;
45}
46
reed@android.com41bccf52009-04-03 13:33:51 +000047static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000048 int s = x << 15 >> 31;
49 return (x ^ s) & 0xFFFF;
50}
51
52static const TileProc gTileProcs[] = {
53 clamp_tileproc,
54 repeat_tileproc,
55 mirror_tileproc
56};
57
58//////////////////////////////////////////////////////////////////////////////
59
reed@android.com41bccf52009-04-03 13:33:51 +000060static inline int repeat_6bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 return x & 63;
62}
63
reed@android.com41bccf52009-04-03 13:33:51 +000064static inline int mirror_6bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000065#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
66 if (x & 64)
67 x = ~x;
68 return x & 63;
69#else
70 int s = x << 25 >> 31;
71 return (x ^ s) & 63;
72#endif
73}
74
reed@android.com41bccf52009-04-03 13:33:51 +000075static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 return x & 0xFF;
77}
78
reed@android.com41bccf52009-04-03 13:33:51 +000079static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000080#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000081 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +000083 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 return x & 255;
85#else
86 int s = x << 23 >> 31;
87 return (x ^ s) & 0xFF;
88#endif
89}
90
91//////////////////////////////////////////////////////////////////////////////
92
93class Gradient_Shader : public SkShader {
94public:
95 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +000096 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +000097 virtual ~Gradient_Shader();
98
99 // overrides
100 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
101 virtual uint32_t getFlags() { return fFlags; }
102
103protected:
104 Gradient_Shader(SkFlattenableReadBuffer& );
105 SkUnitMapper* fMapper;
106 SkMatrix fPtsToUnit; // set by subclass
107 SkMatrix fDstToIndex;
108 SkMatrix::MapXYProc fDstToIndexProc;
109 SkPMColor* fARGB32;
110 TileMode fTileMode;
111 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000112 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 uint8_t fDstToIndexClass;
114 uint8_t fFlags;
115
116 struct Rec {
117 SkFixed fPos; // 0...1
118 uint32_t fScale; // (1 << 24) / range
119 };
120 Rec* fRecs;
121
122 enum {
123 kCache16Bits = 6, // seems like enough for visual accuracy
124 kCache16Count = 1 << kCache16Bits,
125 kCache32Bits = 8, // pretty much should always be 8
126 kCache32Count = 1 << kCache32Bits
127 };
128 virtual void flatten(SkFlattenableWriteBuffer& );
129 const uint16_t* getCache16();
130 const SkPMColor* getCache32();
131
reed@android.com9b46e772009-06-05 12:24:41 +0000132 // called when we kill our cached colors (to be rebuilt later on demand)
reed@android.comec3d6e52009-06-05 14:43:55 +0000133 virtual void onCacheReset() = 0;
reed@android.com9b46e772009-06-05 12:24:41 +0000134
reed@android.com8a1c16f2008-12-17 15:59:43 +0000135private:
136 enum {
137 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
138
139 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec))
140 };
141 SkColor fStorage[(kStorageSize + 3) >> 2];
142 SkColor* fOrigColors;
143
144 uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
145 SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
146
147 uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
148 SkPMColor* fCache32Storage; // storage for fCache32, allocated on demand
149 unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
150
151 typedef SkShader INHERITED;
152};
153
reed@android.com41bccf52009-04-03 13:33:51 +0000154static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155 SkASSERT(x >= 0 && x <= SK_Scalar1);
156
157#ifdef SK_SCALAR_IS_FLOAT
158 return (unsigned)(x * 0xFFFF);
159#else
160 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
161#endif
162}
163
reed@android.com41bccf52009-04-03 13:33:51 +0000164Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
165 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166 SkASSERT(colorCount > 1);
167
168 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
169
170 fMapper = mapper;
171 mapper->safeRef();
172
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
174 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
175 fTileMode = mode;
176 fTileProc = gTileProcs[mode];
reed@android.com41bccf52009-04-03 13:33:51 +0000177
178 fCache16 = fCache16Storage = NULL;
179 fCache32 = fCache32Storage = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180
reed@android.com41bccf52009-04-03 13:33:51 +0000181 /* Note: we let the caller skip the first and/or last position.
182 i.e. pos[0] = 0.3, pos[1] = 0.7
183 In these cases, we insert dummy entries to ensure that the final data
184 will be bracketed by [0, 1].
185 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
186
187 Thus colorCount (the caller's value, and fColorCount (our value) may
188 differ by up to 2. In the above example:
189 colorCount = 2
190 fColorCount = 4
191 */
192 fColorCount = colorCount;
193 // check if we need to add in dummy start and/or end position/colors
194 bool dummyFirst = false;
195 bool dummyLast = false;
196 if (pos) {
197 dummyFirst = pos[0] != 0;
198 dummyLast = pos[colorCount - 1] != SK_Scalar1;
199 fColorCount += dummyFirst + dummyLast;
200 }
201
202 if (fColorCount > kColorStorageCount) {
203 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
204 fOrigColors = reinterpret_cast<SkColor*>(
205 sk_malloc_throw(size * fColorCount));
206 }
207 else {
208 fOrigColors = fStorage;
209 }
210
211 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 {
reed@android.com41bccf52009-04-03 13:33:51 +0000213 SkColor* origColors = fOrigColors;
214 if (dummyFirst) {
215 *origColors++ = colors[0];
216 }
217 memcpy(origColors, colors, colorCount * sizeof(SkColor));
218 if (dummyLast) {
219 origColors += colorCount;
220 *origColors = colors[colorCount - 1];
221 }
222 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223
reed@android.com41bccf52009-04-03 13:33:51 +0000224 // our premul colors point to the 2nd half of the array
225 // these are assigned each time in setContext
226 fARGB32 = fOrigColors + fColorCount;
227 fRecs = (Rec*)(fARGB32 + fColorCount);
228 if (fColorCount > 2) {
229 Rec* recs = fRecs;
230 recs->fPos = 0;
231 // recs->fScale = 0; // unused;
232 recs += 1;
233 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 /* We need to convert the user's array of relative positions into
235 fixed-point positions and scale factors. We need these results
236 to be strictly monotonic (no two values equal or out of order).
237 Hence this complex loop that just jams a zero for the scale
238 value if it sees a segment out of order, and it assures that
239 we start at 0 and end at 1.0
240 */
241 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000242 int startIndex = dummyFirst ? 0 : 1;
243 int count = colorCount + dummyLast;
244 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245 // force the last value to be 1.0
246 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000247 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000248 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000249 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000251 }
reed@android.com41bccf52009-04-03 13:33:51 +0000252 // pin curr withing range
253 if (curr < 0) {
254 curr = 0;
255 } else if (curr > SK_Fixed1) {
256 curr = SK_Fixed1;
257 }
258 recs->fPos = curr;
259 if (curr > prev) {
260 recs->fScale = (1 << 24) / (curr - prev);
261 } else {
262 recs->fScale = 0; // ignore this segment
263 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000264 // get ready for the next value
265 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000266 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000267 }
reed@android.com41bccf52009-04-03 13:33:51 +0000268 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000269 SkFixed dp = SK_Fixed1 / (colorCount - 1);
270 SkFixed p = dp;
271 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000272 for (int i = 1; i < colorCount; i++) {
273 recs->fPos = p;
274 recs->fScale = scale;
275 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000276 p += dp;
277 }
278 }
279 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000280 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000281}
282
283Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000284 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285 fCacheAlpha = 256;
286
287 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
288
289 fCache16 = fCache16Storage = NULL;
290 fCache32 = fCache32Storage = NULL;
291
reed@android.com41bccf52009-04-03 13:33:51 +0000292 int colorCount = fColorCount = buffer.readU32();
293 if (colorCount > kColorStorageCount) {
294 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
295 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
296 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000298 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
300 fARGB32 = fOrigColors + colorCount;
301
302 fTileMode = (TileMode)buffer.readU8();
303 fTileProc = gTileProcs[fTileMode];
304 fRecs = (Rec*)(fARGB32 + colorCount);
305 if (colorCount > 2) {
306 Rec* recs = fRecs;
307 recs[0].fPos = 0;
308 for (int i = 1; i < colorCount; i++) {
309 recs[i].fPos = buffer.readS32();
310 recs[i].fScale = buffer.readU32();
311 }
312 }
313 buffer.read(&fPtsToUnit, sizeof(SkMatrix));
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000314 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315}
316
reed@android.com41bccf52009-04-03 13:33:51 +0000317Gradient_Shader::~Gradient_Shader() {
318 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000320 }
321 if (fCache32Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 sk_free(fCache32Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000323 }
324 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000326 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 fMapper->safeUnref();
328}
329
reed@android.com41bccf52009-04-03 13:33:51 +0000330void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 this->INHERITED::flatten(buffer);
332 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000333 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
335 buffer.write8(fTileMode);
336 if (fColorCount > 2) {
337 Rec* recs = fRecs;
338 for (int i = 1; i < fColorCount; i++) {
339 buffer.write32(recs[i].fPos);
340 buffer.write32(recs[i].fScale);
341 }
342 }
343 buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
344}
345
346bool Gradient_Shader::setContext(const SkBitmap& device,
347 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000348 const SkMatrix& matrix) {
349 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000351 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352
353 const SkMatrix& inverse = this->getTotalInverse();
354
355 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
356 return false;
357 }
358
359 fDstToIndexProc = fDstToIndex.getMapXYProc();
360 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
361
362 // now convert our colors in to PMColors
363 unsigned paintAlpha = this->getPaintAlpha();
364 unsigned colorAlpha = 0xFF;
365
reed@android.com41bccf52009-04-03 13:33:51 +0000366 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367 SkColor src = fOrigColors[i];
368 unsigned sa = SkColorGetA(src);
369 colorAlpha &= sa;
370
371 // now modulate it by the paint for our resulting ARGB32 array
372 sa = SkMulDiv255Round(sa, paintAlpha);
373 fARGB32[i] = SkPreMultiplyARGB(sa, SkColorGetR(src), SkColorGetG(src),
374 SkColorGetB(src));
375 }
376
377 fFlags = this->INHERITED::getFlags();
378 if ((colorAlpha & paintAlpha) == 0xFF) {
379 fFlags |= kOpaqueAlpha_Flag;
380 }
381 // we can do span16 as long as our individual colors are opaque,
382 // regardless of the paint's alpha
383 if (0xFF == colorAlpha) {
384 fFlags |= kHasSpan16_Flag;
385 }
386
387 // if the new alpha differs from the previous time we were called, inval our cache
388 // this will trigger the cache to be rebuilt.
389 // we don't care about the first time, since the cache ptrs will already be NULL
390 if (fCacheAlpha != paintAlpha) {
391 fCache16 = NULL; // inval the cache
392 fCache32 = NULL; // inval the cache
393 fCacheAlpha = paintAlpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000394 // inform our subclasses
395 this->onCacheReset();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000396 }
397 return true;
398}
399
reed@android.com41bccf52009-04-03 13:33:51 +0000400static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000401 SkASSERT(a == SkToU8(a));
402 SkASSERT(b == SkToU8(b));
403 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000404 return a + ((b - a) * scale >> 8);
405}
406
reed@android.com41bccf52009-04-03 13:33:51 +0000407static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
408 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000409#if 0
410 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
411 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
412 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
413 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
414
415 return SkPackARGB32(a, r, g, b);
416#else
417 int otherBlend = 256 - blend;
418
419#if 0
420 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
421 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
422 SkASSERT((t0 & t1) == 0);
423 return t0 | t1;
424#else
425 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
426 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
427#endif
428
429#endif
430}
431
432#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
433
reed@android.com41bccf52009-04-03 13:33:51 +0000434/** We take the original colors, not our premultiplied PMColors, since we can
435 build a 16bit table as long as the original colors are opaque, even if the
436 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000437*/
reed@android.com41bccf52009-04-03 13:33:51 +0000438static void build_16bit_cache(uint16_t cache[], SkColor c0, SkColor c1,
439 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000440 SkASSERT(count > 1);
441 SkASSERT(SkColorGetA(c0) == 0xFF);
442 SkASSERT(SkColorGetA(c1) == 0xFF);
443
444 SkFixed r = SkColorGetR(c0);
445 SkFixed g = SkColorGetG(c0);
446 SkFixed b = SkColorGetB(c0);
447
448 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
449 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
450 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
451
452 r = SkIntToFixed(r) + 0x8000;
453 g = SkIntToFixed(g) + 0x8000;
454 b = SkIntToFixed(b) + 0x8000;
455
456 do {
457 unsigned rr = r >> 16;
458 unsigned gg = g >> 16;
459 unsigned bb = b >> 16;
460 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
461 cache[64] = SkDitherPack888ToRGB16(rr, gg, bb);
462 cache += 1;
463 r += dr;
464 g += dg;
465 b += db;
466 } while (--count != 0);
467}
468
reed@android.com41bccf52009-04-03 13:33:51 +0000469static void build_32bit_cache(SkPMColor cache[], SkPMColor c0, SkPMColor c1,
470 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000471 SkASSERT(count > 1);
472
473 SkFixed a = SkGetPackedA32(c0);
474 SkFixed r = SkGetPackedR32(c0);
475 SkFixed g = SkGetPackedG32(c0);
476 SkFixed b = SkGetPackedB32(c0);
477
478 SkFixed da = SkIntToFixed(SkGetPackedA32(c1) - a) / (count - 1);
479 SkFixed dr = SkIntToFixed(SkGetPackedR32(c1) - r) / (count - 1);
480 SkFixed dg = SkIntToFixed(SkGetPackedG32(c1) - g) / (count - 1);
481 SkFixed db = SkIntToFixed(SkGetPackedB32(c1) - b) / (count - 1);
482
483 a = SkIntToFixed(a) + 0x8000;
484 r = SkIntToFixed(r) + 0x8000;
485 g = SkIntToFixed(g) + 0x8000;
486 b = SkIntToFixed(b) + 0x8000;
487
488 do {
489 *cache++ = SkPackARGB32(a >> 16, r >> 16, g >> 16, b >> 16);
490 a += da;
491 r += dr;
492 g += dg;
493 b += db;
494 } while (--count != 0);
495}
496
reed@android.com41bccf52009-04-03 13:33:51 +0000497static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000498 SkASSERT((unsigned)x <= SK_Fixed1);
499 return x - (x >> 16);
500}
501
reed@android.com41bccf52009-04-03 13:33:51 +0000502static inline U16CPU dot6to16(unsigned x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000503 SkASSERT(x < 64);
504 return (x << 10) | (x << 4) | (x >> 2);
505}
506
reed@android.com41bccf52009-04-03 13:33:51 +0000507const uint16_t* Gradient_Shader::getCache16() {
508 if (fCache16 == NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000509 if (fCache16Storage == NULL) // set the storage and our working ptr
510#ifdef TEST_GRADIENT_DITHER
511 fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count * 2);
512#else
513 fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count);
514#endif
515 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000516 if (fColorCount == 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000517 build_16bit_cache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000518 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000519 Rec* rec = fRecs;
520 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000521 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache16Bits);
523 SkASSERT(nextIndex < kCache16Count);
524
525 if (nextIndex > prevIndex)
526 build_16bit_cache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
527 prevIndex = nextIndex;
528 }
529 SkASSERT(prevIndex == kCache16Count - 1);
530 }
531
reed@android.com41bccf52009-04-03 13:33:51 +0000532 if (fMapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000533#ifdef TEST_GRADIENT_DITHER
534 fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count * 2);
535#else
536 fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count);
537#endif
538 uint16_t* linear = fCache16; // just computed linear data
539 uint16_t* mapped = fCache16Storage; // storage for mapped data
540 SkUnitMapper* map = fMapper;
reed@android.com41bccf52009-04-03 13:33:51 +0000541 for (int i = 0; i < 64; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000542 int index = map->mapUnit16(dot6to16(i)) >> 10;
543 mapped[i] = linear[index];
544#ifdef TEST_GRADIENT_DITHER
545 mapped[i + 64] = linear[index + 64];
546#endif
547 }
548 sk_free(fCache16);
549 fCache16 = fCache16Storage;
550 }
551 }
552 return fCache16;
553}
554
reed@android.com41bccf52009-04-03 13:33:51 +0000555const SkPMColor* Gradient_Shader::getCache32() {
556 if (fCache32 == NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000557 if (fCache32Storage == NULL) // set the storage and our working ptr
558 fCache32Storage = (SkPMColor*)sk_malloc_throw(sizeof(SkPMColor) * kCache32Count);
559
560 fCache32 = fCache32Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000561 if (fColorCount == 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000562 build_32bit_cache(fCache32, fARGB32[0], fARGB32[1], kCache32Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000563 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000564 Rec* rec = fRecs;
565 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000566 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000567 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
568 SkASSERT(nextIndex < kCache32Count);
569
570 if (nextIndex > prevIndex)
571 build_32bit_cache(fCache32 + prevIndex, fARGB32[i-1], fARGB32[i], nextIndex - prevIndex + 1);
572 prevIndex = nextIndex;
573 }
574 SkASSERT(prevIndex == kCache32Count - 1);
575 }
576
reed@android.com41bccf52009-04-03 13:33:51 +0000577 if (fMapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578 fCache32Storage = (SkPMColor*)sk_malloc_throw(sizeof(SkPMColor) * kCache32Count);
579 SkPMColor* linear = fCache32; // just computed linear data
580 SkPMColor* mapped = fCache32Storage; // storage for mapped data
581 SkUnitMapper* map = fMapper;
reed@android.com41bccf52009-04-03 13:33:51 +0000582 for (int i = 0; i < 256; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000583 mapped[i] = linear[map->mapUnit16((i << 8) | i) >> 8];
reed@android.com41bccf52009-04-03 13:33:51 +0000584 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585 sk_free(fCache32);
586 fCache32 = fCache32Storage;
587 }
588 }
589 return fCache32;
590}
591
592///////////////////////////////////////////////////////////////////////////
593
reed@android.com41bccf52009-04-03 13:33:51 +0000594static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000595 SkVector vec = pts[1] - pts[0];
596 SkScalar mag = vec.length();
597 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
598
599 vec.scale(inv);
600 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
601 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
602 matrix->postScale(inv, inv);
603}
604
605///////////////////////////////////////////////////////////////////////////////
606
607class Linear_Gradient : public Gradient_Shader {
608public:
609 Linear_Gradient(const SkPoint pts[2],
610 const SkColor colors[], const SkScalar pos[], int colorCount,
611 SkShader::TileMode mode, SkUnitMapper* mapper)
612 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
613 {
reed@android.com9b46e772009-06-05 12:24:41 +0000614 fCachedBitmap = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615 pts_to_unit_matrix(pts, &fPtsToUnit);
616 }
reed@android.com9b46e772009-06-05 12:24:41 +0000617 virtual ~Linear_Gradient() {
618 if (fCachedBitmap) {
619 SkDELETE(fCachedBitmap);
620 }
621 }
622
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
624 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
625 virtual bool asABitmap(SkBitmap*, SkMatrix*, TileMode*);
reed@android.com9b46e772009-06-05 12:24:41 +0000626 virtual void onCacheReset() {
627 if (fCachedBitmap) {
628 SkDELETE(fCachedBitmap);
629 fCachedBitmap = NULL;
630 }
631 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000632
633 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
634 return SkNEW_ARGS(Linear_Gradient, (buffer));
635 }
636
637protected:
reed@android.comec3d6e52009-06-05 14:43:55 +0000638 Linear_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {
639 fCachedBitmap = NULL;
640 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641 virtual Factory getFactory() { return CreateProc; }
642
643private:
reed@android.com9b46e772009-06-05 12:24:41 +0000644 SkBitmap* fCachedBitmap; // allocated on demand
645
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646 typedef Gradient_Shader INHERITED;
647};
648
649// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@android.comfc25abd2009-01-15 14:38:33 +0000650static inline bool no_need_for_clamp(int fx, int dx, int count)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651{
652 SkASSERT(count > 0);
653 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
654}
655
656void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
657{
658 SkASSERT(count > 0);
659
660 SkPoint srcPt;
661 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
662 TileProc proc = fTileProc;
663 const SkPMColor* cache = this->getCache32();
664
reed@android.comc552a432009-06-12 20:02:50 +0000665 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000666 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
667 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
reed@android.comc552a432009-06-12 20:02:50 +0000668 // preround fx by half the amount we throw away
669 fx += 1 << 7;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670
reed@android.comc552a432009-06-12 20:02:50 +0000671 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672 SkFixed dxStorage[1];
673 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
674 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000675 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000676 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
677 dx = SkScalarToFixed(fDstToIndex.getScaleX());
678 }
679
reed@android.comc552a432009-06-12 20:02:50 +0000680 if (SkFixedNearlyZero(dx)) {
681 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682 unsigned fi = proc(fx);
683 SkASSERT(fi <= 0xFFFF);
684 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000685 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686#if 0
687 if (no_need_for_clamp(fx, dx, count))
688 {
689 unsigned fi;
690 while ((count -= 4) >= 0)
691 {
692 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
693 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
694 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
695 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
696 }
697 SkASSERT(count <= -1 && count >= -4);
698 count += 4;
699 while (--count >= 0)
700 {
701 fi = fx >> 8;
702 SkASSERT(fi <= 0xFF);
703 fx += dx;
704 *dstC++ = cache[fi];
705 }
706 }
707 else
708#endif
709 do {
710 unsigned fi = SkClampMax(fx >> 8, 0xFF);
711 SkASSERT(fi <= 0xFF);
712 fx += dx;
713 *dstC++ = cache[fi];
714 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000715 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 do {
717 unsigned fi = mirror_8bits(fx >> 8);
718 SkASSERT(fi <= 0xFF);
719 fx += dx;
720 *dstC++ = cache[fi];
721 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000722 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723 SkASSERT(proc == repeat_tileproc);
724 do {
725 unsigned fi = repeat_8bits(fx >> 8);
726 SkASSERT(fi <= 0xFF);
727 fx += dx;
728 *dstC++ = cache[fi];
729 } while (--count != 0);
730 }
reed@android.comc552a432009-06-12 20:02:50 +0000731 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732 SkScalar dstX = SkIntToScalar(x);
733 SkScalar dstY = SkIntToScalar(y);
734 do {
735 dstProc(fDstToIndex, dstX, dstY, &srcPt);
736 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
737 SkASSERT(fi <= 0xFFFF);
738 *dstC++ = cache[fi >> (16 - kCache32Bits)];
739 dstX += SK_Scalar1;
740 } while (--count != 0);
741 }
742}
743
744bool Linear_Gradient::asABitmap(SkBitmap* bitmap, SkMatrix* matrix,
745 TileMode xy[]) {
reed@android.com9b46e772009-06-05 12:24:41 +0000746 // we cache our "bitmap", so it's generationID will be const on subsequent
747 // calls to asABitmap
748 if (NULL == fCachedBitmap) {
749 fCachedBitmap = SkNEW(SkBitmap);
750 fCachedBitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
751 fCachedBitmap->setPixels((void*)this->getCache32(), NULL);
752 }
753
reed@android.com8a1c16f2008-12-17 15:59:43 +0000754 if (bitmap) {
reed@android.com9b46e772009-06-05 12:24:41 +0000755 *bitmap = *fCachedBitmap;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000756 }
757 if (matrix) {
758 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
759 matrix->preConcat(fPtsToUnit);
760 }
761 if (xy) {
762 xy[0] = fTileMode;
763 xy[1] = kClamp_TileMode;
764 }
765 return true;
766}
767
768#ifdef TEST_GRADIENT_DITHER
769static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other, int count)
770{
reed@android.com0becfc52009-01-13 13:26:44 +0000771 if (reinterpret_cast<uintptr_t>(dst) & 2)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772 {
773 *dst++ = value;
774 count -= 1;
775 SkTSwap(value, other);
776 }
777
778 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
779
780 if (count & 1)
781 dst[count - 1] = value;
782}
783#endif
784
785void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
786{
787 SkASSERT(count > 0);
788
789 SkPoint srcPt;
790 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
791 TileProc proc = fTileProc;
792 const uint16_t* cache = this->getCache16();
793#ifdef TEST_GRADIENT_DITHER
794 int toggle = ((x ^ y) & 1) << kCache16Bits;
795#endif
796
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000797 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
799 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000800 // preround fx by half the amount we throw away
801 fx += 1 << 7;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000803 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 SkFixed dxStorage[1];
805 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
806 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000807 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
809 dx = SkScalarToFixed(fDstToIndex.getScaleX());
810 }
811
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000812 if (SkFixedNearlyZero(dx)) {
813 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000814 unsigned fi = proc(fx) >> 10;
815 SkASSERT(fi <= 63);
816#ifdef TEST_GRADIENT_DITHER
817 dither_memset16(dstC, cache[toggle + fi], cache[(toggle ^ (1 << kCache16Bits)) + fi], count);
818#else
819 sk_memset16(dstC, cache[fi], count);
820#endif
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000821 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 do {
823 unsigned fi = SkClampMax(fx >> 10, 63);
824 SkASSERT(fi <= 63);
825 fx += dx;
826#ifdef TEST_GRADIENT_DITHER
827 *dstC++ = cache[toggle + fi];
828 toggle ^= (1 << kCache16Bits);
829#else
830 *dstC++ = cache[fi];
831#endif
832 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000833 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834 do {
835 unsigned fi = mirror_6bits(fx >> 10);
836 SkASSERT(fi <= 0x3F);
837 fx += dx;
838#ifdef TEST_GRADIENT_DITHER
839 *dstC++ = cache[toggle + fi];
840 toggle ^= (1 << kCache16Bits);
841#else
842 *dstC++ = cache[fi];
843#endif
844 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000845 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846 SkASSERT(proc == repeat_tileproc);
847 do {
848 unsigned fi = repeat_6bits(fx >> 10);
849 SkASSERT(fi <= 0x3F);
850 fx += dx;
851#ifdef TEST_GRADIENT_DITHER
852 *dstC++ = cache[toggle + fi];
853 toggle ^= (1 << kCache16Bits);
854#else
855 *dstC++ = cache[fi];
856#endif
857 } while (--count != 0);
858 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000859 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 SkScalar dstX = SkIntToScalar(x);
861 SkScalar dstY = SkIntToScalar(y);
862 do {
863 dstProc(fDstToIndex, dstX, dstY, &srcPt);
864 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
865 SkASSERT(fi <= 0xFFFF);
866
867 int index = fi >> (16 - kCache16Bits);
868#ifdef TEST_GRADIENT_DITHER
869 *dstC++ = cache[toggle + index];
870 toggle ^= (1 << kCache16Bits);
871#else
872 *dstC++ = cache[index];
873#endif
874
875 dstX += SK_Scalar1;
876 } while (--count != 0);
877 }
878}
879
880///////////////////////////////////////////////////////////////////////////////
881
882#define kSQRT_TABLE_BITS 11
883#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
884
885#include "SkRadialGradient_Table.h"
886
887#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
888
889#include <stdio.h>
890
891void SkRadialGradient_BuildTable()
892{
893 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
894
895 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
896 SkASSERT(file);
897 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
898
899 for (int i = 0; i < kSQRT_TABLE_SIZE; i++)
900 {
901 if ((i & 15) == 0)
902 ::fprintf(file, "\t");
903
904 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
905
906 ::fprintf(file, "0x%02X", value);
907 if (i < kSQRT_TABLE_SIZE-1)
908 ::fprintf(file, ", ");
909 if ((i & 15) == 15)
910 ::fprintf(file, "\n");
911 }
912 ::fprintf(file, "};\n");
913 ::fclose(file);
914}
915
916#endif
917
918
919static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius, SkMatrix* matrix)
920{
921 SkScalar inv = SkScalarInvert(radius);
922
923 matrix->setTranslate(-center.fX, -center.fY);
924 matrix->postScale(inv, inv);
925}
926
927class Radial_Gradient : public Gradient_Shader {
928public:
929 Radial_Gradient(const SkPoint& center, SkScalar radius,
930 const SkColor colors[], const SkScalar pos[], int colorCount,
931 SkShader::TileMode mode, SkUnitMapper* mapper)
932 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
933 {
934 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
935 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
936
937 rad_to_unit_matrix(center, radius, &fPtsToUnit);
938 }
939 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
940 {
941 SkASSERT(count > 0);
942
943 SkPoint srcPt;
944 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
945 TileProc proc = fTileProc;
946 const SkPMColor* cache = this->getCache32();
947
948 if (fDstToIndexClass != kPerspective_MatrixClass)
949 {
950 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
951 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
952 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
953
954 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
955 {
956 SkFixed storage[2];
957 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
958 dx = storage[0];
959 dy = storage[1];
960 }
961 else
962 {
963 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
964 dx = SkScalarToFixed(fDstToIndex.getScaleX());
965 dy = SkScalarToFixed(fDstToIndex.getSkewY());
966 }
967
968 if (proc == clamp_tileproc)
969 {
970 const uint8_t* sqrt_table = gSqrt8Table;
971 fx >>= 1;
972 dx >>= 1;
973 fy >>= 1;
974 dy >>= 1;
975 do {
976 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
977 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
978 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
979 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
980 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
981 fx += dx;
982 fy += dy;
983 } while (--count != 0);
984 }
985 else if (proc == mirror_tileproc)
986 {
987 do {
988 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
989 unsigned fi = mirror_tileproc(dist);
990 SkASSERT(fi <= 0xFFFF);
991 *dstC++ = cache[fi >> (16 - kCache32Bits)];
992 fx += dx;
993 fy += dy;
994 } while (--count != 0);
995 }
996 else
997 {
998 SkASSERT(proc == repeat_tileproc);
999 do {
1000 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1001 unsigned fi = repeat_tileproc(dist);
1002 SkASSERT(fi <= 0xFFFF);
1003 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1004 fx += dx;
1005 fy += dy;
1006 } while (--count != 0);
1007 }
1008 }
1009 else // perspective case
1010 {
1011 SkScalar dstX = SkIntToScalar(x);
1012 SkScalar dstY = SkIntToScalar(y);
1013 do {
1014 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1015 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1016 SkASSERT(fi <= 0xFFFF);
1017 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1018 dstX += SK_Scalar1;
1019 } while (--count != 0);
1020 }
1021 }
1022 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count)
1023 {
1024 SkASSERT(count > 0);
1025
1026 SkPoint srcPt;
1027 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1028 TileProc proc = fTileProc;
1029 const uint16_t* cache = this->getCache16();
1030#ifdef TEST_GRADIENT_DITHER
1031 int toggle = ((x ^ y) & 1) << kCache16Bits;
1032#endif
1033
1034 if (fDstToIndexClass != kPerspective_MatrixClass)
1035 {
1036 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
1037 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1038 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1039
1040 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1041 {
1042 SkFixed storage[2];
1043 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1044 dx = storage[0];
1045 dy = storage[1];
1046 }
1047 else
1048 {
1049 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1050 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1051 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1052 }
1053
1054 if (proc == clamp_tileproc)
1055 {
1056 const uint8_t* sqrt_table = gSqrt8Table;
1057
1058 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1059 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1060 precision, but that appears to be visually OK. If we decide this is OK for
1061 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1062 to avoid having to do these extra shifts each time.
1063 */
1064 fx >>= 1;
1065 dx >>= 1;
1066 fy >>= 1;
1067 dy >>= 1;
1068 if (dy == 0) // might perform this check for the other modes, but the win will be a smaller % of the total
1069 {
1070 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1071 fy *= fy;
1072 do {
1073 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1074 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1075 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1076 fx += dx;
1077#ifdef TEST_GRADIENT_DITHER
1078 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1079 toggle ^= (1 << kCache16Bits);
1080#else
1081 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache16Bits)];
1082#endif
1083 } while (--count != 0);
1084 }
1085 else
1086 {
1087 do {
1088 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1089 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1090 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1091 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1092 fx += dx;
1093 fy += dy;
1094#ifdef TEST_GRADIENT_DITHER
1095 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1096 toggle ^= (1 << kCache16Bits);
1097#else
1098 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache16Bits)];
1099#endif
1100 } while (--count != 0);
1101 }
1102 }
1103 else if (proc == mirror_tileproc)
1104 {
1105 do {
1106 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1107 unsigned fi = mirror_tileproc(dist);
1108 SkASSERT(fi <= 0xFFFF);
1109 fx += dx;
1110 fy += dy;
1111#ifdef TEST_GRADIENT_DITHER
1112 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1113 toggle ^= (1 << kCache16Bits);
1114#else
1115 *dstC++ = cache[fi >> (16 - kCache16Bits)];
1116#endif
1117 } while (--count != 0);
1118 }
1119 else
1120 {
1121 SkASSERT(proc == repeat_tileproc);
1122 do {
1123 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1124 unsigned fi = repeat_tileproc(dist);
1125 SkASSERT(fi <= 0xFFFF);
1126 fx += dx;
1127 fy += dy;
1128#ifdef TEST_GRADIENT_DITHER
1129 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1130 toggle ^= (1 << kCache16Bits);
1131#else
1132 *dstC++ = cache[fi >> (16 - kCache16Bits)];
1133#endif
1134 } while (--count != 0);
1135 }
1136 }
1137 else // perspective case
1138 {
1139 SkScalar dstX = SkIntToScalar(x);
1140 SkScalar dstY = SkIntToScalar(y);
1141 do {
1142 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1143 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1144 SkASSERT(fi <= 0xFFFF);
1145
1146 int index = fi >> (16 - kCache16Bits);
1147#ifdef TEST_GRADIENT_DITHER
1148 *dstC++ = cache[toggle + index];
1149 toggle ^= (1 << kCache16Bits);
1150#else
1151 *dstC++ = cache[index];
1152#endif
1153
1154 dstX += SK_Scalar1;
1155 } while (--count != 0);
1156 }
1157 }
1158
1159 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1160 return SkNEW_ARGS(Radial_Gradient, (buffer));
1161 }
1162
1163protected:
1164 Radial_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {};
1165 virtual Factory getFactory() { return CreateProc; }
reed@android.comec3d6e52009-06-05 14:43:55 +00001166 virtual void onCacheReset() {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001167
1168private:
1169 typedef Gradient_Shader INHERITED;
1170};
1171
1172///////////////////////////////////////////////////////////////////////////////
1173
1174class Sweep_Gradient : public Gradient_Shader {
1175public:
1176 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1177 const SkScalar pos[], int count, SkUnitMapper* mapper)
1178 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper)
1179 {
1180 fPtsToUnit.setTranslate(-cx, -cy);
1181 }
1182 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1183 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
1184
1185 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1186 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1187 }
1188
1189protected:
1190 Sweep_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001191 virtual Factory getFactory() { return CreateProc; }
reed@android.comec3d6e52009-06-05 14:43:55 +00001192 virtual void onCacheReset() {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001193
1194private:
1195 typedef Gradient_Shader INHERITED;
1196};
1197
1198#ifdef COMPUTE_SWEEP_TABLE
1199#define PI 3.14159265
1200static bool gSweepTableReady;
1201static uint8_t gSweepTable[65];
1202
1203/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1204 We scale the results to [0..32]
1205*/
1206static const uint8_t* build_sweep_table()
1207{
1208 if (!gSweepTableReady)
1209 {
1210 const int N = 65;
1211 const double DENOM = N - 1;
1212
1213 for (int i = 0; i < N; i++)
1214 {
1215 double arg = i / DENOM;
1216 double v = atan(arg);
1217 int iv = (int)round(v * DENOM * 2 / PI);
1218// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1219 printf("%d, ", iv);
1220 gSweepTable[i] = iv;
1221 }
1222 gSweepTableReady = true;
1223 }
1224 return gSweepTable;
1225}
1226#else
1227static const uint8_t gSweepTable[] = {
1228 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1229 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1230 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1231 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1232 32
1233};
1234static const uint8_t* build_sweep_table() { return gSweepTable; }
1235#endif
1236
1237// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1238// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1239// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1240
1241//unsigned div_64(int numer, int denom);
1242static unsigned div_64(int numer, int denom)
1243{
1244 SkASSERT(numer <= denom);
1245 SkASSERT(numer > 0);
1246 SkASSERT(denom > 0);
1247
1248 int nbits = SkCLZ(numer);
1249 int dbits = SkCLZ(denom);
1250 int bits = 6 - nbits + dbits;
1251 SkASSERT(bits <= 6);
1252
1253 if (bits < 0) // detect underflow
1254 return 0;
1255
1256 denom <<= dbits - 1;
1257 numer <<= nbits - 1;
1258
1259 unsigned result = 0;
1260
1261 // do the first one
1262 if ((numer -= denom) >= 0)
1263 result = 1;
1264 else
1265 numer += denom;
1266
1267 // Now fall into our switch statement if there are more bits to compute
1268 if (bits > 0)
1269 {
1270 // make room for the rest of the answer bits
1271 result <<= bits;
1272 switch (bits) {
1273 case 6:
1274 if ((numer = (numer << 1) - denom) >= 0)
1275 result |= 32;
1276 else
1277 numer += denom;
1278 case 5:
1279 if ((numer = (numer << 1) - denom) >= 0)
1280 result |= 16;
1281 else
1282 numer += denom;
1283 case 4:
1284 if ((numer = (numer << 1) - denom) >= 0)
1285 result |= 8;
1286 else
1287 numer += denom;
1288 case 3:
1289 if ((numer = (numer << 1) - denom) >= 0)
1290 result |= 4;
1291 else
1292 numer += denom;
1293 case 2:
1294 if ((numer = (numer << 1) - denom) >= 0)
1295 result |= 2;
1296 else
1297 numer += denom;
1298 case 1:
1299 default: // not strictly need, but makes GCC make better ARM code
1300 if ((numer = (numer << 1) - denom) >= 0)
1301 result |= 1;
1302 else
1303 numer += denom;
1304 }
1305 }
1306 return result;
1307}
1308
1309// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1310static unsigned atan_0_90(SkFixed y, SkFixed x)
1311{
1312#ifdef SK_DEBUG
1313 {
1314 static bool gOnce;
1315 if (!gOnce)
1316 {
1317 gOnce = true;
1318 SkASSERT(div_64(55, 55) == 64);
1319 SkASSERT(div_64(128, 256) == 32);
1320 SkASSERT(div_64(2326528, 4685824) == 31);
1321 SkASSERT(div_64(753664, 5210112) == 9);
1322 SkASSERT(div_64(229376, 4882432) == 3);
1323 SkASSERT(div_64(2, 64) == 2);
1324 SkASSERT(div_64(1, 64) == 1);
1325 // test that we handle underflow correctly
1326 SkASSERT(div_64(12345, 0x54321234) == 0);
1327 }
1328 }
1329#endif
1330
1331 SkASSERT(y > 0 && x > 0);
1332 const uint8_t* table = build_sweep_table();
1333
1334 unsigned result;
1335 bool swap = (x < y);
1336 if (swap)
1337 {
1338 // first part of the atan(v) = PI/2 - atan(1/v) identity
1339 // since our div_64 and table want v <= 1, where v = y/x
1340 SkTSwap<SkFixed>(x, y);
1341 }
1342
1343 result = div_64(y, x);
1344
1345#ifdef SK_DEBUG
1346 {
1347 unsigned result2 = SkDivBits(y, x, 6);
1348 SkASSERT(result2 == result ||
1349 (result == 1 && result2 == 0));
1350 }
1351#endif
1352
1353 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1354 result = table[result];
1355
1356 if (swap)
1357 {
1358 // complete the atan(v) = PI/2 - atan(1/v) identity
1359 result = 64 - result;
1360 // pin to 63
1361 result -= result >> 6;
1362 }
1363
1364 SkASSERT(result <= 63);
1365 return result;
1366}
1367
1368// returns angle in a circle [0..2PI) -> [0..255]
1369static unsigned SkATan2_255(SkFixed y, SkFixed x)
1370{
1371 if (x == 0)
1372 {
1373 if (y == 0)
1374 return 0;
1375 return y < 0 ? 192 : 64;
1376 }
1377 if (y == 0)
1378 return x < 0 ? 128 : 0;
1379
1380 /* Find the right quadrant for x,y
1381 Since atan_0_90 only handles the first quadrant, we rotate x,y
1382 appropriately before calling it, and then add the right amount
1383 to account for the real quadrant.
1384 quadrant 0 : add 0 | x > 0 && y > 0
1385 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1386 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1387 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
1388
1389 map x<0 to (1 << 6)
1390 map y<0 to (3 << 6)
1391 add = map_x ^ map_y
1392 */
1393 int xsign = x >> 31;
1394 int ysign = y >> 31;
1395 int add = ((-xsign) ^ (ysign & 3)) << 6;
1396
1397#ifdef SK_DEBUG
1398 if (0 == add)
1399 SkASSERT(x > 0 && y > 0);
1400 else if (64 == add)
1401 SkASSERT(x < 0 && y > 0);
1402 else if (128 == add)
1403 SkASSERT(x < 0 && y < 0);
1404 else if (192 == add)
1405 SkASSERT(x > 0 && y < 0);
1406 else
1407 SkASSERT(!"bad value for add");
1408#endif
1409
1410 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1411 where we need to rotate x,y by 90 or -90
1412 */
1413 x = (x ^ xsign) - xsign;
1414 y = (y ^ ysign) - ysign;
1415 if (add & 64) // quads 1 or 3 need to swap x,y
1416 SkTSwap<SkFixed>(x, y);
1417
1418 unsigned result = add + atan_0_90(y, x);
1419 SkASSERT(result < 256);
1420 return result;
1421}
1422
1423void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1424{
1425 SkMatrix::MapXYProc proc = fDstToIndexProc;
1426 const SkMatrix& matrix = fDstToIndex;
1427 const SkPMColor* cache = this->getCache32();
1428 SkPoint srcPt;
1429
1430 if (fDstToIndexClass != kPerspective_MatrixClass)
1431 {
1432 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1433 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1434 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1435 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1436
1437 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1438 {
1439 SkFixed storage[2];
1440 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1441 &storage[0], &storage[1]);
1442 dx = storage[0];
1443 dy = storage[1];
1444 }
1445 else
1446 {
1447 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1448 dx = SkScalarToFixed(matrix.getScaleX());
1449 dy = SkScalarToFixed(matrix.getSkewY());
1450 }
1451
1452 for (; count > 0; --count)
1453 {
1454 *dstC++ = cache[SkATan2_255(fy, fx)];
1455 fx += dx;
1456 fy += dy;
1457 }
1458 }
1459 else // perspective case
1460 {
1461 for (int stop = x + count; x < stop; x++)
1462 {
1463 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1464 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1465
1466 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1467 SkScalarToFixed(srcPt.fX));
1468 *dstC++ = cache[index];
1469 }
1470 }
1471}
1472
1473void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1474{
1475 SkMatrix::MapXYProc proc = fDstToIndexProc;
1476 const SkMatrix& matrix = fDstToIndex;
1477 const uint16_t* cache = this->getCache16();
1478 int toggle = ((x ^ y) & 1) << kCache16Bits;
1479 SkPoint srcPt;
1480
1481 if (fDstToIndexClass != kPerspective_MatrixClass)
1482 {
1483 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1484 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1485 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1486 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1487
1488 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1489 {
1490 SkFixed storage[2];
1491 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1492 &storage[0], &storage[1]);
1493 dx = storage[0];
1494 dy = storage[1];
1495 }
1496 else
1497 {
1498 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1499 dx = SkScalarToFixed(matrix.getScaleX());
1500 dy = SkScalarToFixed(matrix.getSkewY());
1501 }
1502
1503 for (; count > 0; --count)
1504 {
1505 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1506 *dstC++ = cache[toggle + index];
1507 toggle ^= (1 << kCache16Bits);
1508 fx += dx;
1509 fy += dy;
1510 }
1511 }
1512 else // perspective case
1513 {
1514 for (int stop = x + count; x < stop; x++)
1515 {
1516 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1517 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1518
1519 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1520 SkScalarToFixed(srcPt.fX));
1521 index >>= (8 - kCache16Bits);
1522 *dstC++ = cache[toggle + index];
1523 toggle ^= (1 << kCache16Bits);
1524 }
1525 }
1526}
1527
1528///////////////////////////////////////////////////////////////////////////
1529///////////////////////////////////////////////////////////////////////////
1530
1531// assumes colors is SkColor* and pos is SkScalar*
1532#define EXPAND_1_COLOR(count) \
1533 SkColor tmp[2]; \
1534 do { \
1535 if (1 == count) { \
1536 tmp[0] = tmp[1] = colors[0]; \
1537 colors = tmp; \
1538 pos = NULL; \
1539 count = 2; \
1540 } \
1541 } while (0)
1542
1543SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
1544 const SkColor colors[], const SkScalar pos[], int colorCount,
1545 SkShader::TileMode mode, SkUnitMapper* mapper)
1546{
1547 if (NULL == pts || NULL == colors || colorCount < 1) {
1548 return NULL;
1549 }
1550 EXPAND_1_COLOR(colorCount);
1551
reed@android.com41bccf52009-04-03 13:33:51 +00001552 SkScalar posStorage[2];
1553 if (colorCount == 2 && pos == NULL) {
1554 posStorage[0] = SK_Scalar1/4;
1555 posStorage[1] = 3*SK_Scalar1/4;
1556 pos = posStorage;
1557 }
1558
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 return SkNEW_ARGS(Linear_Gradient, (pts, colors, pos, colorCount, mode, mapper));
1560}
1561
1562SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
1563 const SkColor colors[], const SkScalar pos[], int colorCount,
1564 SkShader::TileMode mode, SkUnitMapper* mapper)
1565{
1566 if (radius <= 0 || NULL == colors || colorCount < 1) {
1567 return NULL;
1568 }
1569 EXPAND_1_COLOR(colorCount);
1570
1571 return SkNEW_ARGS(Radial_Gradient, (center, radius, colors, pos, colorCount, mode, mapper));
1572}
1573
1574SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
1575 const SkColor colors[],
1576 const SkScalar pos[],
1577 int count, SkUnitMapper* mapper)
1578{
1579 if (NULL == colors || count < 1) {
1580 return NULL;
1581 }
1582 EXPAND_1_COLOR(count);
1583
1584 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
1585}
1586
1587static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
1588 Linear_Gradient::CreateProc);
1589
1590static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
1591 Radial_Gradient::CreateProc);
1592
1593static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
1594 Sweep_Gradient::CreateProc);
1595