blob: 0891a37dd404410c8c2835f7d72ea4f04489bf4c [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.com5119bdb2009-06-12 21:27:03 +0000623 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
625 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
626 virtual bool asABitmap(SkBitmap*, SkMatrix*, TileMode*);
reed@android.com9b46e772009-06-05 12:24:41 +0000627 virtual void onCacheReset() {
628 if (fCachedBitmap) {
629 SkDELETE(fCachedBitmap);
630 fCachedBitmap = NULL;
631 }
632 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633
634 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
635 return SkNEW_ARGS(Linear_Gradient, (buffer));
636 }
637
638protected:
reed@android.comec3d6e52009-06-05 14:43:55 +0000639 Linear_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {
640 fCachedBitmap = NULL;
641 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642 virtual Factory getFactory() { return CreateProc; }
643
644private:
reed@android.com9b46e772009-06-05 12:24:41 +0000645 SkBitmap* fCachedBitmap; // allocated on demand
646
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 typedef Gradient_Shader INHERITED;
648};
649
reed@android.com5119bdb2009-06-12 21:27:03 +0000650bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
651 const SkMatrix& matrix) {
652 if (!this->INHERITED::setContext(device, paint, matrix)) {
653 return false;
654 }
655
656 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
657 if ((fDstToIndex.getType() & ~mask) == 0) {
658 fFlags |= SkShader::kConstInY_Flag;
659 }
660 return true;
661}
662
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@android.comfc25abd2009-01-15 14:38:33 +0000664static inline bool no_need_for_clamp(int fx, int dx, int count)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665{
666 SkASSERT(count > 0);
667 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
668}
669
670void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
671{
672 SkASSERT(count > 0);
673
674 SkPoint srcPt;
675 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
676 TileProc proc = fTileProc;
677 const SkPMColor* cache = this->getCache32();
678
reed@android.comc552a432009-06-12 20:02:50 +0000679 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
681 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
reed@android.comc552a432009-06-12 20:02:50 +0000682 // preround fx by half the amount we throw away
683 fx += 1 << 7;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684
reed@android.comc552a432009-06-12 20:02:50 +0000685 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 SkFixed dxStorage[1];
687 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
688 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000689 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
691 dx = SkScalarToFixed(fDstToIndex.getScaleX());
692 }
693
reed@android.comc552a432009-06-12 20:02:50 +0000694 if (SkFixedNearlyZero(dx)) {
695 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 unsigned fi = proc(fx);
697 SkASSERT(fi <= 0xFFFF);
698 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000699 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700#if 0
701 if (no_need_for_clamp(fx, dx, count))
702 {
703 unsigned fi;
704 while ((count -= 4) >= 0)
705 {
706 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
707 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
708 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
709 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
710 }
711 SkASSERT(count <= -1 && count >= -4);
712 count += 4;
713 while (--count >= 0)
714 {
715 fi = fx >> 8;
716 SkASSERT(fi <= 0xFF);
717 fx += dx;
718 *dstC++ = cache[fi];
719 }
720 }
721 else
722#endif
723 do {
724 unsigned fi = SkClampMax(fx >> 8, 0xFF);
725 SkASSERT(fi <= 0xFF);
726 fx += dx;
727 *dstC++ = cache[fi];
728 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000729 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000730 do {
731 unsigned fi = mirror_8bits(fx >> 8);
732 SkASSERT(fi <= 0xFF);
733 fx += dx;
734 *dstC++ = cache[fi];
735 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000736 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737 SkASSERT(proc == repeat_tileproc);
738 do {
739 unsigned fi = repeat_8bits(fx >> 8);
740 SkASSERT(fi <= 0xFF);
741 fx += dx;
742 *dstC++ = cache[fi];
743 } while (--count != 0);
744 }
reed@android.comc552a432009-06-12 20:02:50 +0000745 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746 SkScalar dstX = SkIntToScalar(x);
747 SkScalar dstY = SkIntToScalar(y);
748 do {
749 dstProc(fDstToIndex, dstX, dstY, &srcPt);
750 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
751 SkASSERT(fi <= 0xFFFF);
752 *dstC++ = cache[fi >> (16 - kCache32Bits)];
753 dstX += SK_Scalar1;
754 } while (--count != 0);
755 }
756}
757
758bool Linear_Gradient::asABitmap(SkBitmap* bitmap, SkMatrix* matrix,
759 TileMode xy[]) {
reed@android.com9b46e772009-06-05 12:24:41 +0000760 // we cache our "bitmap", so it's generationID will be const on subsequent
761 // calls to asABitmap
762 if (NULL == fCachedBitmap) {
763 fCachedBitmap = SkNEW(SkBitmap);
764 fCachedBitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
765 fCachedBitmap->setPixels((void*)this->getCache32(), NULL);
766 }
767
reed@android.com8a1c16f2008-12-17 15:59:43 +0000768 if (bitmap) {
reed@android.com9b46e772009-06-05 12:24:41 +0000769 *bitmap = *fCachedBitmap;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770 }
771 if (matrix) {
772 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
773 matrix->preConcat(fPtsToUnit);
774 }
775 if (xy) {
776 xy[0] = fTileMode;
777 xy[1] = kClamp_TileMode;
778 }
779 return true;
780}
781
782#ifdef TEST_GRADIENT_DITHER
783static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other, int count)
784{
reed@android.com0becfc52009-01-13 13:26:44 +0000785 if (reinterpret_cast<uintptr_t>(dst) & 2)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786 {
787 *dst++ = value;
788 count -= 1;
789 SkTSwap(value, other);
790 }
791
792 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
793
794 if (count & 1)
795 dst[count - 1] = value;
796}
797#endif
798
799void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
800{
801 SkASSERT(count > 0);
802
803 SkPoint srcPt;
804 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
805 TileProc proc = fTileProc;
806 const uint16_t* cache = this->getCache16();
807#ifdef TEST_GRADIENT_DITHER
808 int toggle = ((x ^ y) & 1) << kCache16Bits;
809#endif
810
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000811 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
813 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000814 // preround fx by half the amount we throw away
815 fx += 1 << 7;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000817 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000818 SkFixed dxStorage[1];
819 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
820 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000821 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
823 dx = SkScalarToFixed(fDstToIndex.getScaleX());
824 }
825
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000826 if (SkFixedNearlyZero(dx)) {
827 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828 unsigned fi = proc(fx) >> 10;
829 SkASSERT(fi <= 63);
830#ifdef TEST_GRADIENT_DITHER
831 dither_memset16(dstC, cache[toggle + fi], cache[(toggle ^ (1 << kCache16Bits)) + fi], count);
832#else
833 sk_memset16(dstC, cache[fi], count);
834#endif
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000835 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836 do {
837 unsigned fi = SkClampMax(fx >> 10, 63);
838 SkASSERT(fi <= 63);
839 fx += dx;
840#ifdef TEST_GRADIENT_DITHER
841 *dstC++ = cache[toggle + fi];
842 toggle ^= (1 << kCache16Bits);
843#else
844 *dstC++ = cache[fi];
845#endif
846 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000847 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848 do {
849 unsigned fi = mirror_6bits(fx >> 10);
850 SkASSERT(fi <= 0x3F);
851 fx += dx;
852#ifdef TEST_GRADIENT_DITHER
853 *dstC++ = cache[toggle + fi];
854 toggle ^= (1 << kCache16Bits);
855#else
856 *dstC++ = cache[fi];
857#endif
858 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000859 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 SkASSERT(proc == repeat_tileproc);
861 do {
862 unsigned fi = repeat_6bits(fx >> 10);
863 SkASSERT(fi <= 0x3F);
864 fx += dx;
865#ifdef TEST_GRADIENT_DITHER
866 *dstC++ = cache[toggle + fi];
867 toggle ^= (1 << kCache16Bits);
868#else
869 *dstC++ = cache[fi];
870#endif
871 } while (--count != 0);
872 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000873 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874 SkScalar dstX = SkIntToScalar(x);
875 SkScalar dstY = SkIntToScalar(y);
876 do {
877 dstProc(fDstToIndex, dstX, dstY, &srcPt);
878 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
879 SkASSERT(fi <= 0xFFFF);
880
881 int index = fi >> (16 - kCache16Bits);
882#ifdef TEST_GRADIENT_DITHER
883 *dstC++ = cache[toggle + index];
884 toggle ^= (1 << kCache16Bits);
885#else
886 *dstC++ = cache[index];
887#endif
888
889 dstX += SK_Scalar1;
890 } while (--count != 0);
891 }
892}
893
894///////////////////////////////////////////////////////////////////////////////
895
896#define kSQRT_TABLE_BITS 11
897#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
898
899#include "SkRadialGradient_Table.h"
900
901#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
902
903#include <stdio.h>
904
905void SkRadialGradient_BuildTable()
906{
907 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
908
909 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
910 SkASSERT(file);
911 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
912
913 for (int i = 0; i < kSQRT_TABLE_SIZE; i++)
914 {
915 if ((i & 15) == 0)
916 ::fprintf(file, "\t");
917
918 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
919
920 ::fprintf(file, "0x%02X", value);
921 if (i < kSQRT_TABLE_SIZE-1)
922 ::fprintf(file, ", ");
923 if ((i & 15) == 15)
924 ::fprintf(file, "\n");
925 }
926 ::fprintf(file, "};\n");
927 ::fclose(file);
928}
929
930#endif
931
932
933static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius, SkMatrix* matrix)
934{
935 SkScalar inv = SkScalarInvert(radius);
936
937 matrix->setTranslate(-center.fX, -center.fY);
938 matrix->postScale(inv, inv);
939}
940
941class Radial_Gradient : public Gradient_Shader {
942public:
943 Radial_Gradient(const SkPoint& center, SkScalar radius,
944 const SkColor colors[], const SkScalar pos[], int colorCount,
945 SkShader::TileMode mode, SkUnitMapper* mapper)
946 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
947 {
948 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
949 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
950
951 rad_to_unit_matrix(center, radius, &fPtsToUnit);
952 }
953 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
954 {
955 SkASSERT(count > 0);
956
957 SkPoint srcPt;
958 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
959 TileProc proc = fTileProc;
960 const SkPMColor* cache = this->getCache32();
961
962 if (fDstToIndexClass != kPerspective_MatrixClass)
963 {
964 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
965 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
966 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
967
968 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
969 {
970 SkFixed storage[2];
971 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
972 dx = storage[0];
973 dy = storage[1];
974 }
975 else
976 {
977 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
978 dx = SkScalarToFixed(fDstToIndex.getScaleX());
979 dy = SkScalarToFixed(fDstToIndex.getSkewY());
980 }
981
982 if (proc == clamp_tileproc)
983 {
984 const uint8_t* sqrt_table = gSqrt8Table;
985 fx >>= 1;
986 dx >>= 1;
987 fy >>= 1;
988 dy >>= 1;
989 do {
990 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
991 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
992 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
993 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
994 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
995 fx += dx;
996 fy += dy;
997 } while (--count != 0);
998 }
999 else if (proc == mirror_tileproc)
1000 {
1001 do {
1002 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1003 unsigned fi = mirror_tileproc(dist);
1004 SkASSERT(fi <= 0xFFFF);
1005 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1006 fx += dx;
1007 fy += dy;
1008 } while (--count != 0);
1009 }
1010 else
1011 {
1012 SkASSERT(proc == repeat_tileproc);
1013 do {
1014 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1015 unsigned fi = repeat_tileproc(dist);
1016 SkASSERT(fi <= 0xFFFF);
1017 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1018 fx += dx;
1019 fy += dy;
1020 } while (--count != 0);
1021 }
1022 }
1023 else // perspective case
1024 {
1025 SkScalar dstX = SkIntToScalar(x);
1026 SkScalar dstY = SkIntToScalar(y);
1027 do {
1028 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1029 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1030 SkASSERT(fi <= 0xFFFF);
1031 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1032 dstX += SK_Scalar1;
1033 } while (--count != 0);
1034 }
1035 }
1036 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count)
1037 {
1038 SkASSERT(count > 0);
1039
1040 SkPoint srcPt;
1041 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1042 TileProc proc = fTileProc;
1043 const uint16_t* cache = this->getCache16();
1044#ifdef TEST_GRADIENT_DITHER
1045 int toggle = ((x ^ y) & 1) << kCache16Bits;
1046#endif
1047
1048 if (fDstToIndexClass != kPerspective_MatrixClass)
1049 {
1050 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
1051 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1052 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1053
1054 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1055 {
1056 SkFixed storage[2];
1057 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1058 dx = storage[0];
1059 dy = storage[1];
1060 }
1061 else
1062 {
1063 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1064 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1065 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1066 }
1067
1068 if (proc == clamp_tileproc)
1069 {
1070 const uint8_t* sqrt_table = gSqrt8Table;
1071
1072 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1073 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1074 precision, but that appears to be visually OK. If we decide this is OK for
1075 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1076 to avoid having to do these extra shifts each time.
1077 */
1078 fx >>= 1;
1079 dx >>= 1;
1080 fy >>= 1;
1081 dy >>= 1;
1082 if (dy == 0) // might perform this check for the other modes, but the win will be a smaller % of the total
1083 {
1084 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1085 fy *= fy;
1086 do {
1087 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1088 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1089 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1090 fx += dx;
1091#ifdef TEST_GRADIENT_DITHER
1092 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1093 toggle ^= (1 << kCache16Bits);
1094#else
1095 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache16Bits)];
1096#endif
1097 } while (--count != 0);
1098 }
1099 else
1100 {
1101 do {
1102 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1103 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1104 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1105 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1106 fx += dx;
1107 fy += dy;
1108#ifdef TEST_GRADIENT_DITHER
1109 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1110 toggle ^= (1 << kCache16Bits);
1111#else
1112 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache16Bits)];
1113#endif
1114 } while (--count != 0);
1115 }
1116 }
1117 else if (proc == mirror_tileproc)
1118 {
1119 do {
1120 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1121 unsigned fi = mirror_tileproc(dist);
1122 SkASSERT(fi <= 0xFFFF);
1123 fx += dx;
1124 fy += dy;
1125#ifdef TEST_GRADIENT_DITHER
1126 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1127 toggle ^= (1 << kCache16Bits);
1128#else
1129 *dstC++ = cache[fi >> (16 - kCache16Bits)];
1130#endif
1131 } while (--count != 0);
1132 }
1133 else
1134 {
1135 SkASSERT(proc == repeat_tileproc);
1136 do {
1137 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1138 unsigned fi = repeat_tileproc(dist);
1139 SkASSERT(fi <= 0xFFFF);
1140 fx += dx;
1141 fy += dy;
1142#ifdef TEST_GRADIENT_DITHER
1143 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1144 toggle ^= (1 << kCache16Bits);
1145#else
1146 *dstC++ = cache[fi >> (16 - kCache16Bits)];
1147#endif
1148 } while (--count != 0);
1149 }
1150 }
1151 else // perspective case
1152 {
1153 SkScalar dstX = SkIntToScalar(x);
1154 SkScalar dstY = SkIntToScalar(y);
1155 do {
1156 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1157 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1158 SkASSERT(fi <= 0xFFFF);
1159
1160 int index = fi >> (16 - kCache16Bits);
1161#ifdef TEST_GRADIENT_DITHER
1162 *dstC++ = cache[toggle + index];
1163 toggle ^= (1 << kCache16Bits);
1164#else
1165 *dstC++ = cache[index];
1166#endif
1167
1168 dstX += SK_Scalar1;
1169 } while (--count != 0);
1170 }
1171 }
1172
1173 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1174 return SkNEW_ARGS(Radial_Gradient, (buffer));
1175 }
1176
1177protected:
1178 Radial_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {};
1179 virtual Factory getFactory() { return CreateProc; }
reed@android.comec3d6e52009-06-05 14:43:55 +00001180 virtual void onCacheReset() {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001181
1182private:
1183 typedef Gradient_Shader INHERITED;
1184};
1185
1186///////////////////////////////////////////////////////////////////////////////
1187
1188class Sweep_Gradient : public Gradient_Shader {
1189public:
1190 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1191 const SkScalar pos[], int count, SkUnitMapper* mapper)
1192 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper)
1193 {
1194 fPtsToUnit.setTranslate(-cx, -cy);
1195 }
1196 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1197 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
1198
1199 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1200 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1201 }
1202
1203protected:
1204 Sweep_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205 virtual Factory getFactory() { return CreateProc; }
reed@android.comec3d6e52009-06-05 14:43:55 +00001206 virtual void onCacheReset() {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001207
1208private:
1209 typedef Gradient_Shader INHERITED;
1210};
1211
1212#ifdef COMPUTE_SWEEP_TABLE
1213#define PI 3.14159265
1214static bool gSweepTableReady;
1215static uint8_t gSweepTable[65];
1216
1217/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1218 We scale the results to [0..32]
1219*/
1220static const uint8_t* build_sweep_table()
1221{
1222 if (!gSweepTableReady)
1223 {
1224 const int N = 65;
1225 const double DENOM = N - 1;
1226
1227 for (int i = 0; i < N; i++)
1228 {
1229 double arg = i / DENOM;
1230 double v = atan(arg);
1231 int iv = (int)round(v * DENOM * 2 / PI);
1232// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1233 printf("%d, ", iv);
1234 gSweepTable[i] = iv;
1235 }
1236 gSweepTableReady = true;
1237 }
1238 return gSweepTable;
1239}
1240#else
1241static const uint8_t gSweepTable[] = {
1242 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1243 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1244 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1245 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1246 32
1247};
1248static const uint8_t* build_sweep_table() { return gSweepTable; }
1249#endif
1250
1251// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1252// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1253// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1254
1255//unsigned div_64(int numer, int denom);
1256static unsigned div_64(int numer, int denom)
1257{
1258 SkASSERT(numer <= denom);
1259 SkASSERT(numer > 0);
1260 SkASSERT(denom > 0);
1261
1262 int nbits = SkCLZ(numer);
1263 int dbits = SkCLZ(denom);
1264 int bits = 6 - nbits + dbits;
1265 SkASSERT(bits <= 6);
1266
1267 if (bits < 0) // detect underflow
1268 return 0;
1269
1270 denom <<= dbits - 1;
1271 numer <<= nbits - 1;
1272
1273 unsigned result = 0;
1274
1275 // do the first one
1276 if ((numer -= denom) >= 0)
1277 result = 1;
1278 else
1279 numer += denom;
1280
1281 // Now fall into our switch statement if there are more bits to compute
1282 if (bits > 0)
1283 {
1284 // make room for the rest of the answer bits
1285 result <<= bits;
1286 switch (bits) {
1287 case 6:
1288 if ((numer = (numer << 1) - denom) >= 0)
1289 result |= 32;
1290 else
1291 numer += denom;
1292 case 5:
1293 if ((numer = (numer << 1) - denom) >= 0)
1294 result |= 16;
1295 else
1296 numer += denom;
1297 case 4:
1298 if ((numer = (numer << 1) - denom) >= 0)
1299 result |= 8;
1300 else
1301 numer += denom;
1302 case 3:
1303 if ((numer = (numer << 1) - denom) >= 0)
1304 result |= 4;
1305 else
1306 numer += denom;
1307 case 2:
1308 if ((numer = (numer << 1) - denom) >= 0)
1309 result |= 2;
1310 else
1311 numer += denom;
1312 case 1:
1313 default: // not strictly need, but makes GCC make better ARM code
1314 if ((numer = (numer << 1) - denom) >= 0)
1315 result |= 1;
1316 else
1317 numer += denom;
1318 }
1319 }
1320 return result;
1321}
1322
1323// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1324static unsigned atan_0_90(SkFixed y, SkFixed x)
1325{
1326#ifdef SK_DEBUG
1327 {
1328 static bool gOnce;
1329 if (!gOnce)
1330 {
1331 gOnce = true;
1332 SkASSERT(div_64(55, 55) == 64);
1333 SkASSERT(div_64(128, 256) == 32);
1334 SkASSERT(div_64(2326528, 4685824) == 31);
1335 SkASSERT(div_64(753664, 5210112) == 9);
1336 SkASSERT(div_64(229376, 4882432) == 3);
1337 SkASSERT(div_64(2, 64) == 2);
1338 SkASSERT(div_64(1, 64) == 1);
1339 // test that we handle underflow correctly
1340 SkASSERT(div_64(12345, 0x54321234) == 0);
1341 }
1342 }
1343#endif
1344
1345 SkASSERT(y > 0 && x > 0);
1346 const uint8_t* table = build_sweep_table();
1347
1348 unsigned result;
1349 bool swap = (x < y);
1350 if (swap)
1351 {
1352 // first part of the atan(v) = PI/2 - atan(1/v) identity
1353 // since our div_64 and table want v <= 1, where v = y/x
1354 SkTSwap<SkFixed>(x, y);
1355 }
1356
1357 result = div_64(y, x);
1358
1359#ifdef SK_DEBUG
1360 {
1361 unsigned result2 = SkDivBits(y, x, 6);
1362 SkASSERT(result2 == result ||
1363 (result == 1 && result2 == 0));
1364 }
1365#endif
1366
1367 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1368 result = table[result];
1369
1370 if (swap)
1371 {
1372 // complete the atan(v) = PI/2 - atan(1/v) identity
1373 result = 64 - result;
1374 // pin to 63
1375 result -= result >> 6;
1376 }
1377
1378 SkASSERT(result <= 63);
1379 return result;
1380}
1381
1382// returns angle in a circle [0..2PI) -> [0..255]
1383static unsigned SkATan2_255(SkFixed y, SkFixed x)
1384{
1385 if (x == 0)
1386 {
1387 if (y == 0)
1388 return 0;
1389 return y < 0 ? 192 : 64;
1390 }
1391 if (y == 0)
1392 return x < 0 ? 128 : 0;
1393
1394 /* Find the right quadrant for x,y
1395 Since atan_0_90 only handles the first quadrant, we rotate x,y
1396 appropriately before calling it, and then add the right amount
1397 to account for the real quadrant.
1398 quadrant 0 : add 0 | x > 0 && y > 0
1399 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1400 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1401 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
1402
1403 map x<0 to (1 << 6)
1404 map y<0 to (3 << 6)
1405 add = map_x ^ map_y
1406 */
1407 int xsign = x >> 31;
1408 int ysign = y >> 31;
1409 int add = ((-xsign) ^ (ysign & 3)) << 6;
1410
1411#ifdef SK_DEBUG
1412 if (0 == add)
1413 SkASSERT(x > 0 && y > 0);
1414 else if (64 == add)
1415 SkASSERT(x < 0 && y > 0);
1416 else if (128 == add)
1417 SkASSERT(x < 0 && y < 0);
1418 else if (192 == add)
1419 SkASSERT(x > 0 && y < 0);
1420 else
1421 SkASSERT(!"bad value for add");
1422#endif
1423
1424 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1425 where we need to rotate x,y by 90 or -90
1426 */
1427 x = (x ^ xsign) - xsign;
1428 y = (y ^ ysign) - ysign;
1429 if (add & 64) // quads 1 or 3 need to swap x,y
1430 SkTSwap<SkFixed>(x, y);
1431
1432 unsigned result = add + atan_0_90(y, x);
1433 SkASSERT(result < 256);
1434 return result;
1435}
1436
1437void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1438{
1439 SkMatrix::MapXYProc proc = fDstToIndexProc;
1440 const SkMatrix& matrix = fDstToIndex;
1441 const SkPMColor* cache = this->getCache32();
1442 SkPoint srcPt;
1443
1444 if (fDstToIndexClass != kPerspective_MatrixClass)
1445 {
1446 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1447 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1448 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1449 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1450
1451 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1452 {
1453 SkFixed storage[2];
1454 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1455 &storage[0], &storage[1]);
1456 dx = storage[0];
1457 dy = storage[1];
1458 }
1459 else
1460 {
1461 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1462 dx = SkScalarToFixed(matrix.getScaleX());
1463 dy = SkScalarToFixed(matrix.getSkewY());
1464 }
1465
1466 for (; count > 0; --count)
1467 {
1468 *dstC++ = cache[SkATan2_255(fy, fx)];
1469 fx += dx;
1470 fy += dy;
1471 }
1472 }
1473 else // perspective case
1474 {
1475 for (int stop = x + count; x < stop; x++)
1476 {
1477 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1478 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1479
1480 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1481 SkScalarToFixed(srcPt.fX));
1482 *dstC++ = cache[index];
1483 }
1484 }
1485}
1486
1487void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1488{
1489 SkMatrix::MapXYProc proc = fDstToIndexProc;
1490 const SkMatrix& matrix = fDstToIndex;
1491 const uint16_t* cache = this->getCache16();
1492 int toggle = ((x ^ y) & 1) << kCache16Bits;
1493 SkPoint srcPt;
1494
1495 if (fDstToIndexClass != kPerspective_MatrixClass)
1496 {
1497 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1498 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1499 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1500 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1501
1502 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1503 {
1504 SkFixed storage[2];
1505 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1506 &storage[0], &storage[1]);
1507 dx = storage[0];
1508 dy = storage[1];
1509 }
1510 else
1511 {
1512 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1513 dx = SkScalarToFixed(matrix.getScaleX());
1514 dy = SkScalarToFixed(matrix.getSkewY());
1515 }
1516
1517 for (; count > 0; --count)
1518 {
1519 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1520 *dstC++ = cache[toggle + index];
1521 toggle ^= (1 << kCache16Bits);
1522 fx += dx;
1523 fy += dy;
1524 }
1525 }
1526 else // perspective case
1527 {
1528 for (int stop = x + count; x < stop; x++)
1529 {
1530 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1531 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1532
1533 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1534 SkScalarToFixed(srcPt.fX));
1535 index >>= (8 - kCache16Bits);
1536 *dstC++ = cache[toggle + index];
1537 toggle ^= (1 << kCache16Bits);
1538 }
1539 }
1540}
1541
1542///////////////////////////////////////////////////////////////////////////
1543///////////////////////////////////////////////////////////////////////////
1544
1545// assumes colors is SkColor* and pos is SkScalar*
1546#define EXPAND_1_COLOR(count) \
1547 SkColor tmp[2]; \
1548 do { \
1549 if (1 == count) { \
1550 tmp[0] = tmp[1] = colors[0]; \
1551 colors = tmp; \
1552 pos = NULL; \
1553 count = 2; \
1554 } \
1555 } while (0)
1556
1557SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
1558 const SkColor colors[], const SkScalar pos[], int colorCount,
1559 SkShader::TileMode mode, SkUnitMapper* mapper)
1560{
1561 if (NULL == pts || NULL == colors || colorCount < 1) {
1562 return NULL;
1563 }
1564 EXPAND_1_COLOR(colorCount);
1565
reed@android.comab840b82009-07-01 17:00:03 +00001566 return SkNEW_ARGS(Linear_Gradient,
1567 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568}
1569
1570SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
1571 const SkColor colors[], const SkScalar pos[], int colorCount,
1572 SkShader::TileMode mode, SkUnitMapper* mapper)
1573{
1574 if (radius <= 0 || NULL == colors || colorCount < 1) {
1575 return NULL;
1576 }
1577 EXPAND_1_COLOR(colorCount);
1578
reed@android.comab840b82009-07-01 17:00:03 +00001579 return SkNEW_ARGS(Radial_Gradient,
1580 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581}
1582
1583SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
1584 const SkColor colors[],
1585 const SkScalar pos[],
1586 int count, SkUnitMapper* mapper)
1587{
1588 if (NULL == colors || count < 1) {
1589 return NULL;
1590 }
1591 EXPAND_1_COLOR(count);
1592
1593 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
1594}
1595
1596static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
1597 Linear_Gradient::CreateProc);
1598
1599static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
1600 Radial_Gradient::CreateProc);
1601
1602static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
1603 Sweep_Gradient::CreateProc);
1604