blob: d629c57a1e686883487400f3cf71fe4db726bc76 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkGradientShader.h"
11#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000012#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkUnitMapper.h"
14#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000015#include "SkTemplates.h"
16#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
reed@google.com51baf5a2011-09-21 13:38:36 +000018#if defined(SK_SCALAR_IS_FLOAT) && !defined(SK_DONT_USE_FLOAT_SQRT)
19 #define SK_USE_FLOAT_SQRT
20#endif
21
reed@google.com9c7443d2011-01-17 18:46:37 +000022#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
23 #define USE_DITHER_32BIT_GRADIENT
24#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +000025
reed@google.com5eb158d2011-04-15 15:50:34 +000026static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
27 int count) {
28 if (count > 0) {
29 if (v0 == v1) {
30 sk_memset32(dst, v0, count);
31 } else {
32 int pairs = count >> 1;
33 for (int i = 0; i < pairs; i++) {
34 *dst++ = v0;
35 *dst++ = v1;
36 }
37 if (count & 1) {
38 *dst = v0;
39 }
40 }
41 }
42}
43
reed@google.com61eb0402011-04-15 12:11:12 +000044///////////////////////////////////////////////////////////////////////////////
tomhudson@google.com9ce767c2011-04-25 20:49:39 +000045// Can't use a two-argument function with side effects like this in a
46// constructor's initializer's argument list because the order of
47// evaluations in that context is undefined (and backwards on linux/gcc).
48static SkPoint unflatten_point(SkReader32& buffer) {
49 SkPoint retval;
50 retval.fX = buffer.readScalar();
51 retval.fY = buffer.readScalar();
52 return retval;
53}
54
55///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000056
57typedef SkFixed (*TileProc)(SkFixed);
58
reed@android.com41bccf52009-04-03 13:33:51 +000059static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000060 return SkClampMax(x, 0xFFFF);
61}
62
reed@android.com41bccf52009-04-03 13:33:51 +000063static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 return x & 0xFFFF;
65}
66
reed@android.com41bccf52009-04-03 13:33:51 +000067static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 int s = x << 15 >> 31;
69 return (x ^ s) & 0xFFFF;
70}
71
72static const TileProc gTileProcs[] = {
73 clamp_tileproc,
74 repeat_tileproc,
75 mirror_tileproc
76};
77
reed@google.com61eb0402011-04-15 12:11:12 +000078///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000079
reed@android.com200645d2009-12-14 16:41:57 +000080static inline int repeat_bits(int x, const int bits) {
81 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082}
83
reed@android.com200645d2009-12-14 16:41:57 +000084static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000085#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000086 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000087 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000088 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000089#else
reed@android.com200645d2009-12-14 16:41:57 +000090 int s = x << (31 - bits) >> 31;
91 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000092#endif
93}
94
reed@android.com41bccf52009-04-03 13:33:51 +000095static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 return x & 0xFF;
97}
98
reed@android.com41bccf52009-04-03 13:33:51 +000099static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +0000101 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +0000103 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 return x & 255;
105#else
106 int s = x << 23 >> 31;
107 return (x ^ s) & 0xFF;
108#endif
109}
110
reed@google.com61eb0402011-04-15 12:11:12 +0000111///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112
113class Gradient_Shader : public SkShader {
114public:
115 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000116 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 virtual ~Gradient_Shader();
118
119 // overrides
120 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
121 virtual uint32_t getFlags() { return fFlags; }
122
123protected:
124 Gradient_Shader(SkFlattenableReadBuffer& );
125 SkUnitMapper* fMapper;
126 SkMatrix fPtsToUnit; // set by subclass
127 SkMatrix fDstToIndex;
128 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 TileMode fTileMode;
130 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000131 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132 uint8_t fDstToIndexClass;
133 uint8_t fFlags;
134
135 struct Rec {
136 SkFixed fPos; // 0...1
137 uint32_t fScale; // (1 << 24) / range
138 };
139 Rec* fRecs;
140
141 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000142 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000144 kCache16Mask = kCache16Count - 1,
145 kCache16Shift = 16 - kCache16Bits,
146
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 kCache32Bits = 8, // pretty much should always be 8
148 kCache32Count = 1 << kCache32Bits
149 };
150 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000151 const uint16_t* getCache16() const;
152 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153
reed@google.com7c2f27d2011-03-07 19:29:00 +0000154 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000155 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000156
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157private:
158 enum {
159 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
160
reed@android.com1c12abe2009-07-02 15:01:02 +0000161 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 };
163 SkColor fStorage[(kStorageSize + 3) >> 2];
164 SkColor* fOrigColors;
165
reed@google.com7c2f27d2011-03-07 19:29:00 +0000166 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
167 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168
reed@google.com7c2f27d2011-03-07 19:29:00 +0000169 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
170 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000171 mutable unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172
reed@android.com512a8762009-12-14 15:25:36 +0000173 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000174 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
175 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000176 void setCacheAlpha(U8CPU alpha) const;
reed@android.com512a8762009-12-14 15:25:36 +0000177
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178 typedef SkShader INHERITED;
179};
180
reed@android.com41bccf52009-04-03 13:33:51 +0000181static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182 SkASSERT(x >= 0 && x <= SK_Scalar1);
183
184#ifdef SK_SCALAR_IS_FLOAT
185 return (unsigned)(x * 0xFFFF);
186#else
187 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
188#endif
189}
190
reed@android.com41bccf52009-04-03 13:33:51 +0000191Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
192 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 SkASSERT(colorCount > 1);
194
195 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
196
197 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000198 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000199
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
201 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
202 fTileMode = mode;
203 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000204
reed@android.com41bccf52009-04-03 13:33:51 +0000205 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000206 fCache32 = NULL;
207 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208
reed@android.com41bccf52009-04-03 13:33:51 +0000209 /* Note: we let the caller skip the first and/or last position.
210 i.e. pos[0] = 0.3, pos[1] = 0.7
211 In these cases, we insert dummy entries to ensure that the final data
212 will be bracketed by [0, 1].
213 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
214
215 Thus colorCount (the caller's value, and fColorCount (our value) may
216 differ by up to 2. In the above example:
217 colorCount = 2
218 fColorCount = 4
219 */
220 fColorCount = colorCount;
221 // check if we need to add in dummy start and/or end position/colors
222 bool dummyFirst = false;
223 bool dummyLast = false;
224 if (pos) {
225 dummyFirst = pos[0] != 0;
226 dummyLast = pos[colorCount - 1] != SK_Scalar1;
227 fColorCount += dummyFirst + dummyLast;
228 }
229
230 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000231 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000232 fOrigColors = reinterpret_cast<SkColor*>(
233 sk_malloc_throw(size * fColorCount));
234 }
235 else {
236 fOrigColors = fStorage;
237 }
238
239 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 {
reed@android.com41bccf52009-04-03 13:33:51 +0000241 SkColor* origColors = fOrigColors;
242 if (dummyFirst) {
243 *origColors++ = colors[0];
244 }
245 memcpy(origColors, colors, colorCount * sizeof(SkColor));
246 if (dummyLast) {
247 origColors += colorCount;
248 *origColors = colors[colorCount - 1];
249 }
250 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000251
reed@android.com1c12abe2009-07-02 15:01:02 +0000252 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000253 if (fColorCount > 2) {
254 Rec* recs = fRecs;
255 recs->fPos = 0;
256 // recs->fScale = 0; // unused;
257 recs += 1;
258 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000259 /* We need to convert the user's array of relative positions into
260 fixed-point positions and scale factors. We need these results
261 to be strictly monotonic (no two values equal or out of order).
262 Hence this complex loop that just jams a zero for the scale
263 value if it sees a segment out of order, and it assures that
264 we start at 0 and end at 1.0
265 */
266 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000267 int startIndex = dummyFirst ? 0 : 1;
268 int count = colorCount + dummyLast;
269 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000270 // force the last value to be 1.0
271 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000272 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000273 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000274 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000275 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000276 }
reed@android.com41bccf52009-04-03 13:33:51 +0000277 // pin curr withing range
278 if (curr < 0) {
279 curr = 0;
280 } else if (curr > SK_Fixed1) {
281 curr = SK_Fixed1;
282 }
283 recs->fPos = curr;
284 if (curr > prev) {
285 recs->fScale = (1 << 24) / (curr - prev);
286 } else {
287 recs->fScale = 0; // ignore this segment
288 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289 // get ready for the next value
290 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000291 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000292 }
reed@android.com41bccf52009-04-03 13:33:51 +0000293 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000294 SkFixed dp = SK_Fixed1 / (colorCount - 1);
295 SkFixed p = dp;
296 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000297 for (int i = 1; i < colorCount; i++) {
298 recs->fPos = p;
299 recs->fScale = scale;
300 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 p += dp;
302 }
303 }
304 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000305 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306}
307
308Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000309 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 fCacheAlpha = 256;
311
312 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
313
314 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000315 fCache32 = NULL;
316 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317
reed@android.com41bccf52009-04-03 13:33:51 +0000318 int colorCount = fColorCount = buffer.readU32();
319 if (colorCount > kColorStorageCount) {
320 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
321 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
322 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000324 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326
327 fTileMode = (TileMode)buffer.readU8();
328 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000329 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330 if (colorCount > 2) {
331 Rec* recs = fRecs;
332 recs[0].fPos = 0;
333 for (int i = 1; i < colorCount; i++) {
334 recs[i].fPos = buffer.readS32();
335 recs[i].fScale = buffer.readU32();
336 }
337 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000338 SkReadMatrix(&buffer, &fPtsToUnit);
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000339 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340}
341
reed@android.com41bccf52009-04-03 13:33:51 +0000342Gradient_Shader::~Gradient_Shader() {
343 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000345 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000346 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000347 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000348 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000349 }
reed@google.com82065d62011-02-07 15:30:46 +0000350 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351}
352
reed@android.com41bccf52009-04-03 13:33:51 +0000353void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354 this->INHERITED::flatten(buffer);
355 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000356 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000357 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
358 buffer.write8(fTileMode);
359 if (fColorCount > 2) {
360 Rec* recs = fRecs;
361 for (int i = 1; i < fColorCount; i++) {
362 buffer.write32(recs[i].fPos);
363 buffer.write32(recs[i].fScale);
364 }
365 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000366 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367}
368
369bool Gradient_Shader::setContext(const SkBitmap& device,
370 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000371 const SkMatrix& matrix) {
372 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000374 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000375
376 const SkMatrix& inverse = this->getTotalInverse();
377
378 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
379 return false;
380 }
381
382 fDstToIndexProc = fDstToIndex.getMapXYProc();
383 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
384
385 // now convert our colors in to PMColors
386 unsigned paintAlpha = this->getPaintAlpha();
387 unsigned colorAlpha = 0xFF;
388
reed@android.com3d06a8c2009-07-07 18:19:59 +0000389 // FIXME: record colorAlpha in constructor, since this is not affected
390 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000391 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000392 SkColor src = fOrigColors[i];
393 unsigned sa = SkColorGetA(src);
394 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000395 }
396
397 fFlags = this->INHERITED::getFlags();
398 if ((colorAlpha & paintAlpha) == 0xFF) {
399 fFlags |= kOpaqueAlpha_Flag;
400 }
401 // we can do span16 as long as our individual colors are opaque,
402 // regardless of the paint's alpha
403 if (0xFF == colorAlpha) {
404 fFlags |= kHasSpan16_Flag;
405 }
406
reed@google.com95eed982011-07-05 17:01:56 +0000407 this->setCacheAlpha(paintAlpha);
408 return true;
409}
410
411void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412 // if the new alpha differs from the previous time we were called, inval our cache
413 // this will trigger the cache to be rebuilt.
414 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000415 if (fCacheAlpha != alpha) {
416 fCache16 = NULL; // inval the cache
417 fCache32 = NULL; // inval the cache
418 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000419 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000420 if (fCache32PixelRef) {
421 fCache32PixelRef->notifyPixelsChanged();
422 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000423 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424}
425
reed@android.com41bccf52009-04-03 13:33:51 +0000426static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000427 SkASSERT(a == SkToU8(a));
428 SkASSERT(b == SkToU8(b));
429 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 return a + ((b - a) * scale >> 8);
431}
432
reed@android.com41bccf52009-04-03 13:33:51 +0000433static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
434 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435#if 0
436 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
437 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
438 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
439 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
440
441 return SkPackARGB32(a, r, g, b);
442#else
443 int otherBlend = 256 - blend;
444
445#if 0
446 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
447 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
448 SkASSERT((t0 & t1) == 0);
449 return t0 | t1;
450#else
451 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
452 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
453#endif
454
455#endif
456}
457
458#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
459
reed@android.com41bccf52009-04-03 13:33:51 +0000460/** We take the original colors, not our premultiplied PMColors, since we can
461 build a 16bit table as long as the original colors are opaque, even if the
462 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000463*/
reed@android.com512a8762009-12-14 15:25:36 +0000464void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
465 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000466 SkASSERT(count > 1);
467 SkASSERT(SkColorGetA(c0) == 0xFF);
468 SkASSERT(SkColorGetA(c1) == 0xFF);
469
470 SkFixed r = SkColorGetR(c0);
471 SkFixed g = SkColorGetG(c0);
472 SkFixed b = SkColorGetB(c0);
473
474 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
475 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
476 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
477
478 r = SkIntToFixed(r) + 0x8000;
479 g = SkIntToFixed(g) + 0x8000;
480 b = SkIntToFixed(b) + 0x8000;
481
482 do {
483 unsigned rr = r >> 16;
484 unsigned gg = g >> 16;
485 unsigned bb = b >> 16;
486 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000487 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000488 cache += 1;
489 r += dr;
490 g += dg;
491 b += db;
492 } while (--count != 0);
493}
494
reed@google.com55b8e8c2011-01-13 16:22:35 +0000495/*
496 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
497 * semantics of how we 2x2 dither 32->16
498 */
499static inline U8CPU dither_fixed_to_8(SkFixed n) {
500 n >>= 8;
501 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
502}
503
504/*
505 * For dithering with premultiply, we want to ceiling the alpha component,
506 * to ensure that it is always >= any color component.
507 */
508static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
509 n >>= 8;
510 return ((n << 1) - (n | (n >> 8))) >> 8;
511}
512
513void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
514 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000515 SkASSERT(count > 1);
516
reed@android.com1c12abe2009-07-02 15:01:02 +0000517 // need to apply paintAlpha to our two endpoints
518 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
519 SkFixed da;
520 {
521 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
522 da = SkIntToFixed(tmp - a) / (count - 1);
523 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000524
reed@android.com1c12abe2009-07-02 15:01:02 +0000525 SkFixed r = SkColorGetR(c0);
526 SkFixed g = SkColorGetG(c0);
527 SkFixed b = SkColorGetB(c0);
528 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
529 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
530 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531
532 a = SkIntToFixed(a) + 0x8000;
533 r = SkIntToFixed(r) + 0x8000;
534 g = SkIntToFixed(g) + 0x8000;
535 b = SkIntToFixed(b) + 0x8000;
536
537 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000538 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
539 cache[kCache32Count] = SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
540 dither_fixed_to_8(r),
541 dither_fixed_to_8(g),
542 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000543 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000544 a += da;
545 r += dr;
546 g += dg;
547 b += db;
548 } while (--count != 0);
549}
550
reed@android.com41bccf52009-04-03 13:33:51 +0000551static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000552 SkASSERT((unsigned)x <= SK_Fixed1);
553 return x - (x >> 16);
554}
555
reed@android.com200645d2009-12-14 16:41:57 +0000556static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000557 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000558 if (6 == bits) {
559 return (x << 10) | (x << 4) | (x >> 2);
560 }
561 if (8 == bits) {
562 return (x << 8) | x;
563 }
564 sk_throw();
565 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000566}
567
reed@google.com7c2f27d2011-03-07 19:29:00 +0000568const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000569 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000570 // double the count for dither entries
571 const int entryCount = kCache16Count * 2;
572 const size_t allocSize = sizeof(uint16_t) * entryCount;
573
reed@android.com3c9b2a42009-08-27 19:28:37 +0000574 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000575 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000576 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000578 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000579 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000580 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581 Rec* rec = fRecs;
582 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000583 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000584 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585 SkASSERT(nextIndex < kCache16Count);
586
587 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000588 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589 prevIndex = nextIndex;
590 }
591 SkASSERT(prevIndex == kCache16Count - 1);
592 }
593
reed@android.com41bccf52009-04-03 13:33:51 +0000594 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000595 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 uint16_t* linear = fCache16; // just computed linear data
597 uint16_t* mapped = fCache16Storage; // storage for mapped data
598 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000599 for (int i = 0; i < kCache16Count; i++) {
600 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000602 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603 }
604 sk_free(fCache16);
605 fCache16 = fCache16Storage;
606 }
607 }
608 return fCache16;
609}
610
reed@google.com7c2f27d2011-03-07 19:29:00 +0000611const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000612 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000613 // double the count for dither entries
614 const int entryCount = kCache32Count * 2;
615 const size_t allocSize = sizeof(SkPMColor) * entryCount;
616
reed@google.comdc731fd2010-12-23 15:19:47 +0000617 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000618 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
619 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000620 }
621 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000622 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000623 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
624 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000625 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626 Rec* rec = fRecs;
627 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000628 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
630 SkASSERT(nextIndex < kCache32Count);
631
632 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000633 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
634 fOrigColors[i],
635 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 prevIndex = nextIndex;
637 }
638 SkASSERT(prevIndex == kCache32Count - 1);
639 }
640
reed@android.com41bccf52009-04-03 13:33:51 +0000641 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000642 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000643 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000645 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000647 for (int i = 0; i < kCache32Count; i++) {
648 int index = map->mapUnit16((i << 8) | i) >> 8;
649 mapped[i] = linear[index];
650 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000651 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000652 fCache32PixelRef->unref();
653 fCache32PixelRef = newPR;
654 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655 }
656 }
657 return fCache32;
658}
659
reed@google.comdc731fd2010-12-23 15:19:47 +0000660/*
661 * Because our caller might rebuild the same (logically the same) gradient
662 * over and over, we'd like to return exactly the same "bitmap" if possible,
663 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
664 * To do that, we maintain a private cache of built-bitmaps, based on our
665 * colors and positions. Note: we don't try to flatten the fMapper, so if one
666 * is present, we skip the cache for now.
667 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000668void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000669 // our caller assumes no external alpha, so we ensure that our cache is
670 // built with 0xFF
671 this->setCacheAlpha(0xFF);
672
reed@google.comdc731fd2010-12-23 15:19:47 +0000673 // don't have a way to put the mapper into our cache-key yet
674 if (fMapper) {
675 // force our cahce32pixelref to be built
676 (void)this->getCache32();
677 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
678 bitmap->setPixelRef(fCache32PixelRef);
679 return;
680 }
681
682 // build our key: [numColors + colors[] + {positions[]} ]
683 int count = 1 + fColorCount;
684 if (fColorCount > 2) {
685 count += fColorCount - 1; // fRecs[].fPos
686 }
687
688 SkAutoSTMalloc<16, int32_t> storage(count);
689 int32_t* buffer = storage.get();
690
691 *buffer++ = fColorCount;
692 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
693 buffer += fColorCount;
694 if (fColorCount > 2) {
695 for (int i = 1; i < fColorCount; i++) {
696 *buffer++ = fRecs[i].fPos;
697 }
698 }
699 SkASSERT(buffer - storage.get() == count);
700
701 ///////////////////////////////////
702
703 static SkMutex gMutex;
704 static SkBitmapCache* gCache;
705 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
706 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
707 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000708
reed@google.comdc731fd2010-12-23 15:19:47 +0000709 if (NULL == gCache) {
710 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
711 }
712 size_t size = count * sizeof(int32_t);
713
714 if (!gCache->find(storage.get(), size, bitmap)) {
715 // force our cahce32pixelref to be built
716 (void)this->getCache32();
717 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
718 bitmap->setPixelRef(fCache32PixelRef);
719
720 gCache->add(storage.get(), size, *bitmap);
721 }
722}
723
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000724void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
725 if (info) {
726 if (info->fColorCount >= fColorCount) {
727 if (info->fColors) {
728 memcpy(info->fColors, fOrigColors,
729 fColorCount * sizeof(SkColor));
730 }
731 if (info->fColorOffsets) {
732 if (fColorCount == 2) {
733 info->fColorOffsets[0] = 0;
734 info->fColorOffsets[1] = SK_Scalar1;
735 } else if (fColorCount > 2) {
736 for (int i = 0; i < fColorCount; i++)
737 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
738 }
739 }
740 }
741 info->fColorCount = fColorCount;
742 info->fTileMode = fTileMode;
743 }
744}
745
reed@google.com61eb0402011-04-15 12:11:12 +0000746///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747
reed@android.com41bccf52009-04-03 13:33:51 +0000748static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000749 SkVector vec = pts[1] - pts[0];
750 SkScalar mag = vec.length();
751 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
752
753 vec.scale(inv);
754 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
755 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
756 matrix->postScale(inv, inv);
757}
758
759///////////////////////////////////////////////////////////////////////////////
760
761class Linear_Gradient : public Gradient_Shader {
762public:
763 Linear_Gradient(const SkPoint pts[2],
764 const SkColor colors[], const SkScalar pos[], int colorCount,
765 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000766 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
767 fStart(pts[0]),
768 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769 {
770 pts_to_unit_matrix(pts, &fPtsToUnit);
771 }
reed@android.com9b46e772009-06-05 12:24:41 +0000772
reed@android.com5119bdb2009-06-12 21:27:03 +0000773 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
775 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000776 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*,
reed@google.com7c2f27d2011-03-07 19:29:00 +0000777 TileMode*, SkScalar* twoPointRadialParams) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000778 virtual GradientType asAGradient(GradientInfo* info) const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779
reed@google.com55b8e8c2011-01-13 16:22:35 +0000780 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781 return SkNEW_ARGS(Linear_Gradient, (buffer));
782 }
783
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000784 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
785 this->INHERITED::flatten(buffer);
786 buffer.writeScalar(fStart.fX);
787 buffer.writeScalar(fStart.fY);
788 buffer.writeScalar(fEnd.fX);
789 buffer.writeScalar(fEnd.fY);
790 }
791
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000793 Linear_Gradient(SkFlattenableReadBuffer& buffer)
794 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000795 fStart(unflatten_point(buffer)),
796 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000797 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 virtual Factory getFactory() { return CreateProc; }
799
800private:
801 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000802 const SkPoint fStart;
803 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804};
805
reed@android.com5119bdb2009-06-12 21:27:03 +0000806bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
807 const SkMatrix& matrix) {
808 if (!this->INHERITED::setContext(device, paint, matrix)) {
809 return false;
810 }
811
812 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
813 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000814 fFlags |= SkShader::kConstInY32_Flag;
815 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
816 // only claim this if we do have a 16bit mode (i.e. none of our
817 // colors have alpha), and if we are not dithering (which obviously
818 // is not const in Y).
819 fFlags |= SkShader::kConstInY16_Flag;
820 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000821 }
822 return true;
823}
824
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@google.com61eb0402011-04-15 12:11:12 +0000826static inline bool no_need_for_clamp(int fx, int dx, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000827 SkASSERT(count > 0);
828 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
829}
830
reed@google.com5eb158d2011-04-15 15:50:34 +0000831#include "SkClampRange.h"
832
833#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000834 do { \
835 unsigned fi = fx >> 8; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000836 SkASSERT(fi <= 0xFF); \
837 fx += dx; \
838 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000839 toggle ^= TOGGLE_MASK; \
840 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000841
842
reed@google.comdd5bd672011-09-20 15:56:13 +0000843void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844 SkASSERT(count > 0);
845
846 SkPoint srcPt;
847 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
848 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +0000849 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000850#ifdef USE_DITHER_32BIT_GRADIENT
851 int toggle = ((x ^ y) & 1) << kCache32Bits;
852 const int TOGGLE_MASK = (1 << kCache32Bits);
853#else
854 int toggle = 0;
855 const int TOGGLE_MASK = 0;
856#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857
reed@android.comc552a432009-06-12 20:02:50 +0000858 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000859 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
860 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
862
reed@android.comc552a432009-06-12 20:02:50 +0000863 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864 SkFixed dxStorage[1];
865 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
866 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000867 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
869 dx = SkScalarToFixed(fDstToIndex.getScaleX());
870 }
871
reed@android.comc552a432009-06-12 20:02:50 +0000872 if (SkFixedNearlyZero(dx)) {
873 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874 unsigned fi = proc(fx);
875 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000876 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000878 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000879 SkClampRange range;
880 range.init(fx, dx, count, 0, 0xFF);
881
882 if ((count = range.fCount0) > 0) {
883 sk_memset32_dither(dstC,
884 cache[toggle + range.fV0],
885 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
886 count);
887 dstC += count;
888 }
889 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +0000890 int unroll = count >> 3;
891 fx = range.fFx1;
892 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000893 NO_CHECK_ITER; NO_CHECK_ITER;
894 NO_CHECK_ITER; NO_CHECK_ITER;
895 NO_CHECK_ITER; NO_CHECK_ITER;
896 NO_CHECK_ITER; NO_CHECK_ITER;
897 }
898 if ((count &= 7) > 0) {
899 do {
900 NO_CHECK_ITER;
901 } while (--count != 0);
902 }
903 }
904 if ((count = range.fCount2) > 0) {
905 sk_memset32_dither(dstC,
906 cache[toggle + range.fV1],
907 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
908 count);
909 }
reed@android.comc552a432009-06-12 20:02:50 +0000910 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000911 do {
912 unsigned fi = mirror_8bits(fx >> 8);
913 SkASSERT(fi <= 0xFF);
914 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000915 *dstC++ = cache[toggle + fi];
916 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000917 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000918 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000919 SkASSERT(proc == repeat_tileproc);
920 do {
921 unsigned fi = repeat_8bits(fx >> 8);
922 SkASSERT(fi <= 0xFF);
923 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000924 *dstC++ = cache[toggle + fi];
925 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000926 } while (--count != 0);
927 }
reed@android.comc552a432009-06-12 20:02:50 +0000928 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000929 SkScalar dstX = SkIntToScalar(x);
930 SkScalar dstY = SkIntToScalar(y);
931 do {
932 dstProc(fDstToIndex, dstX, dstY, &srcPt);
933 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
934 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000935 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
936 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000937 dstX += SK_Scalar1;
938 } while (--count != 0);
939 }
940}
941
reed@google.com55b8e8c2011-01-13 16:22:35 +0000942SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000943 SkMatrix* matrix,
944 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000945 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000947 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000948 }
949 if (matrix) {
950 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
951 matrix->preConcat(fPtsToUnit);
952 }
953 if (xy) {
954 xy[0] = fTileMode;
955 xy[1] = kClamp_TileMode;
956 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000957 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000958}
959
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000960SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
961 if (info) {
962 commonAsAGradient(info);
963 info->fPoint[0] = fStart;
964 info->fPoint[1] = fEnd;
965 }
966 return kLinear_GradientType;
967}
968
reed@android.com3c9b2a42009-08-27 19:28:37 +0000969static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
970 int count) {
971 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 *dst++ = value;
973 count -= 1;
974 SkTSwap(value, other);
975 }
976
977 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000978
reed@android.com3c9b2a42009-08-27 19:28:37 +0000979 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000981 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000982}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000983
reed@google.com5eb158d2011-04-15 15:50:34 +0000984#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +0000985 do { \
986 unsigned fi = fx >> kCache16Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000987 SkASSERT(fi <= kCache16Mask); \
988 fx += dx; \
989 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000990 toggle ^= TOGGLE_MASK; \
991 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000992
993
reed@google.comdd5bd672011-09-20 15:56:13 +0000994void Linear_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995 SkASSERT(count > 0);
996
997 SkPoint srcPt;
998 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
999 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001000 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@google.com5eb158d2011-04-15 15:50:34 +00001002 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001003
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001004 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001005 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1006 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1008
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001009 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010 SkFixed dxStorage[1];
1011 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1012 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001013 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1015 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1016 }
1017
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001018 if (SkFixedNearlyZero(dx)) {
1019 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +00001020 unsigned fi = proc(fx) >> kCache16Shift;
1021 SkASSERT(fi <= kCache16Mask);
reed@google.com5eb158d2011-04-15 15:50:34 +00001022 dither_memset16(dstC, cache[toggle + fi],
1023 cache[(toggle ^ TOGGLE_MASK) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001024 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001025 SkClampRange range;
1026 range.init(fx, dx, count, 0, kCache16Mask);
1027
1028 if ((count = range.fCount0) > 0) {
1029 dither_memset16(dstC,
1030 cache[toggle + range.fV0],
1031 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
1032 count);
1033 dstC += count;
1034 }
1035 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +00001036 int unroll = count >> 3;
1037 fx = range.fFx1;
1038 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001039 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1040 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1041 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1042 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1043 }
1044 if ((count &= 7) > 0) {
1045 do {
1046 NO_CHECK_ITER_16;
1047 } while (--count != 0);
1048 }
1049 }
1050 if ((count = range.fCount2) > 0) {
1051 dither_memset16(dstC,
1052 cache[toggle + range.fV1],
1053 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
1054 count);
1055 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001056 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001057 do {
reed@android.com200645d2009-12-14 16:41:57 +00001058 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001059 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001062 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001064 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 SkASSERT(proc == repeat_tileproc);
1066 do {
reed@android.com200645d2009-12-14 16:41:57 +00001067 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001068 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001071 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072 } while (--count != 0);
1073 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001074 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 SkScalar dstX = SkIntToScalar(x);
1076 SkScalar dstY = SkIntToScalar(y);
1077 do {
1078 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1079 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1080 SkASSERT(fi <= 0xFFFF);
1081
reed@android.com512a8762009-12-14 15:25:36 +00001082 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001083 *dstC++ = cache[toggle + index];
reed@google.com5eb158d2011-04-15 15:50:34 +00001084 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085
1086 dstX += SK_Scalar1;
1087 } while (--count != 0);
1088 }
1089}
1090
1091///////////////////////////////////////////////////////////////////////////////
1092
1093#define kSQRT_TABLE_BITS 11
1094#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1095
1096#include "SkRadialGradient_Table.h"
1097
1098#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1099
1100#include <stdio.h>
1101
reed@google.com61eb0402011-04-15 12:11:12 +00001102void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001103 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1104
1105 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1106 SkASSERT(file);
1107 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1108
reed@google.com61eb0402011-04-15 12:11:12 +00001109 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1110 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001112 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113
1114 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1115
1116 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001117 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001119 }
1120 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001121 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001122 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123 }
1124 ::fprintf(file, "};\n");
1125 ::fclose(file);
1126}
1127
1128#endif
1129
1130
reed@google.com61eb0402011-04-15 12:11:12 +00001131static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1132 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001133 SkScalar inv = SkScalarInvert(radius);
1134
1135 matrix->setTranslate(-center.fX, -center.fY);
1136 matrix->postScale(inv, inv);
1137}
1138
1139class Radial_Gradient : public Gradient_Shader {
1140public:
1141 Radial_Gradient(const SkPoint& center, SkScalar radius,
1142 const SkColor colors[], const SkScalar pos[], int colorCount,
1143 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001144 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1145 fCenter(center),
1146 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001147 {
1148 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1149 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1150
1151 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1152 }
reed@google.com61eb0402011-04-15 12:11:12 +00001153
reed@google.comdd5bd672011-09-20 15:56:13 +00001154 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155 SkASSERT(count > 0);
1156
1157 SkPoint srcPt;
1158 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1159 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001160 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161
reed@google.com61eb0402011-04-15 12:11:12 +00001162 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001163 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1164 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001165 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1166 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com51baf5a2011-09-21 13:38:36 +00001167#ifdef SK_USE_FLOAT_SQRT
1168 float fdx, fdy;
1169#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001170
reed@google.com61eb0402011-04-15 12:11:12 +00001171 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172 SkFixed storage[2];
1173 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1174 dx = storage[0];
1175 dy = storage[1];
reed@google.com51baf5a2011-09-21 13:38:36 +00001176#ifdef SK_USE_FLOAT_SQRT
1177 fdx = SkFixedToFloat(storage[0]);
1178 fdy = SkFixedToFloat(storage[1]);
1179#endif
reed@google.com61eb0402011-04-15 12:11:12 +00001180 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001181 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1182 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1183 dy = SkScalarToFixed(fDstToIndex.getSkewY());
reed@google.com51baf5a2011-09-21 13:38:36 +00001184#ifdef SK_USE_FLOAT_SQRT
1185 fdx = fDstToIndex.getScaleX();
1186 fdy = fDstToIndex.getSkewY();
1187#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 }
1189
reed@google.com61eb0402011-04-15 12:11:12 +00001190 if (proc == clamp_tileproc) {
reed@google.comdd5bd672011-09-20 15:56:13 +00001191 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001192 fx >>= 1;
1193 dx >>= 1;
1194 fy >>= 1;
1195 dy >>= 1;
1196 do {
1197 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1198 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1199 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1200 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1201 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1202 fx += dx;
1203 fy += dy;
1204 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001205 } else if (proc == mirror_tileproc) {
reed@google.com51baf5a2011-09-21 13:38:36 +00001206#ifdef SK_USE_FLOAT_SQRT
1207 float ffx = srcPt.fX;
1208 float ffy = srcPt.fY;
1209 do {
1210 float fdist = sk_float_sqrt(ffx*ffx + ffy*ffy);
1211 unsigned fi = mirror_tileproc(SkFloatToFixed(fdist));
1212 SkASSERT(fi <= 0xFFFF);
1213 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1214 ffx += fdx;
1215 ffy += fdy;
1216 } while (--count != 0);
1217#else
reed@android.com8a1c16f2008-12-17 15:59:43 +00001218 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001219 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1220 if (magnitudeSquared < 0) // Overflow.
1221 magnitudeSquared = SK_FixedMax;
1222 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001223 unsigned fi = mirror_tileproc(dist);
1224 SkASSERT(fi <= 0xFFFF);
1225 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1226 fx += dx;
1227 fy += dy;
1228 } while (--count != 0);
reed@google.com51baf5a2011-09-21 13:38:36 +00001229#endif
reed@google.com61eb0402011-04-15 12:11:12 +00001230 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231 SkASSERT(proc == repeat_tileproc);
1232 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001233 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1234 if (magnitudeSquared < 0) // Overflow.
1235 magnitudeSquared = SK_FixedMax;
1236 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 unsigned fi = repeat_tileproc(dist);
1238 SkASSERT(fi <= 0xFFFF);
1239 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1240 fx += dx;
1241 fy += dy;
1242 } while (--count != 0);
1243 }
reed@google.com61eb0402011-04-15 12:11:12 +00001244 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245 SkScalar dstX = SkIntToScalar(x);
1246 SkScalar dstY = SkIntToScalar(y);
1247 do {
1248 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1249 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1250 SkASSERT(fi <= 0xFFFF);
1251 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1252 dstX += SK_Scalar1;
1253 } while (--count != 0);
1254 }
1255 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001256
reed@google.comdd5bd672011-09-20 15:56:13 +00001257 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258 SkASSERT(count > 0);
1259
1260 SkPoint srcPt;
1261 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1262 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001263 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001265
reed@android.com3c9b2a42009-08-27 19:28:37 +00001266 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001267 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1268 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1270 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1271
reed@android.com3c9b2a42009-08-27 19:28:37 +00001272 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273 SkFixed storage[2];
1274 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1275 dx = storage[0];
1276 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001277 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1279 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1280 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1281 }
1282
reed@android.com3c9b2a42009-08-27 19:28:37 +00001283 if (proc == clamp_tileproc) {
reed@google.comdd5bd672011-09-20 15:56:13 +00001284 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285
1286 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1287 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1288 precision, but that appears to be visually OK. If we decide this is OK for
1289 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1290 to avoid having to do these extra shifts each time.
1291 */
1292 fx >>= 1;
1293 dx >>= 1;
1294 fy >>= 1;
1295 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001296 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 +00001297 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1298 fy *= fy;
1299 do {
1300 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1301 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1302 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1303 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1305 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001307 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308 do {
1309 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1310 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1311 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1312 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1313 fx += dx;
1314 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1316 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001317 } while (--count != 0);
1318 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001319 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320 do {
1321 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1322 unsigned fi = mirror_tileproc(dist);
1323 SkASSERT(fi <= 0xFFFF);
1324 fx += dx;
1325 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001326 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1327 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001329 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 SkASSERT(proc == repeat_tileproc);
1331 do {
1332 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1333 unsigned fi = repeat_tileproc(dist);
1334 SkASSERT(fi <= 0xFFFF);
1335 fx += dx;
1336 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1338 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339 } while (--count != 0);
1340 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001341 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 SkScalar dstX = SkIntToScalar(x);
1343 SkScalar dstY = SkIntToScalar(y);
1344 do {
1345 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1346 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1347 SkASSERT(fi <= 0xFFFF);
1348
1349 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 *dstC++ = cache[toggle + index];
1351 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352
1353 dstX += SK_Scalar1;
1354 } while (--count != 0);
1355 }
1356 }
1357
reed@google.com55b8e8c2011-01-13 16:22:35 +00001358 virtual BitmapType asABitmap(SkBitmap* bitmap,
1359 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001360 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001361 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001362 if (bitmap) {
1363 this->commonAsABitmap(bitmap);
1364 }
1365 if (matrix) {
1366 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1367 matrix->preConcat(fPtsToUnit);
1368 }
1369 if (xy) {
1370 xy[0] = fTileMode;
1371 xy[1] = kClamp_TileMode;
1372 }
1373 return kRadial_BitmapType;
1374 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001375 virtual GradientType asAGradient(GradientInfo* info) const {
1376 if (info) {
1377 commonAsAGradient(info);
1378 info->fPoint[0] = fCenter;
1379 info->fRadius[0] = fRadius;
1380 }
1381 return kRadial_GradientType;
1382 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001383
1384 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 return SkNEW_ARGS(Radial_Gradient, (buffer));
1386 }
1387
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001388 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1389 this->INHERITED::flatten(buffer);
1390 buffer.writeScalar(fCenter.fX);
1391 buffer.writeScalar(fCenter.fY);
1392 buffer.writeScalar(fRadius);
1393 }
1394
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001396 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1397 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001398 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001399 fRadius(buffer.readScalar()) {
1400 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 virtual Factory getFactory() { return CreateProc; }
1402
1403private:
1404 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001405 const SkPoint fCenter;
1406 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407};
1408
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001409/* Two-point radial gradients are specified by two circles, each with a center
1410 point and radius. The gradient can be considered to be a series of
1411 concentric circles, with the color interpolated from the start circle
1412 (at t=0) to the end circle (at t=1).
1413
1414 For each point (x, y) in the span, we want to find the
1415 interpolated circle that intersects that point. The center
1416 of the desired circle (Cx, Cy) falls at some distance t
1417 along the line segment between the start point (Sx, Sy) and
1418 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001419
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001420 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1421 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001422
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001423 The radius of the desired circle (r) is also a linear interpolation t
1424 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001425
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001426 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001427
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001428 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001429
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001430 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001431
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001432 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001433
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001434 (x - ((1 - t) * Sx + t * Ex))^2
1435 + (y - ((1 - t) * Sy + t * Ey))^2
1436 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001437
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001438 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001439
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001440 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1441 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1442 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001443
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001444 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1445
1446 [Dx^2 + Dy^2 - Dr^2)] * t^2
1447 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1448 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001449
1450 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001451 possible circles on which the point may fall. Solving for t yields
1452 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001453
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001454 If a<0, the start circle is entirely contained in the
1455 end circle, and one of the roots will be <0 or >1 (off the line
1456 segment). If a>0, the start circle falls at least partially
1457 outside the end circle (or vice versa), and the gradient
1458 defines a "tube" where a point may be on one circle (on the
1459 inside of the tube) or the other (outside of the tube). We choose
1460 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001461
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001462 In order to keep the math to within the limits of fixed point,
1463 we divide the entire quadratic by Dr^2, and replace
1464 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001465
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001466 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1467 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1468 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001469
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001470 (x' and y' are computed by appending the subtract and scale to the
1471 fDstToIndex matrix in the constructor).
1472
1473 Since the 'A' component of the quadratic is independent of x' and y', it
1474 is precomputed in the constructor. Since the 'B' component is linear in
1475 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001476 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001477 a perspective projection), it must be computed in the loop.
1478
1479*/
1480
reed@google.com84e9c082011-04-13 17:44:24 +00001481static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1482 SkScalar sr2d2, SkScalar foura,
1483 SkScalar oneOverTwoA, bool posRoot) {
1484 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001485 if (0 == foura) {
1486 return SkScalarToFixed(SkScalarDiv(-c, b));
1487 }
1488
reed@google.com84e9c082011-04-13 17:44:24 +00001489 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001490 if (discrim < 0) {
1491 discrim = -discrim;
1492 }
reed@google.com84e9c082011-04-13 17:44:24 +00001493 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1494 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001495 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001496 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001497 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001498 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001499 }
reed@google.com84e9c082011-04-13 17:44:24 +00001500 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001501}
1502
1503class Two_Point_Radial_Gradient : public Gradient_Shader {
1504public:
1505 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1506 const SkPoint& end, SkScalar endRadius,
1507 const SkColor colors[], const SkScalar pos[],
1508 int colorCount, SkShader::TileMode mode,
1509 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001510 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1511 fCenter1(start),
1512 fCenter2(end),
1513 fRadius1(startRadius),
1514 fRadius2(endRadius) {
1515 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001516 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001517
1518 virtual BitmapType asABitmap(SkBitmap* bitmap,
1519 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001520 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001521 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001522 if (bitmap) {
1523 this->commonAsABitmap(bitmap);
1524 }
1525 SkScalar diffL = 0; // just to avoid gcc warning
1526 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001527 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001528 SkScalarSquare(fDiff.fY));
1529 }
1530 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001531 if (diffL) {
1532 SkScalar invDiffL = SkScalarInvert(diffL);
1533 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1534 SkScalarMul(invDiffL, fDiff.fX));
1535 } else {
1536 matrix->reset();
1537 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001538 matrix->preConcat(fPtsToUnit);
1539 }
1540 if (xy) {
1541 xy[0] = fTileMode;
1542 xy[1] = kClamp_TileMode;
1543 }
1544 if (NULL != twoPointRadialParams) {
1545 twoPointRadialParams[0] = diffL;
1546 twoPointRadialParams[1] = fStartRadius;
1547 twoPointRadialParams[2] = fDiffRadius;
1548 }
1549 return kTwoPointRadial_BitmapType;
1550 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001551
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001552 virtual GradientType asAGradient(GradientInfo* info) const {
1553 if (info) {
1554 commonAsAGradient(info);
1555 info->fPoint[0] = fCenter1;
1556 info->fPoint[1] = fCenter2;
1557 info->fRadius[0] = fRadius1;
1558 info->fRadius[1] = fRadius2;
1559 }
1560 return kRadial2_GradientType;
1561 }
1562
reed@google.comdd5bd672011-09-20 15:56:13 +00001563 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001564 SkASSERT(count > 0);
1565
1566 // Zero difference between radii: fill with transparent black.
1567 if (fDiffRadius == 0) {
1568 sk_bzero(dstC, count * sizeof(*dstC));
1569 return;
1570 }
1571 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1572 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001573 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001574
1575 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001576 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001577 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001578 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001579 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1580 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001581 SkScalar dx, fx = srcPt.fX;
1582 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001583
reed@google.com61eb0402011-04-15 12:11:12 +00001584 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001585 SkFixed fixedX, fixedY;
1586 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1587 dx = SkFixedToScalar(fixedX);
1588 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001589 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001590 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001591 dx = fDstToIndex.getScaleX();
1592 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001593 }
reed@google.com84e9c082011-04-13 17:44:24 +00001594 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1595 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1596 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1597 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001598 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001599 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001600 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001601 SkFixed index = SkClampMax(t, 0xFFFF);
1602 SkASSERT(index <= 0xFFFF);
1603 *dstC++ = cache[index >> (16 - kCache32Bits)];
1604 fx += dx;
1605 fy += dy;
1606 b += db;
1607 }
reed@google.com61eb0402011-04-15 12:11:12 +00001608 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001609 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001610 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001611 SkFixed index = mirror_tileproc(t);
1612 SkASSERT(index <= 0xFFFF);
1613 *dstC++ = cache[index >> (16 - kCache32Bits)];
1614 fx += dx;
1615 fy += dy;
1616 b += db;
1617 }
reed@google.com61eb0402011-04-15 12:11:12 +00001618 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001619 SkASSERT(proc == repeat_tileproc);
1620 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001621 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001622 SkFixed index = repeat_tileproc(t);
1623 SkASSERT(index <= 0xFFFF);
1624 *dstC++ = cache[index >> (16 - kCache32Bits)];
1625 fx += dx;
1626 fy += dy;
1627 b += db;
1628 }
1629 }
reed@google.com61eb0402011-04-15 12:11:12 +00001630 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001631 SkScalar dstX = SkIntToScalar(x);
1632 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001633 for (; count > 0; --count) {
1634 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001635 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001636 SkScalar fx = srcPt.fX;
1637 SkScalar fy = srcPt.fY;
1638 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1639 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1640 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001641 SkFixed index = proc(t);
1642 SkASSERT(index <= 0xFFFF);
1643 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001644 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001645 }
1646 }
1647 }
1648
reed@android.com6c59a172009-09-22 20:24:05 +00001649 virtual bool setContext(const SkBitmap& device,
1650 const SkPaint& paint,
1651 const SkMatrix& matrix) {
1652 if (!this->INHERITED::setContext(device, paint, matrix)) {
1653 return false;
1654 }
1655
1656 // we don't have a span16 proc
1657 fFlags &= ~kHasSpan16_Flag;
1658 return true;
1659 }
1660
reed@google.com55b8e8c2011-01-13 16:22:35 +00001661 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001662 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1663 }
1664
reed@android.combcfc7332009-11-10 16:19:39 +00001665 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1666 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001667 buffer.writeScalar(fCenter1.fX);
1668 buffer.writeScalar(fCenter1.fY);
1669 buffer.writeScalar(fCenter2.fX);
1670 buffer.writeScalar(fCenter2.fY);
1671 buffer.writeScalar(fRadius1);
1672 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001673 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001674
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001675protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001676 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001677 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001678 fCenter1(unflatten_point(buffer)),
1679 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001680 fRadius1(buffer.readScalar()),
1681 fRadius2(buffer.readScalar()) {
1682 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001683 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001684 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001685
1686private:
1687 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001688 const SkPoint fCenter1;
1689 const SkPoint fCenter2;
1690 const SkScalar fRadius1;
1691 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001692 SkPoint fDiff;
1693 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001694
1695 void init() {
1696 fDiff = fCenter1 - fCenter2;
1697 fDiffRadius = fRadius2 - fRadius1;
1698 SkScalar inv = SkScalarInvert(fDiffRadius);
1699 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1700 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1701 fStartRadius = SkScalarMul(fRadius1, inv);
1702 fSr2D2 = SkScalarSquare(fStartRadius);
1703 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00001704 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001705
1706 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1707 fPtsToUnit.postScale(inv, inv);
1708 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001709};
1710
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711///////////////////////////////////////////////////////////////////////////////
1712
1713class Sweep_Gradient : public Gradient_Shader {
1714public:
1715 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1716 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001717 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1718 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719 {
1720 fPtsToUnit.setTranslate(-cx, -cy);
1721 }
1722 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1723 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001724
1725 virtual BitmapType asABitmap(SkBitmap* bitmap,
1726 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001727 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001728 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001729 if (bitmap) {
1730 this->commonAsABitmap(bitmap);
1731 }
1732 if (matrix) {
1733 *matrix = fPtsToUnit;
1734 }
1735 if (xy) {
1736 xy[0] = fTileMode;
1737 xy[1] = kClamp_TileMode;
1738 }
1739 return kSweep_BitmapType;
1740 }
1741
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001742 virtual GradientType asAGradient(GradientInfo* info) const {
1743 if (info) {
1744 commonAsAGradient(info);
1745 info->fPoint[0] = fCenter;
1746 }
1747 return kSweep_GradientType;
1748 }
1749
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1751 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1752 }
1753
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001754 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1755 this->INHERITED::flatten(buffer);
1756 buffer.writeScalar(fCenter.fX);
1757 buffer.writeScalar(fCenter.fY);
1758 }
1759
reed@android.com8a1c16f2008-12-17 15:59:43 +00001760protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001761 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1762 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001763 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001764 }
1765
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 virtual Factory getFactory() { return CreateProc; }
1767
1768private:
1769 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001770 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771};
1772
1773#ifdef COMPUTE_SWEEP_TABLE
1774#define PI 3.14159265
1775static bool gSweepTableReady;
1776static uint8_t gSweepTable[65];
1777
1778/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1779 We scale the results to [0..32]
1780*/
reed@google.com61eb0402011-04-15 12:11:12 +00001781static const uint8_t* build_sweep_table() {
1782 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 const int N = 65;
1784 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001785
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 for (int i = 0; i < N; i++)
1787 {
1788 double arg = i / DENOM;
1789 double v = atan(arg);
1790 int iv = (int)round(v * DENOM * 2 / PI);
1791// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1792 printf("%d, ", iv);
1793 gSweepTable[i] = iv;
1794 }
1795 gSweepTableReady = true;
1796 }
1797 return gSweepTable;
1798}
1799#else
1800static const uint8_t gSweepTable[] = {
1801 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1802 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1803 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1804 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1805 32
1806};
1807static const uint8_t* build_sweep_table() { return gSweepTable; }
1808#endif
1809
1810// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1811// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1812// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1813
1814//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001815static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 SkASSERT(numer <= denom);
1817 SkASSERT(numer > 0);
1818 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001819
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820 int nbits = SkCLZ(numer);
1821 int dbits = SkCLZ(denom);
1822 int bits = 6 - nbits + dbits;
1823 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001824
reed@google.com61eb0402011-04-15 12:11:12 +00001825 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001827 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828
1829 denom <<= dbits - 1;
1830 numer <<= nbits - 1;
1831
1832 unsigned result = 0;
1833
1834 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001835 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001837 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001838 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001839 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001840
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001842 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001843 // make room for the rest of the answer bits
1844 result <<= bits;
1845 switch (bits) {
1846 case 6:
1847 if ((numer = (numer << 1) - denom) >= 0)
1848 result |= 32;
1849 else
1850 numer += denom;
1851 case 5:
1852 if ((numer = (numer << 1) - denom) >= 0)
1853 result |= 16;
1854 else
1855 numer += denom;
1856 case 4:
1857 if ((numer = (numer << 1) - denom) >= 0)
1858 result |= 8;
1859 else
1860 numer += denom;
1861 case 3:
1862 if ((numer = (numer << 1) - denom) >= 0)
1863 result |= 4;
1864 else
1865 numer += denom;
1866 case 2:
1867 if ((numer = (numer << 1) - denom) >= 0)
1868 result |= 2;
1869 else
1870 numer += denom;
1871 case 1:
1872 default: // not strictly need, but makes GCC make better ARM code
1873 if ((numer = (numer << 1) - denom) >= 0)
1874 result |= 1;
1875 else
1876 numer += denom;
1877 }
1878 }
1879 return result;
1880}
1881
1882// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001883static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884#ifdef SK_DEBUG
1885 {
1886 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001887 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888 gOnce = true;
1889 SkASSERT(div_64(55, 55) == 64);
1890 SkASSERT(div_64(128, 256) == 32);
1891 SkASSERT(div_64(2326528, 4685824) == 31);
1892 SkASSERT(div_64(753664, 5210112) == 9);
1893 SkASSERT(div_64(229376, 4882432) == 3);
1894 SkASSERT(div_64(2, 64) == 2);
1895 SkASSERT(div_64(1, 64) == 1);
1896 // test that we handle underflow correctly
1897 SkASSERT(div_64(12345, 0x54321234) == 0);
1898 }
1899 }
1900#endif
1901
1902 SkASSERT(y > 0 && x > 0);
1903 const uint8_t* table = build_sweep_table();
1904
1905 unsigned result;
1906 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00001907 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908 // first part of the atan(v) = PI/2 - atan(1/v) identity
1909 // since our div_64 and table want v <= 1, where v = y/x
1910 SkTSwap<SkFixed>(x, y);
1911 }
1912
1913 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001914
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915#ifdef SK_DEBUG
1916 {
1917 unsigned result2 = SkDivBits(y, x, 6);
1918 SkASSERT(result2 == result ||
1919 (result == 1 && result2 == 0));
1920 }
1921#endif
1922
1923 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1924 result = table[result];
1925
reed@google.com61eb0402011-04-15 12:11:12 +00001926 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001927 // complete the atan(v) = PI/2 - atan(1/v) identity
1928 result = 64 - result;
1929 // pin to 63
1930 result -= result >> 6;
1931 }
1932
1933 SkASSERT(result <= 63);
1934 return result;
1935}
1936
1937// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00001938#ifdef SK_SCALAR_IS_FLOAT
1939static unsigned SkATan2_255(float y, float x) {
1940 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
1941 static const float g255Over2PI = 40.584510488433314f;
1942
1943 float result = sk_float_atan2(y, x);
1944 if (result < 0) {
1945 result += 2 * SK_ScalarPI;
1946 }
1947 SkASSERT(result >= 0);
1948 // since our value is always >= 0, we can cast to int, which is faster than
1949 // calling floorf()
1950 int ir = (int)(result * g255Over2PI);
1951 SkASSERT(ir >= 0 && ir <= 255);
1952 return ir;
1953}
1954#else
reed@google.com61eb0402011-04-15 12:11:12 +00001955static unsigned SkATan2_255(SkFixed y, SkFixed x) {
1956 if (x == 0) {
1957 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001958 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001959 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 return y < 0 ? 192 : 64;
1961 }
reed@google.com61eb0402011-04-15 12:11:12 +00001962 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001964 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001965
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 /* Find the right quadrant for x,y
1967 Since atan_0_90 only handles the first quadrant, we rotate x,y
1968 appropriately before calling it, and then add the right amount
1969 to account for the real quadrant.
1970 quadrant 0 : add 0 | x > 0 && y > 0
1971 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1972 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1973 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001974
reed@android.com8a1c16f2008-12-17 15:59:43 +00001975 map x<0 to (1 << 6)
1976 map y<0 to (3 << 6)
1977 add = map_x ^ map_y
1978 */
1979 int xsign = x >> 31;
1980 int ysign = y >> 31;
1981 int add = ((-xsign) ^ (ysign & 3)) << 6;
1982
1983#ifdef SK_DEBUG
1984 if (0 == add)
1985 SkASSERT(x > 0 && y > 0);
1986 else if (64 == add)
1987 SkASSERT(x < 0 && y > 0);
1988 else if (128 == add)
1989 SkASSERT(x < 0 && y < 0);
1990 else if (192 == add)
1991 SkASSERT(x > 0 && y < 0);
1992 else
1993 SkASSERT(!"bad value for add");
1994#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00001995
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1997 where we need to rotate x,y by 90 or -90
1998 */
1999 x = (x ^ xsign) - xsign;
2000 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002001 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002003 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002004
2005 unsigned result = add + atan_0_90(y, x);
2006 SkASSERT(result < 256);
2007 return result;
2008}
reed@google.com51baf5a2011-09-21 13:38:36 +00002009#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010
reed@google.comdd5bd672011-09-20 15:56:13 +00002011void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002012 SkMatrix::MapXYProc proc = fDstToIndexProc;
2013 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002014 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002016
reed@google.com61eb0402011-04-15 12:11:12 +00002017 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2019 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002020 SkScalar dx, fx = srcPt.fX;
2021 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002022
reed@google.com61eb0402011-04-15 12:11:12 +00002023 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 SkFixed storage[2];
2025 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2026 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002027 dx = SkFixedToScalar(storage[0]);
2028 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002029 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002031 dx = matrix.getScaleX();
2032 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002034
reed@google.com61eb0402011-04-15 12:11:12 +00002035 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 *dstC++ = cache[SkATan2_255(fy, fx)];
2037 fx += dx;
2038 fy += dy;
2039 }
reed@google.com61eb0402011-04-15 12:11:12 +00002040 } else { // perspective case
2041 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002043 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2044 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045 }
2046 }
2047}
2048
reed@google.comdd5bd672011-09-20 15:56:13 +00002049void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002050 SkMatrix::MapXYProc proc = fDstToIndexProc;
2051 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002052 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002053 int toggle = ((x ^ y) & 1) << kCache16Bits;
2054 SkPoint srcPt;
2055
reed@google.com61eb0402011-04-15 12:11:12 +00002056 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002057 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2058 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002059 SkScalar dx, fx = srcPt.fX;
2060 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002061
reed@google.com61eb0402011-04-15 12:11:12 +00002062 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002063 SkFixed storage[2];
2064 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2065 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002066 dx = SkFixedToScalar(storage[0]);
2067 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002068 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002069 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002070 dx = matrix.getScaleX();
2071 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002073
reed@google.com61eb0402011-04-15 12:11:12 +00002074 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002075 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2076 *dstC++ = cache[toggle + index];
2077 toggle ^= (1 << kCache16Bits);
2078 fx += dx;
2079 fy += dy;
2080 }
reed@google.com61eb0402011-04-15 12:11:12 +00002081 } else { // perspective case
2082 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002083 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2084 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002085
reed@google.com51baf5a2011-09-21 13:38:36 +00002086 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087 index >>= (8 - kCache16Bits);
2088 *dstC++ = cache[toggle + index];
2089 toggle ^= (1 << kCache16Bits);
2090 }
2091 }
2092}
2093
reed@google.com61eb0402011-04-15 12:11:12 +00002094///////////////////////////////////////////////////////////////////////////////
2095///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096
2097// assumes colors is SkColor* and pos is SkScalar*
2098#define EXPAND_1_COLOR(count) \
2099 SkColor tmp[2]; \
2100 do { \
2101 if (1 == count) { \
2102 tmp[0] = tmp[1] = colors[0]; \
2103 colors = tmp; \
2104 pos = NULL; \
2105 count = 2; \
2106 } \
2107 } while (0)
2108
reed@google.com61eb0402011-04-15 12:11:12 +00002109SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2110 const SkColor colors[],
2111 const SkScalar pos[], int colorCount,
2112 SkShader::TileMode mode,
2113 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002114 if (NULL == pts || NULL == colors || colorCount < 1) {
2115 return NULL;
2116 }
2117 EXPAND_1_COLOR(colorCount);
2118
reed@android.comab840b82009-07-01 17:00:03 +00002119 return SkNEW_ARGS(Linear_Gradient,
2120 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121}
2122
reed@google.com61eb0402011-04-15 12:11:12 +00002123SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2124 const SkColor colors[],
2125 const SkScalar pos[], int colorCount,
2126 SkShader::TileMode mode,
2127 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002128 if (radius <= 0 || NULL == colors || colorCount < 1) {
2129 return NULL;
2130 }
2131 EXPAND_1_COLOR(colorCount);
2132
reed@android.comab840b82009-07-01 17:00:03 +00002133 return SkNEW_ARGS(Radial_Gradient,
2134 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135}
2136
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002137SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2138 SkScalar startRadius,
2139 const SkPoint& end,
2140 SkScalar endRadius,
2141 const SkColor colors[],
2142 const SkScalar pos[],
2143 int colorCount,
2144 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002145 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002146 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2147 return NULL;
2148 }
2149 EXPAND_1_COLOR(colorCount);
2150
2151 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002152 (start, startRadius, end, endRadius, colors, pos,
2153 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002154}
2155
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2157 const SkColor colors[],
2158 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002159 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 if (NULL == colors || count < 1) {
2161 return NULL;
2162 }
2163 EXPAND_1_COLOR(count);
2164
2165 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2166}
2167
2168static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2169 Linear_Gradient::CreateProc);
2170
2171static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2172 Radial_Gradient::CreateProc);
2173
2174static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2175 Sweep_Gradient::CreateProc);
tomhudson@google.com938d6042011-04-27 13:57:03 +00002176
2177static SkFlattenable::Registrar
2178 gTwoPointRadialGradientReg("Two_Point_Radial_Gradient",
2179 Two_Point_Radial_Gradient::CreateProc);