blob: 0899cfbfeb7d4ce85e4365f2eda8980bf6904c00 [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
reed@android.com8a1c16f2008-12-17 15:59:43 +000023///////////////////////////////////////////////////////////////////////////
24
25typedef SkFixed (*TileProc)(SkFixed);
26
reed@android.com41bccf52009-04-03 13:33:51 +000027static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000028 return SkClampMax(x, 0xFFFF);
29}
30
reed@android.com41bccf52009-04-03 13:33:51 +000031static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000032 return x & 0xFFFF;
33}
34
reed@android.com41bccf52009-04-03 13:33:51 +000035static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000036 int s = x << 15 >> 31;
37 return (x ^ s) & 0xFFFF;
38}
39
40static const TileProc gTileProcs[] = {
41 clamp_tileproc,
42 repeat_tileproc,
43 mirror_tileproc
44};
45
46//////////////////////////////////////////////////////////////////////////////
47
reed@android.com41bccf52009-04-03 13:33:51 +000048static inline int repeat_6bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000049 return x & 63;
50}
51
reed@android.com41bccf52009-04-03 13:33:51 +000052static inline int mirror_6bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000053#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
54 if (x & 64)
55 x = ~x;
56 return x & 63;
57#else
58 int s = x << 25 >> 31;
59 return (x ^ s) & 63;
60#endif
61}
62
reed@android.com41bccf52009-04-03 13:33:51 +000063static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 return x & 0xFF;
65}
66
reed@android.com41bccf52009-04-03 13:33:51 +000067static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000068#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000069 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000070 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +000071 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000072 return x & 255;
73#else
74 int s = x << 23 >> 31;
75 return (x ^ s) & 0xFF;
76#endif
77}
78
79//////////////////////////////////////////////////////////////////////////////
80
81class Gradient_Shader : public SkShader {
82public:
83 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +000084 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +000085 virtual ~Gradient_Shader();
86
87 // overrides
88 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
89 virtual uint32_t getFlags() { return fFlags; }
90
91protected:
92 Gradient_Shader(SkFlattenableReadBuffer& );
93 SkUnitMapper* fMapper;
94 SkMatrix fPtsToUnit; // set by subclass
95 SkMatrix fDstToIndex;
96 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +000097 TileMode fTileMode;
98 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +000099 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 uint8_t fDstToIndexClass;
101 uint8_t fFlags;
102
103 struct Rec {
104 SkFixed fPos; // 0...1
105 uint32_t fScale; // (1 << 24) / range
106 };
107 Rec* fRecs;
108
109 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000110 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000112 kCache16Mask = kCache16Count - 1,
113 kCache16Shift = 16 - kCache16Bits,
114
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115 kCache32Bits = 8, // pretty much should always be 8
116 kCache32Count = 1 << kCache32Bits
117 };
118 virtual void flatten(SkFlattenableWriteBuffer& );
119 const uint16_t* getCache16();
120 const SkPMColor* getCache32();
121
reed@android.com9b46e772009-06-05 12:24:41 +0000122 // called when we kill our cached colors (to be rebuilt later on demand)
reed@android.comec3d6e52009-06-05 14:43:55 +0000123 virtual void onCacheReset() = 0;
reed@android.com9b46e772009-06-05 12:24:41 +0000124
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125private:
126 enum {
127 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
128
reed@android.com1c12abe2009-07-02 15:01:02 +0000129 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130 };
131 SkColor fStorage[(kStorageSize + 3) >> 2];
132 SkColor* fOrigColors;
133
134 uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
135 SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
136
137 uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
138 SkPMColor* fCache32Storage; // storage for fCache32, allocated on demand
139 unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
140
reed@android.com512a8762009-12-14 15:25:36 +0000141 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
142
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143 typedef SkShader INHERITED;
144};
145
reed@android.com41bccf52009-04-03 13:33:51 +0000146static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 SkASSERT(x >= 0 && x <= SK_Scalar1);
148
149#ifdef SK_SCALAR_IS_FLOAT
150 return (unsigned)(x * 0xFFFF);
151#else
152 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
153#endif
154}
155
reed@android.com41bccf52009-04-03 13:33:51 +0000156Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
157 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158 SkASSERT(colorCount > 1);
159
160 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
161
162 fMapper = mapper;
163 mapper->safeRef();
164
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
166 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
167 fTileMode = mode;
168 fTileProc = gTileProcs[mode];
reed@android.com41bccf52009-04-03 13:33:51 +0000169
170 fCache16 = fCache16Storage = NULL;
171 fCache32 = fCache32Storage = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172
reed@android.com41bccf52009-04-03 13:33:51 +0000173 /* Note: we let the caller skip the first and/or last position.
174 i.e. pos[0] = 0.3, pos[1] = 0.7
175 In these cases, we insert dummy entries to ensure that the final data
176 will be bracketed by [0, 1].
177 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
178
179 Thus colorCount (the caller's value, and fColorCount (our value) may
180 differ by up to 2. In the above example:
181 colorCount = 2
182 fColorCount = 4
183 */
184 fColorCount = colorCount;
185 // check if we need to add in dummy start and/or end position/colors
186 bool dummyFirst = false;
187 bool dummyLast = false;
188 if (pos) {
189 dummyFirst = pos[0] != 0;
190 dummyLast = pos[colorCount - 1] != SK_Scalar1;
191 fColorCount += dummyFirst + dummyLast;
192 }
193
194 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000195 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000196 fOrigColors = reinterpret_cast<SkColor*>(
197 sk_malloc_throw(size * fColorCount));
198 }
199 else {
200 fOrigColors = fStorage;
201 }
202
203 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 {
reed@android.com41bccf52009-04-03 13:33:51 +0000205 SkColor* origColors = fOrigColors;
206 if (dummyFirst) {
207 *origColors++ = colors[0];
208 }
209 memcpy(origColors, colors, colorCount * sizeof(SkColor));
210 if (dummyLast) {
211 origColors += colorCount;
212 *origColors = colors[colorCount - 1];
213 }
214 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215
reed@android.com1c12abe2009-07-02 15:01:02 +0000216 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000217 if (fColorCount > 2) {
218 Rec* recs = fRecs;
219 recs->fPos = 0;
220 // recs->fScale = 0; // unused;
221 recs += 1;
222 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223 /* We need to convert the user's array of relative positions into
224 fixed-point positions and scale factors. We need these results
225 to be strictly monotonic (no two values equal or out of order).
226 Hence this complex loop that just jams a zero for the scale
227 value if it sees a segment out of order, and it assures that
228 we start at 0 and end at 1.0
229 */
230 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000231 int startIndex = dummyFirst ? 0 : 1;
232 int count = colorCount + dummyLast;
233 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 // force the last value to be 1.0
235 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000236 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000237 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000238 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000239 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 }
reed@android.com41bccf52009-04-03 13:33:51 +0000241 // pin curr withing range
242 if (curr < 0) {
243 curr = 0;
244 } else if (curr > SK_Fixed1) {
245 curr = SK_Fixed1;
246 }
247 recs->fPos = curr;
248 if (curr > prev) {
249 recs->fScale = (1 << 24) / (curr - prev);
250 } else {
251 recs->fScale = 0; // ignore this segment
252 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000253 // get ready for the next value
254 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000255 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256 }
reed@android.com41bccf52009-04-03 13:33:51 +0000257 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 SkFixed dp = SK_Fixed1 / (colorCount - 1);
259 SkFixed p = dp;
260 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000261 for (int i = 1; i < colorCount; i++) {
262 recs->fPos = p;
263 recs->fScale = scale;
264 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265 p += dp;
266 }
267 }
268 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000269 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000270}
271
272Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000273 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000274 fCacheAlpha = 256;
275
276 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
277
278 fCache16 = fCache16Storage = NULL;
279 fCache32 = fCache32Storage = NULL;
280
reed@android.com41bccf52009-04-03 13:33:51 +0000281 int colorCount = fColorCount = buffer.readU32();
282 if (colorCount > kColorStorageCount) {
283 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
284 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
285 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000287 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000288 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289
290 fTileMode = (TileMode)buffer.readU8();
291 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000292 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000293 if (colorCount > 2) {
294 Rec* recs = fRecs;
295 recs[0].fPos = 0;
296 for (int i = 1; i < colorCount; i++) {
297 recs[i].fPos = buffer.readS32();
298 recs[i].fScale = buffer.readU32();
299 }
300 }
301 buffer.read(&fPtsToUnit, sizeof(SkMatrix));
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000302 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303}
304
reed@android.com41bccf52009-04-03 13:33:51 +0000305Gradient_Shader::~Gradient_Shader() {
306 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000308 }
309 if (fCache32Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 sk_free(fCache32Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000311 }
312 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000314 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315 fMapper->safeUnref();
316}
317
reed@android.com41bccf52009-04-03 13:33:51 +0000318void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319 this->INHERITED::flatten(buffer);
320 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000321 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
323 buffer.write8(fTileMode);
324 if (fColorCount > 2) {
325 Rec* recs = fRecs;
326 for (int i = 1; i < fColorCount; i++) {
327 buffer.write32(recs[i].fPos);
328 buffer.write32(recs[i].fScale);
329 }
330 }
331 buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
332}
333
334bool Gradient_Shader::setContext(const SkBitmap& device,
335 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000336 const SkMatrix& matrix) {
337 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000339 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340
341 const SkMatrix& inverse = this->getTotalInverse();
342
343 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
344 return false;
345 }
346
347 fDstToIndexProc = fDstToIndex.getMapXYProc();
348 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
349
350 // now convert our colors in to PMColors
351 unsigned paintAlpha = this->getPaintAlpha();
352 unsigned colorAlpha = 0xFF;
353
reed@android.com3d06a8c2009-07-07 18:19:59 +0000354 // FIXME: record colorAlpha in constructor, since this is not affected
355 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000356 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000357 SkColor src = fOrigColors[i];
358 unsigned sa = SkColorGetA(src);
359 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 }
361
362 fFlags = this->INHERITED::getFlags();
363 if ((colorAlpha & paintAlpha) == 0xFF) {
364 fFlags |= kOpaqueAlpha_Flag;
365 }
366 // we can do span16 as long as our individual colors are opaque,
367 // regardless of the paint's alpha
368 if (0xFF == colorAlpha) {
369 fFlags |= kHasSpan16_Flag;
370 }
371
372 // if the new alpha differs from the previous time we were called, inval our cache
373 // this will trigger the cache to be rebuilt.
374 // we don't care about the first time, since the cache ptrs will already be NULL
375 if (fCacheAlpha != paintAlpha) {
376 fCache16 = NULL; // inval the cache
377 fCache32 = NULL; // inval the cache
378 fCacheAlpha = paintAlpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000379 // inform our subclasses
380 this->onCacheReset();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000381 }
382 return true;
383}
384
reed@android.com41bccf52009-04-03 13:33:51 +0000385static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000386 SkASSERT(a == SkToU8(a));
387 SkASSERT(b == SkToU8(b));
388 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389 return a + ((b - a) * scale >> 8);
390}
391
reed@android.com41bccf52009-04-03 13:33:51 +0000392static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
393 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000394#if 0
395 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
396 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
397 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
398 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
399
400 return SkPackARGB32(a, r, g, b);
401#else
402 int otherBlend = 256 - blend;
403
404#if 0
405 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
406 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
407 SkASSERT((t0 & t1) == 0);
408 return t0 | t1;
409#else
410 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
411 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
412#endif
413
414#endif
415}
416
417#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
418
reed@android.com41bccf52009-04-03 13:33:51 +0000419/** We take the original colors, not our premultiplied PMColors, since we can
420 build a 16bit table as long as the original colors are opaque, even if the
421 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000422*/
reed@android.com512a8762009-12-14 15:25:36 +0000423void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
424 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425 SkASSERT(count > 1);
426 SkASSERT(SkColorGetA(c0) == 0xFF);
427 SkASSERT(SkColorGetA(c1) == 0xFF);
428
429 SkFixed r = SkColorGetR(c0);
430 SkFixed g = SkColorGetG(c0);
431 SkFixed b = SkColorGetB(c0);
432
433 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
434 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
435 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
436
437 r = SkIntToFixed(r) + 0x8000;
438 g = SkIntToFixed(g) + 0x8000;
439 b = SkIntToFixed(b) + 0x8000;
440
441 do {
442 unsigned rr = r >> 16;
443 unsigned gg = g >> 16;
444 unsigned bb = b >> 16;
445 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000446 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000447 cache += 1;
448 r += dr;
449 g += dg;
450 b += db;
451 } while (--count != 0);
452}
453
reed@android.com1c12abe2009-07-02 15:01:02 +0000454static void build_32bit_cache(SkPMColor cache[], SkColor c0, SkColor c1,
455 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000456 SkASSERT(count > 1);
457
reed@android.com1c12abe2009-07-02 15:01:02 +0000458 // need to apply paintAlpha to our two endpoints
459 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
460 SkFixed da;
461 {
462 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
463 da = SkIntToFixed(tmp - a) / (count - 1);
464 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000465
reed@android.com1c12abe2009-07-02 15:01:02 +0000466 SkFixed r = SkColorGetR(c0);
467 SkFixed g = SkColorGetG(c0);
468 SkFixed b = SkColorGetB(c0);
469 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
470 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
471 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000472
473 a = SkIntToFixed(a) + 0x8000;
474 r = SkIntToFixed(r) + 0x8000;
475 g = SkIntToFixed(g) + 0x8000;
476 b = SkIntToFixed(b) + 0x8000;
477
478 do {
reed@android.com1c12abe2009-07-02 15:01:02 +0000479 *cache++ = SkPreMultiplyARGB(a >> 16, r >> 16, g >> 16, b >> 16);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000480 a += da;
481 r += dr;
482 g += dg;
483 b += db;
484 } while (--count != 0);
485}
486
reed@android.com41bccf52009-04-03 13:33:51 +0000487static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000488 SkASSERT((unsigned)x <= SK_Fixed1);
489 return x - (x >> 16);
490}
491
reed@android.com41bccf52009-04-03 13:33:51 +0000492static inline U16CPU dot6to16(unsigned x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000493 SkASSERT(x < 64);
494 return (x << 10) | (x << 4) | (x >> 2);
495}
496
reed@android.com41bccf52009-04-03 13:33:51 +0000497const uint16_t* Gradient_Shader::getCache16() {
498 if (fCache16 == NULL) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000499 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@android.com8a1c16f2008-12-17 15:59:43 +0000500 fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count * 2);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000501 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000502 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000503 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000504 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000505 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000506 Rec* rec = fRecs;
507 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000508 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000509 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000510 SkASSERT(nextIndex < kCache16Count);
511
512 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000513 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000514 prevIndex = nextIndex;
515 }
516 SkASSERT(prevIndex == kCache16Count - 1);
517 }
518
reed@android.com41bccf52009-04-03 13:33:51 +0000519 if (fMapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000520 fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count * 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000521 uint16_t* linear = fCache16; // just computed linear data
522 uint16_t* mapped = fCache16Storage; // storage for mapped data
523 SkUnitMapper* map = fMapper;
reed@android.com41bccf52009-04-03 13:33:51 +0000524 for (int i = 0; i < 64; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000525 int index = map->mapUnit16(dot6to16(i)) >> 10;
526 mapped[i] = linear[index];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000527 mapped[i + 64] = linear[index + 64];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000528 }
529 sk_free(fCache16);
530 fCache16 = fCache16Storage;
531 }
532 }
533 return fCache16;
534}
535
reed@android.com41bccf52009-04-03 13:33:51 +0000536const SkPMColor* Gradient_Shader::getCache32() {
537 if (fCache32 == NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000538 if (fCache32Storage == NULL) // set the storage and our working ptr
539 fCache32Storage = (SkPMColor*)sk_malloc_throw(sizeof(SkPMColor) * kCache32Count);
540
541 fCache32 = fCache32Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000542 if (fColorCount == 2) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000543 build_32bit_cache(fCache32, fOrigColors[0], fOrigColors[1],
544 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000545 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000546 Rec* rec = fRecs;
547 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000548 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000549 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
550 SkASSERT(nextIndex < kCache32Count);
551
552 if (nextIndex > prevIndex)
reed@android.com1c12abe2009-07-02 15:01:02 +0000553 build_32bit_cache(fCache32 + prevIndex, fOrigColors[i-1],
554 fOrigColors[i],
555 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000556 prevIndex = nextIndex;
557 }
558 SkASSERT(prevIndex == kCache32Count - 1);
559 }
560
reed@android.com41bccf52009-04-03 13:33:51 +0000561 if (fMapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000562 fCache32Storage = (SkPMColor*)sk_malloc_throw(sizeof(SkPMColor) * kCache32Count);
563 SkPMColor* linear = fCache32; // just computed linear data
564 SkPMColor* mapped = fCache32Storage; // storage for mapped data
565 SkUnitMapper* map = fMapper;
reed@android.com41bccf52009-04-03 13:33:51 +0000566 for (int i = 0; i < 256; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000567 mapped[i] = linear[map->mapUnit16((i << 8) | i) >> 8];
reed@android.com41bccf52009-04-03 13:33:51 +0000568 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000569 sk_free(fCache32);
570 fCache32 = fCache32Storage;
571 }
572 }
573 return fCache32;
574}
575
576///////////////////////////////////////////////////////////////////////////
577
reed@android.com41bccf52009-04-03 13:33:51 +0000578static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000579 SkVector vec = pts[1] - pts[0];
580 SkScalar mag = vec.length();
581 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
582
583 vec.scale(inv);
584 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
585 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
586 matrix->postScale(inv, inv);
587}
588
589///////////////////////////////////////////////////////////////////////////////
590
591class Linear_Gradient : public Gradient_Shader {
592public:
593 Linear_Gradient(const SkPoint pts[2],
594 const SkColor colors[], const SkScalar pos[], int colorCount,
595 SkShader::TileMode mode, SkUnitMapper* mapper)
596 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
597 {
reed@android.com9b46e772009-06-05 12:24:41 +0000598 fCachedBitmap = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 pts_to_unit_matrix(pts, &fPtsToUnit);
600 }
reed@android.com9b46e772009-06-05 12:24:41 +0000601 virtual ~Linear_Gradient() {
602 if (fCachedBitmap) {
603 SkDELETE(fCachedBitmap);
604 }
605 }
606
reed@android.com5119bdb2009-06-12 21:27:03 +0000607 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000608 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
609 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
610 virtual bool asABitmap(SkBitmap*, SkMatrix*, TileMode*);
reed@android.com9b46e772009-06-05 12:24:41 +0000611 virtual void onCacheReset() {
612 if (fCachedBitmap) {
613 SkDELETE(fCachedBitmap);
614 fCachedBitmap = NULL;
615 }
616 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617
618 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
619 return SkNEW_ARGS(Linear_Gradient, (buffer));
620 }
621
622protected:
reed@android.comec3d6e52009-06-05 14:43:55 +0000623 Linear_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {
624 fCachedBitmap = NULL;
625 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626 virtual Factory getFactory() { return CreateProc; }
627
628private:
reed@android.com9b46e772009-06-05 12:24:41 +0000629 SkBitmap* fCachedBitmap; // allocated on demand
630
reed@android.com8a1c16f2008-12-17 15:59:43 +0000631 typedef Gradient_Shader INHERITED;
632};
633
reed@android.com5119bdb2009-06-12 21:27:03 +0000634bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
635 const SkMatrix& matrix) {
636 if (!this->INHERITED::setContext(device, paint, matrix)) {
637 return false;
638 }
639
640 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
641 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000642 fFlags |= SkShader::kConstInY32_Flag;
643 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
644 // only claim this if we do have a 16bit mode (i.e. none of our
645 // colors have alpha), and if we are not dithering (which obviously
646 // is not const in Y).
647 fFlags |= SkShader::kConstInY16_Flag;
648 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000649 }
650 return true;
651}
652
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@android.comfc25abd2009-01-15 14:38:33 +0000654static inline bool no_need_for_clamp(int fx, int dx, int count)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655{
656 SkASSERT(count > 0);
657 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
658}
659
660void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
661{
662 SkASSERT(count > 0);
663
664 SkPoint srcPt;
665 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
666 TileProc proc = fTileProc;
667 const SkPMColor* cache = this->getCache32();
668
reed@android.comc552a432009-06-12 20:02:50 +0000669 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000670 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
671 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
reed@android.comc552a432009-06-12 20:02:50 +0000672 // preround fx by half the amount we throw away
673 fx += 1 << 7;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674
reed@android.comc552a432009-06-12 20:02:50 +0000675 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000676 SkFixed dxStorage[1];
677 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
678 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000679 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
681 dx = SkScalarToFixed(fDstToIndex.getScaleX());
682 }
683
reed@android.comc552a432009-06-12 20:02:50 +0000684 if (SkFixedNearlyZero(dx)) {
685 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 unsigned fi = proc(fx);
687 SkASSERT(fi <= 0xFFFF);
688 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000689 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690#if 0
691 if (no_need_for_clamp(fx, dx, count))
692 {
693 unsigned fi;
694 while ((count -= 4) >= 0)
695 {
696 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
697 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
698 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
699 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
700 }
701 SkASSERT(count <= -1 && count >= -4);
702 count += 4;
703 while (--count >= 0)
704 {
705 fi = fx >> 8;
706 SkASSERT(fi <= 0xFF);
707 fx += dx;
708 *dstC++ = cache[fi];
709 }
710 }
711 else
712#endif
713 do {
714 unsigned fi = SkClampMax(fx >> 8, 0xFF);
715 SkASSERT(fi <= 0xFF);
716 fx += dx;
717 *dstC++ = cache[fi];
718 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000719 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720 do {
721 unsigned fi = mirror_8bits(fx >> 8);
722 SkASSERT(fi <= 0xFF);
723 fx += dx;
724 *dstC++ = cache[fi];
725 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000726 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727 SkASSERT(proc == repeat_tileproc);
728 do {
729 unsigned fi = repeat_8bits(fx >> 8);
730 SkASSERT(fi <= 0xFF);
731 fx += dx;
732 *dstC++ = cache[fi];
733 } while (--count != 0);
734 }
reed@android.comc552a432009-06-12 20:02:50 +0000735 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736 SkScalar dstX = SkIntToScalar(x);
737 SkScalar dstY = SkIntToScalar(y);
738 do {
739 dstProc(fDstToIndex, dstX, dstY, &srcPt);
740 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
741 SkASSERT(fi <= 0xFFFF);
742 *dstC++ = cache[fi >> (16 - kCache32Bits)];
743 dstX += SK_Scalar1;
744 } while (--count != 0);
745 }
746}
747
748bool Linear_Gradient::asABitmap(SkBitmap* bitmap, SkMatrix* matrix,
749 TileMode xy[]) {
reed@android.com9b46e772009-06-05 12:24:41 +0000750 // we cache our "bitmap", so it's generationID will be const on subsequent
751 // calls to asABitmap
752 if (NULL == fCachedBitmap) {
753 fCachedBitmap = SkNEW(SkBitmap);
754 fCachedBitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
755 fCachedBitmap->setPixels((void*)this->getCache32(), NULL);
756 }
757
reed@android.com8a1c16f2008-12-17 15:59:43 +0000758 if (bitmap) {
reed@android.com9b46e772009-06-05 12:24:41 +0000759 *bitmap = *fCachedBitmap;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000760 }
761 if (matrix) {
762 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
763 matrix->preConcat(fPtsToUnit);
764 }
765 if (xy) {
766 xy[0] = fTileMode;
767 xy[1] = kClamp_TileMode;
768 }
769 return true;
770}
771
reed@android.com3c9b2a42009-08-27 19:28:37 +0000772static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
773 int count) {
774 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775 *dst++ = value;
776 count -= 1;
777 SkTSwap(value, other);
778 }
779
780 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
781
reed@android.com3c9b2a42009-08-27 19:28:37 +0000782 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000784 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786
787void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
788{
789 SkASSERT(count > 0);
790
791 SkPoint srcPt;
792 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
793 TileProc proc = fTileProc;
794 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796
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.com512a8762009-12-14 15:25:36 +0000814 unsigned fi = proc(fx) >> kCache16Shift;
815 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 dither_memset16(dstC, cache[toggle + fi], cache[(toggle ^ (1 << kCache16Bits)) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000817 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000818 do {
reed@android.com512a8762009-12-14 15:25:36 +0000819 unsigned fi = SkClampMax(fx >> kCache16Shift, kCache16Mask);
820 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000821 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 *dstC++ = cache[toggle + fi];
823 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000825 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826 do {
reed@android.com512a8762009-12-14 15:25:36 +0000827 unsigned fi = mirror_6bits(fx >> kCache16Shift);
828 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830 *dstC++ = cache[toggle + fi];
831 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000833 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834 SkASSERT(proc == repeat_tileproc);
835 do {
reed@android.com512a8762009-12-14 15:25:36 +0000836 unsigned fi = repeat_6bits(fx >> kCache16Shift);
837 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000838 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839 *dstC++ = cache[toggle + fi];
840 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 } while (--count != 0);
842 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000843 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844 SkScalar dstX = SkIntToScalar(x);
845 SkScalar dstY = SkIntToScalar(y);
846 do {
847 dstProc(fDstToIndex, dstX, dstY, &srcPt);
848 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
849 SkASSERT(fi <= 0xFFFF);
850
reed@android.com512a8762009-12-14 15:25:36 +0000851 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852 *dstC++ = cache[toggle + index];
853 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854
855 dstX += SK_Scalar1;
856 } while (--count != 0);
857 }
858}
859
860///////////////////////////////////////////////////////////////////////////////
861
862#define kSQRT_TABLE_BITS 11
863#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
864
865#include "SkRadialGradient_Table.h"
866
867#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
868
869#include <stdio.h>
870
871void SkRadialGradient_BuildTable()
872{
873 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
874
875 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
876 SkASSERT(file);
877 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
878
879 for (int i = 0; i < kSQRT_TABLE_SIZE; i++)
880 {
881 if ((i & 15) == 0)
882 ::fprintf(file, "\t");
883
884 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
885
886 ::fprintf(file, "0x%02X", value);
887 if (i < kSQRT_TABLE_SIZE-1)
888 ::fprintf(file, ", ");
889 if ((i & 15) == 15)
890 ::fprintf(file, "\n");
891 }
892 ::fprintf(file, "};\n");
893 ::fclose(file);
894}
895
896#endif
897
898
899static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius, SkMatrix* matrix)
900{
901 SkScalar inv = SkScalarInvert(radius);
902
903 matrix->setTranslate(-center.fX, -center.fY);
904 matrix->postScale(inv, inv);
905}
906
907class Radial_Gradient : public Gradient_Shader {
908public:
909 Radial_Gradient(const SkPoint& center, SkScalar radius,
910 const SkColor colors[], const SkScalar pos[], int colorCount,
911 SkShader::TileMode mode, SkUnitMapper* mapper)
912 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
913 {
914 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
915 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
916
917 rad_to_unit_matrix(center, radius, &fPtsToUnit);
918 }
919 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
920 {
921 SkASSERT(count > 0);
922
923 SkPoint srcPt;
924 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
925 TileProc proc = fTileProc;
926 const SkPMColor* cache = this->getCache32();
927
928 if (fDstToIndexClass != kPerspective_MatrixClass)
929 {
930 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
931 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
932 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
933
934 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
935 {
936 SkFixed storage[2];
937 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
938 dx = storage[0];
939 dy = storage[1];
940 }
941 else
942 {
943 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
944 dx = SkScalarToFixed(fDstToIndex.getScaleX());
945 dy = SkScalarToFixed(fDstToIndex.getSkewY());
946 }
947
948 if (proc == clamp_tileproc)
949 {
950 const uint8_t* sqrt_table = gSqrt8Table;
951 fx >>= 1;
952 dx >>= 1;
953 fy >>= 1;
954 dy >>= 1;
955 do {
956 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
957 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
958 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
959 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
960 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
961 fx += dx;
962 fy += dy;
963 } while (--count != 0);
964 }
965 else if (proc == mirror_tileproc)
966 {
967 do {
968 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
969 unsigned fi = mirror_tileproc(dist);
970 SkASSERT(fi <= 0xFFFF);
971 *dstC++ = cache[fi >> (16 - kCache32Bits)];
972 fx += dx;
973 fy += dy;
974 } while (--count != 0);
975 }
976 else
977 {
978 SkASSERT(proc == repeat_tileproc);
979 do {
980 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
981 unsigned fi = repeat_tileproc(dist);
982 SkASSERT(fi <= 0xFFFF);
983 *dstC++ = cache[fi >> (16 - kCache32Bits)];
984 fx += dx;
985 fy += dy;
986 } while (--count != 0);
987 }
988 }
989 else // perspective case
990 {
991 SkScalar dstX = SkIntToScalar(x);
992 SkScalar dstY = SkIntToScalar(y);
993 do {
994 dstProc(fDstToIndex, dstX, dstY, &srcPt);
995 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
996 SkASSERT(fi <= 0xFFFF);
997 *dstC++ = cache[fi >> (16 - kCache32Bits)];
998 dstX += SK_Scalar1;
999 } while (--count != 0);
1000 }
1001 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001002
1003 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 SkASSERT(count > 0);
1005
1006 SkPoint srcPt;
1007 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1008 TileProc proc = fTileProc;
1009 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011
reed@android.com3c9b2a42009-08-27 19:28:37 +00001012 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001013 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
1014 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1015 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1016
reed@android.com3c9b2a42009-08-27 19:28:37 +00001017 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001018 SkFixed storage[2];
1019 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1020 dx = storage[0];
1021 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001022 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1024 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1025 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1026 }
1027
reed@android.com3c9b2a42009-08-27 19:28:37 +00001028 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001029 const uint8_t* sqrt_table = gSqrt8Table;
1030
1031 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1032 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1033 precision, but that appears to be visually OK. If we decide this is OK for
1034 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1035 to avoid having to do these extra shifts each time.
1036 */
1037 fx >>= 1;
1038 dx >>= 1;
1039 fy >>= 1;
1040 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001041 if (dy == 0) { // might perform this check for the other modes, but the win will be a smaller % of the total
reed@android.com8a1c16f2008-12-17 15:59:43 +00001042 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1043 fy *= fy;
1044 do {
1045 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1046 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1047 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1048 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001049 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1050 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001051 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001052 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001053 do {
1054 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1055 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1056 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1057 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1058 fx += dx;
1059 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1061 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001062 } while (--count != 0);
1063 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001064 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 do {
1066 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1067 unsigned fi = mirror_tileproc(dist);
1068 SkASSERT(fi <= 0xFFFF);
1069 fx += dx;
1070 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001071 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1072 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001073 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001074 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 SkASSERT(proc == repeat_tileproc);
1076 do {
1077 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1078 unsigned fi = repeat_tileproc(dist);
1079 SkASSERT(fi <= 0xFFFF);
1080 fx += dx;
1081 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1083 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001084 } while (--count != 0);
1085 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001086 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001087 SkScalar dstX = SkIntToScalar(x);
1088 SkScalar dstY = SkIntToScalar(y);
1089 do {
1090 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1091 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1092 SkASSERT(fi <= 0xFFFF);
1093
1094 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001095 *dstC++ = cache[toggle + index];
1096 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001097
1098 dstX += SK_Scalar1;
1099 } while (--count != 0);
1100 }
1101 }
1102
1103 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1104 return SkNEW_ARGS(Radial_Gradient, (buffer));
1105 }
1106
1107protected:
1108 Radial_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {};
1109 virtual Factory getFactory() { return CreateProc; }
reed@android.comec3d6e52009-06-05 14:43:55 +00001110 virtual void onCacheReset() {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111
1112private:
1113 typedef Gradient_Shader INHERITED;
1114};
1115
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001116/* Two-point radial gradients are specified by two circles, each with a center
1117 point and radius. The gradient can be considered to be a series of
1118 concentric circles, with the color interpolated from the start circle
1119 (at t=0) to the end circle (at t=1).
1120
1121 For each point (x, y) in the span, we want to find the
1122 interpolated circle that intersects that point. The center
1123 of the desired circle (Cx, Cy) falls at some distance t
1124 along the line segment between the start point (Sx, Sy) and
1125 end point (Ex, Ey):
1126
1127 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1128 Cy = (1 - t) * Sy + t * Ey
1129
1130 The radius of the desired circle (r) is also a linear interpolation t
1131 between the start and end radii (Sr and Er):
1132
1133 r = (1 - t) * Sr + t * Er
1134
1135 But
1136
1137 (x - Cx)^2 + (y - Cy)^2 = r^2
1138
1139 so
1140
1141 (x - ((1 - t) * Sx + t * Ex))^2
1142 + (y - ((1 - t) * Sy + t * Ey))^2
1143 = ((1 - t) * Sr + t * Er)^2
1144
1145 Solving for t yields
1146
1147 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1148 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1149 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
1150
1151 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1152
1153 [Dx^2 + Dy^2 - Dr^2)] * t^2
1154 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1155 + [dx^2 + dy^2 - Sr^2] = 0
1156
1157 A quadratic in t. The two roots of the quadratic reflect the two
1158 possible circles on which the point may fall. Solving for t yields
1159 the gradient value to use.
1160
1161 If a<0, the start circle is entirely contained in the
1162 end circle, and one of the roots will be <0 or >1 (off the line
1163 segment). If a>0, the start circle falls at least partially
1164 outside the end circle (or vice versa), and the gradient
1165 defines a "tube" where a point may be on one circle (on the
1166 inside of the tube) or the other (outside of the tube). We choose
1167 one arbitrarily.
1168
1169 In order to keep the math to within the limits of fixed point,
1170 we divide the entire quadratic by Dr^2, and replace
1171 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
1172
1173 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1174 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1175 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
1176
1177 (x' and y' are computed by appending the subtract and scale to the
1178 fDstToIndex matrix in the constructor).
1179
1180 Since the 'A' component of the quadratic is independent of x' and y', it
1181 is precomputed in the constructor. Since the 'B' component is linear in
1182 x' and y', if x and y are linear in the span, 'B' can be computed
1183 incrementally with a simple delta (db below). If it is not (e.g.,
1184 a perspective projection), it must be computed in the loop.
1185
1186*/
1187
1188static inline SkFixed two_point_radial(SkFixed b, SkFixed fx, SkFixed fy, SkFixed sr2d2, SkFixed foura, SkFixed oneOverTwoA, bool posRoot) {
1189 SkFixed c = SkFixedSquare(fx) + SkFixedSquare(fy) - sr2d2;
1190 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1191 if (discrim < 0) {
1192 discrim = -discrim;
1193 }
1194 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1195 if (posRoot) {
1196 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1197 } else {
1198 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1199 }
1200}
1201
1202class Two_Point_Radial_Gradient : public Gradient_Shader {
1203public:
1204 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1205 const SkPoint& end, SkScalar endRadius,
1206 const SkColor colors[], const SkScalar pos[],
1207 int colorCount, SkShader::TileMode mode,
1208 SkUnitMapper* mapper)
1209 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
1210 {
1211 fDiff = start - end;
1212 fDiffRadius = endRadius - startRadius;
1213 SkScalar inv = SkScalarInvert(fDiffRadius);
1214 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1215 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1216 fStartRadius = SkScalarMul(startRadius, inv);
1217 fSr2D2 = SkScalarSquare(fStartRadius);
1218 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1219 fOneOverTwoA = SkScalarInvert(fA * 2);
1220
1221 fPtsToUnit.setTranslate(-start.fX, -start.fY);
1222 fPtsToUnit.postScale(inv, inv);
1223 }
1224 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1225 {
1226 SkASSERT(count > 0);
1227
1228 // Zero difference between radii: fill with transparent black.
1229 if (fDiffRadius == 0) {
1230 sk_bzero(dstC, count * sizeof(*dstC));
1231 return;
1232 }
1233 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1234 TileProc proc = fTileProc;
1235 const SkPMColor* cache = this->getCache32();
1236 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1237 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1238 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1239 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1240 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1241 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1242 bool posRoot = fDiffRadius < 0;
1243 if (fDstToIndexClass != kPerspective_MatrixClass)
1244 {
1245 SkPoint srcPt;
1246 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
1247 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1248 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1249
1250 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1251 {
1252 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1253 }
1254 else
1255 {
1256 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1257 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1258 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1259 }
1260 SkFixed b = (SkFixedMul(diffx, fx) +
1261 SkFixedMul(diffy, fy) - startRadius) << 1;
1262 SkFixed db = (SkFixedMul(diffx, dx) +
1263 SkFixedMul(diffy, dy)) << 1;
1264 if (proc == clamp_tileproc)
1265 {
1266 for (; count > 0; --count) {
1267 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1268 SkFixed index = SkClampMax(t, 0xFFFF);
1269 SkASSERT(index <= 0xFFFF);
1270 *dstC++ = cache[index >> (16 - kCache32Bits)];
1271 fx += dx;
1272 fy += dy;
1273 b += db;
1274 }
1275 }
1276 else if (proc == mirror_tileproc)
1277 {
1278 for (; count > 0; --count) {
1279 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1280 SkFixed index = mirror_tileproc(t);
1281 SkASSERT(index <= 0xFFFF);
1282 *dstC++ = cache[index >> (16 - kCache32Bits)];
1283 fx += dx;
1284 fy += dy;
1285 b += db;
1286 }
1287 }
1288 else
1289 {
1290 SkASSERT(proc == repeat_tileproc);
1291 for (; count > 0; --count) {
1292 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1293 SkFixed index = repeat_tileproc(t);
1294 SkASSERT(index <= 0xFFFF);
1295 *dstC++ = cache[index >> (16 - kCache32Bits)];
1296 fx += dx;
1297 fy += dy;
1298 b += db;
1299 }
1300 }
1301 }
1302 else // perspective case
1303 {
reed@android.com6c59a172009-09-22 20:24:05 +00001304 SkScalar dstX = SkIntToScalar(x);
1305 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001306 for (; count > 0; --count) {
1307 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001308 dstProc(fDstToIndex, dstX, dstY, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001309 SkFixed fx = SkScalarToFixed(srcPt.fX);
1310 SkFixed fy = SkScalarToFixed(srcPt.fY);
1311 SkFixed b = (SkFixedMul(diffx, fx) +
1312 SkFixedMul(diffy, fy) - startRadius) << 1;
1313 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1314 SkFixed index = proc(t);
1315 SkASSERT(index <= 0xFFFF);
1316 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001317 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001318 }
1319 }
1320 }
1321
reed@android.com6c59a172009-09-22 20:24:05 +00001322 virtual bool setContext(const SkBitmap& device,
1323 const SkPaint& paint,
1324 const SkMatrix& matrix) {
1325 if (!this->INHERITED::setContext(device, paint, matrix)) {
1326 return false;
1327 }
1328
1329 // we don't have a span16 proc
1330 fFlags &= ~kHasSpan16_Flag;
1331 return true;
1332 }
1333
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001334 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1335 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1336 }
1337
reed@android.combcfc7332009-11-10 16:19:39 +00001338 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1339 this->INHERITED::flatten(buffer);
1340 buffer.writeScalar(fDiff.fX);
1341 buffer.writeScalar(fDiff.fY);
1342 buffer.writeScalar(fStartRadius);
1343 buffer.writeScalar(fDiffRadius);
1344 buffer.writeScalar(fSr2D2);
1345 buffer.writeScalar(fA);
1346 buffer.writeScalar(fOneOverTwoA);
1347 }
1348
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001349protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001350 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
1351 : Gradient_Shader(buffer) {
1352 fDiff.fX = buffer.readScalar();
1353 fDiff.fY = buffer.readScalar();
1354 fStartRadius = buffer.readScalar();
1355 fDiffRadius = buffer.readScalar();
1356 fSr2D2 = buffer.readScalar();
1357 fA = buffer.readScalar();
1358 fOneOverTwoA = buffer.readScalar();
1359 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001360 virtual Factory getFactory() { return CreateProc; }
1361 virtual void onCacheReset() {}
1362
1363private:
1364 typedef Gradient_Shader INHERITED;
1365 SkPoint fDiff;
1366 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
1367};
1368
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369///////////////////////////////////////////////////////////////////////////////
1370
1371class Sweep_Gradient : public Gradient_Shader {
1372public:
1373 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1374 const SkScalar pos[], int count, SkUnitMapper* mapper)
1375 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper)
1376 {
1377 fPtsToUnit.setTranslate(-cx, -cy);
1378 }
1379 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1380 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
1381
1382 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1383 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1384 }
1385
1386protected:
1387 Sweep_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001388 virtual Factory getFactory() { return CreateProc; }
reed@android.comec3d6e52009-06-05 14:43:55 +00001389 virtual void onCacheReset() {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001390
1391private:
1392 typedef Gradient_Shader INHERITED;
1393};
1394
1395#ifdef COMPUTE_SWEEP_TABLE
1396#define PI 3.14159265
1397static bool gSweepTableReady;
1398static uint8_t gSweepTable[65];
1399
1400/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1401 We scale the results to [0..32]
1402*/
1403static const uint8_t* build_sweep_table()
1404{
1405 if (!gSweepTableReady)
1406 {
1407 const int N = 65;
1408 const double DENOM = N - 1;
1409
1410 for (int i = 0; i < N; i++)
1411 {
1412 double arg = i / DENOM;
1413 double v = atan(arg);
1414 int iv = (int)round(v * DENOM * 2 / PI);
1415// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1416 printf("%d, ", iv);
1417 gSweepTable[i] = iv;
1418 }
1419 gSweepTableReady = true;
1420 }
1421 return gSweepTable;
1422}
1423#else
1424static const uint8_t gSweepTable[] = {
1425 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1426 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1427 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1428 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1429 32
1430};
1431static const uint8_t* build_sweep_table() { return gSweepTable; }
1432#endif
1433
1434// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1435// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1436// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1437
1438//unsigned div_64(int numer, int denom);
1439static unsigned div_64(int numer, int denom)
1440{
1441 SkASSERT(numer <= denom);
1442 SkASSERT(numer > 0);
1443 SkASSERT(denom > 0);
1444
1445 int nbits = SkCLZ(numer);
1446 int dbits = SkCLZ(denom);
1447 int bits = 6 - nbits + dbits;
1448 SkASSERT(bits <= 6);
1449
1450 if (bits < 0) // detect underflow
1451 return 0;
1452
1453 denom <<= dbits - 1;
1454 numer <<= nbits - 1;
1455
1456 unsigned result = 0;
1457
1458 // do the first one
1459 if ((numer -= denom) >= 0)
1460 result = 1;
1461 else
1462 numer += denom;
1463
1464 // Now fall into our switch statement if there are more bits to compute
1465 if (bits > 0)
1466 {
1467 // make room for the rest of the answer bits
1468 result <<= bits;
1469 switch (bits) {
1470 case 6:
1471 if ((numer = (numer << 1) - denom) >= 0)
1472 result |= 32;
1473 else
1474 numer += denom;
1475 case 5:
1476 if ((numer = (numer << 1) - denom) >= 0)
1477 result |= 16;
1478 else
1479 numer += denom;
1480 case 4:
1481 if ((numer = (numer << 1) - denom) >= 0)
1482 result |= 8;
1483 else
1484 numer += denom;
1485 case 3:
1486 if ((numer = (numer << 1) - denom) >= 0)
1487 result |= 4;
1488 else
1489 numer += denom;
1490 case 2:
1491 if ((numer = (numer << 1) - denom) >= 0)
1492 result |= 2;
1493 else
1494 numer += denom;
1495 case 1:
1496 default: // not strictly need, but makes GCC make better ARM code
1497 if ((numer = (numer << 1) - denom) >= 0)
1498 result |= 1;
1499 else
1500 numer += denom;
1501 }
1502 }
1503 return result;
1504}
1505
1506// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1507static unsigned atan_0_90(SkFixed y, SkFixed x)
1508{
1509#ifdef SK_DEBUG
1510 {
1511 static bool gOnce;
1512 if (!gOnce)
1513 {
1514 gOnce = true;
1515 SkASSERT(div_64(55, 55) == 64);
1516 SkASSERT(div_64(128, 256) == 32);
1517 SkASSERT(div_64(2326528, 4685824) == 31);
1518 SkASSERT(div_64(753664, 5210112) == 9);
1519 SkASSERT(div_64(229376, 4882432) == 3);
1520 SkASSERT(div_64(2, 64) == 2);
1521 SkASSERT(div_64(1, 64) == 1);
1522 // test that we handle underflow correctly
1523 SkASSERT(div_64(12345, 0x54321234) == 0);
1524 }
1525 }
1526#endif
1527
1528 SkASSERT(y > 0 && x > 0);
1529 const uint8_t* table = build_sweep_table();
1530
1531 unsigned result;
1532 bool swap = (x < y);
1533 if (swap)
1534 {
1535 // first part of the atan(v) = PI/2 - atan(1/v) identity
1536 // since our div_64 and table want v <= 1, where v = y/x
1537 SkTSwap<SkFixed>(x, y);
1538 }
1539
1540 result = div_64(y, x);
1541
1542#ifdef SK_DEBUG
1543 {
1544 unsigned result2 = SkDivBits(y, x, 6);
1545 SkASSERT(result2 == result ||
1546 (result == 1 && result2 == 0));
1547 }
1548#endif
1549
1550 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1551 result = table[result];
1552
1553 if (swap)
1554 {
1555 // complete the atan(v) = PI/2 - atan(1/v) identity
1556 result = 64 - result;
1557 // pin to 63
1558 result -= result >> 6;
1559 }
1560
1561 SkASSERT(result <= 63);
1562 return result;
1563}
1564
1565// returns angle in a circle [0..2PI) -> [0..255]
1566static unsigned SkATan2_255(SkFixed y, SkFixed x)
1567{
1568 if (x == 0)
1569 {
1570 if (y == 0)
1571 return 0;
1572 return y < 0 ? 192 : 64;
1573 }
1574 if (y == 0)
1575 return x < 0 ? 128 : 0;
1576
1577 /* Find the right quadrant for x,y
1578 Since atan_0_90 only handles the first quadrant, we rotate x,y
1579 appropriately before calling it, and then add the right amount
1580 to account for the real quadrant.
1581 quadrant 0 : add 0 | x > 0 && y > 0
1582 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1583 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1584 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
1585
1586 map x<0 to (1 << 6)
1587 map y<0 to (3 << 6)
1588 add = map_x ^ map_y
1589 */
1590 int xsign = x >> 31;
1591 int ysign = y >> 31;
1592 int add = ((-xsign) ^ (ysign & 3)) << 6;
1593
1594#ifdef SK_DEBUG
1595 if (0 == add)
1596 SkASSERT(x > 0 && y > 0);
1597 else if (64 == add)
1598 SkASSERT(x < 0 && y > 0);
1599 else if (128 == add)
1600 SkASSERT(x < 0 && y < 0);
1601 else if (192 == add)
1602 SkASSERT(x > 0 && y < 0);
1603 else
1604 SkASSERT(!"bad value for add");
1605#endif
1606
1607 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1608 where we need to rotate x,y by 90 or -90
1609 */
1610 x = (x ^ xsign) - xsign;
1611 y = (y ^ ysign) - ysign;
1612 if (add & 64) // quads 1 or 3 need to swap x,y
1613 SkTSwap<SkFixed>(x, y);
1614
1615 unsigned result = add + atan_0_90(y, x);
1616 SkASSERT(result < 256);
1617 return result;
1618}
1619
1620void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1621{
1622 SkMatrix::MapXYProc proc = fDstToIndexProc;
1623 const SkMatrix& matrix = fDstToIndex;
1624 const SkPMColor* cache = this->getCache32();
1625 SkPoint srcPt;
1626
1627 if (fDstToIndexClass != kPerspective_MatrixClass)
1628 {
1629 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1630 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1631 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1632 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1633
1634 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1635 {
1636 SkFixed storage[2];
1637 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1638 &storage[0], &storage[1]);
1639 dx = storage[0];
1640 dy = storage[1];
1641 }
1642 else
1643 {
1644 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1645 dx = SkScalarToFixed(matrix.getScaleX());
1646 dy = SkScalarToFixed(matrix.getSkewY());
1647 }
1648
1649 for (; count > 0; --count)
1650 {
1651 *dstC++ = cache[SkATan2_255(fy, fx)];
1652 fx += dx;
1653 fy += dy;
1654 }
1655 }
1656 else // perspective case
1657 {
1658 for (int stop = x + count; x < stop; x++)
1659 {
1660 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1661 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1662
1663 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1664 SkScalarToFixed(srcPt.fX));
1665 *dstC++ = cache[index];
1666 }
1667 }
1668}
1669
1670void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1671{
1672 SkMatrix::MapXYProc proc = fDstToIndexProc;
1673 const SkMatrix& matrix = fDstToIndex;
1674 const uint16_t* cache = this->getCache16();
1675 int toggle = ((x ^ y) & 1) << kCache16Bits;
1676 SkPoint srcPt;
1677
1678 if (fDstToIndexClass != kPerspective_MatrixClass)
1679 {
1680 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1681 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1682 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1683 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1684
1685 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1686 {
1687 SkFixed storage[2];
1688 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1689 &storage[0], &storage[1]);
1690 dx = storage[0];
1691 dy = storage[1];
1692 }
1693 else
1694 {
1695 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1696 dx = SkScalarToFixed(matrix.getScaleX());
1697 dy = SkScalarToFixed(matrix.getSkewY());
1698 }
1699
1700 for (; count > 0; --count)
1701 {
1702 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1703 *dstC++ = cache[toggle + index];
1704 toggle ^= (1 << kCache16Bits);
1705 fx += dx;
1706 fy += dy;
1707 }
1708 }
1709 else // perspective case
1710 {
1711 for (int stop = x + count; x < stop; x++)
1712 {
1713 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1714 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1715
1716 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1717 SkScalarToFixed(srcPt.fX));
1718 index >>= (8 - kCache16Bits);
1719 *dstC++ = cache[toggle + index];
1720 toggle ^= (1 << kCache16Bits);
1721 }
1722 }
1723}
1724
1725///////////////////////////////////////////////////////////////////////////
1726///////////////////////////////////////////////////////////////////////////
1727
1728// assumes colors is SkColor* and pos is SkScalar*
1729#define EXPAND_1_COLOR(count) \
1730 SkColor tmp[2]; \
1731 do { \
1732 if (1 == count) { \
1733 tmp[0] = tmp[1] = colors[0]; \
1734 colors = tmp; \
1735 pos = NULL; \
1736 count = 2; \
1737 } \
1738 } while (0)
1739
1740SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
1741 const SkColor colors[], const SkScalar pos[], int colorCount,
1742 SkShader::TileMode mode, SkUnitMapper* mapper)
1743{
1744 if (NULL == pts || NULL == colors || colorCount < 1) {
1745 return NULL;
1746 }
1747 EXPAND_1_COLOR(colorCount);
1748
reed@android.comab840b82009-07-01 17:00:03 +00001749 return SkNEW_ARGS(Linear_Gradient,
1750 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751}
1752
1753SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
1754 const SkColor colors[], const SkScalar pos[], int colorCount,
1755 SkShader::TileMode mode, SkUnitMapper* mapper)
1756{
1757 if (radius <= 0 || NULL == colors || colorCount < 1) {
1758 return NULL;
1759 }
1760 EXPAND_1_COLOR(colorCount);
1761
reed@android.comab840b82009-07-01 17:00:03 +00001762 return SkNEW_ARGS(Radial_Gradient,
1763 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001764}
1765
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001766SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
1767 SkScalar startRadius,
1768 const SkPoint& end,
1769 SkScalar endRadius,
1770 const SkColor colors[],
1771 const SkScalar pos[],
1772 int colorCount,
1773 SkShader::TileMode mode,
1774 SkUnitMapper* mapper)
1775{
1776 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
1777 return NULL;
1778 }
1779 EXPAND_1_COLOR(colorCount);
1780
1781 return SkNEW_ARGS(Two_Point_Radial_Gradient,
1782 (start, startRadius, end, endRadius, colors, pos, colorCount, mode, mapper));
1783}
1784
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
1786 const SkColor colors[],
1787 const SkScalar pos[],
1788 int count, SkUnitMapper* mapper)
1789{
1790 if (NULL == colors || count < 1) {
1791 return NULL;
1792 }
1793 EXPAND_1_COLOR(count);
1794
1795 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
1796}
1797
1798static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
1799 Linear_Gradient::CreateProc);
1800
1801static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
1802 Radial_Gradient::CreateProc);
1803
1804static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
1805 Sweep_Gradient::CreateProc);
1806