blob: 0ff128bb6e860863a3017232368256f3fe526b06 [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
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001154 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count);
reed@google.comdd5bd672011-09-20 15:56:13 +00001155 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001156 SkASSERT(count > 0);
1157
1158 SkPoint srcPt;
1159 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1160 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001161 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001163
reed@android.com3c9b2a42009-08-27 19:28:37 +00001164 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001165 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1166 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001167 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1168 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1169
reed@android.com3c9b2a42009-08-27 19:28:37 +00001170 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001171 SkFixed storage[2];
1172 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1173 dx = storage[0];
1174 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001175 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1177 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1178 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1179 }
1180
reed@android.com3c9b2a42009-08-27 19:28:37 +00001181 if (proc == clamp_tileproc) {
reed@google.comdd5bd672011-09-20 15:56:13 +00001182 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001183
1184 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1185 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1186 precision, but that appears to be visually OK. If we decide this is OK for
1187 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1188 to avoid having to do these extra shifts each time.
1189 */
1190 fx >>= 1;
1191 dx >>= 1;
1192 fy >>= 1;
1193 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001194 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 +00001195 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1196 fy *= fy;
1197 do {
1198 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1199 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1200 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1201 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1203 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001205 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206 do {
1207 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1208 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1209 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1210 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1211 fx += dx;
1212 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1214 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215 } while (--count != 0);
1216 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001217 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001218 do {
1219 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1220 unsigned fi = mirror_tileproc(dist);
1221 SkASSERT(fi <= 0xFFFF);
1222 fx += dx;
1223 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1225 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001226 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001227 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228 SkASSERT(proc == repeat_tileproc);
1229 do {
1230 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1231 unsigned fi = repeat_tileproc(dist);
1232 SkASSERT(fi <= 0xFFFF);
1233 fx += dx;
1234 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1236 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 } while (--count != 0);
1238 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001239 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 SkScalar dstX = SkIntToScalar(x);
1241 SkScalar dstY = SkIntToScalar(y);
1242 do {
1243 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1244 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1245 SkASSERT(fi <= 0xFFFF);
1246
1247 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 *dstC++ = cache[toggle + index];
1249 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250
1251 dstX += SK_Scalar1;
1252 } while (--count != 0);
1253 }
1254 }
1255
reed@google.com55b8e8c2011-01-13 16:22:35 +00001256 virtual BitmapType asABitmap(SkBitmap* bitmap,
1257 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001258 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001259 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001260 if (bitmap) {
1261 this->commonAsABitmap(bitmap);
1262 }
1263 if (matrix) {
1264 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1265 matrix->preConcat(fPtsToUnit);
1266 }
1267 if (xy) {
1268 xy[0] = fTileMode;
1269 xy[1] = kClamp_TileMode;
1270 }
1271 return kRadial_BitmapType;
1272 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001273 virtual GradientType asAGradient(GradientInfo* info) const {
1274 if (info) {
1275 commonAsAGradient(info);
1276 info->fPoint[0] = fCenter;
1277 info->fRadius[0] = fRadius;
1278 }
1279 return kRadial_GradientType;
1280 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001281
1282 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 return SkNEW_ARGS(Radial_Gradient, (buffer));
1284 }
1285
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001286 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1287 this->INHERITED::flatten(buffer);
1288 buffer.writeScalar(fCenter.fX);
1289 buffer.writeScalar(fCenter.fY);
1290 buffer.writeScalar(fRadius);
1291 }
1292
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001294 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1295 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001296 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001297 fRadius(buffer.readScalar()) {
1298 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 virtual Factory getFactory() { return CreateProc; }
1300
1301private:
1302 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001303 const SkPoint fCenter;
1304 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305};
1306
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001307static inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
1308 // fast, overly-conservative test: checks unit square instead
1309 // of unit circle
1310 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1311 (fx <= -SK_FixedHalf && dx <= 0);
1312 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1313 (fy <= -SK_FixedHalf && dy <= 0);
1314
1315 return xClamped || yClamped;
1316}
1317
1318// Return true if (fx * fy) is always inside the unit circle
1319// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1320// so it shouldn't be run if count is small.
1321static inline bool no_need_for_radial_pin(int fx, int dx,
1322 int fy, int dy, int count) {
1323 SkASSERT(count > 0);
1324 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1325 return false;
1326 }
1327 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1328 return false;
1329 }
1330 fx += (count - 1) * dx;
1331 fy += (count - 1) * dy;
1332 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1333 return false;
1334 }
1335 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1336}
1337
1338#define UNPINNED_RADIAL_STEP \
1339 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
1340 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)]; \
1341 fx += dx; \
1342 fy += dy;
1343
1344// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
1345static void radial_clamp(SkFixed fx, SkFixed fy, SkFixed dx, SkFixed dy,
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001346 SkPMColor* SK_RESTRICT dstC, int count,
1347 const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001348 const int kCache32Bits, const int kCache32Count) {
1349 // Floating point seems to be slower than fixed point,
1350 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001351 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001352 fx >>= 1;
1353 dx >>= 1;
1354 fy >>= 1;
1355 dy >>= 1;
1356 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
1357 sk_memset32(dstC, cache[kCache32Count - 1], count);
1358 } else if ((count > 4) &&
1359 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1360 unsigned fi;
1361 // 4x unroll appears to be no faster than 2x unroll on Linux
1362 while (count > 1) {
1363 UNPINNED_RADIAL_STEP;
1364 UNPINNED_RADIAL_STEP;
1365 count -= 2;
1366 }
1367 if (count) {
1368 UNPINNED_RADIAL_STEP;
1369 }
1370 }
1371 else {
1372 do {
1373 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1374 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1375 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1376 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1377 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1378 fx += dx;
1379 fy += dy;
1380 } while (--count != 0);
1381 }
1382}
1383
1384void Radial_Gradient::shadeSpan(int x, int y,
1385 SkPMColor* SK_RESTRICT dstC, int count) {
1386 SkASSERT(count > 0);
1387
1388 SkPoint srcPt;
1389 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1390 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001391 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001392
1393 if (fDstToIndexClass != kPerspective_MatrixClass) {
1394 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1395 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1396 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1397 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1398#ifdef SK_USE_FLOAT_SQRT
1399 float fdx, fdy;
1400#endif
1401
1402 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1403 SkFixed storage[2];
1404 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1405 dx = storage[0];
1406 dy = storage[1];
1407#ifdef SK_USE_FLOAT_SQRT
1408 fdx = SkFixedToFloat(storage[0]);
1409 fdy = SkFixedToFloat(storage[1]);
1410#endif
1411 } else {
1412 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1413 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1414 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1415#ifdef SK_USE_FLOAT_SQRT
1416 fdx = fDstToIndex.getScaleX();
1417 fdy = fDstToIndex.getSkewY();
1418#endif
1419 }
1420
1421 if (proc == clamp_tileproc) {
1422 radial_clamp(fx, fy, dx, dy, dstC, count, cache,
1423 kCache32Bits, kCache32Count);
1424 } else if (proc == mirror_tileproc) {
1425#ifdef SK_USE_FLOAT_SQRT
1426 float ffx = srcPt.fX;
1427 float ffy = srcPt.fY;
1428 do {
1429 float fdist = sk_float_sqrt(ffx*ffx + ffy*ffy);
1430 unsigned fi = mirror_tileproc(SkFloatToFixed(fdist));
1431 SkASSERT(fi <= 0xFFFF);
1432 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1433 ffx += fdx;
1434 ffy += fdy;
1435 } while (--count != 0);
1436#else
1437 do {
1438 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1439 SkFixedSquare(fy);
1440 if (magnitudeSquared < 0) // Overflow.
1441 magnitudeSquared = SK_FixedMax;
1442 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1443 unsigned fi = mirror_tileproc(dist);
1444 SkASSERT(fi <= 0xFFFF);
1445 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1446 fx += dx;
1447 fy += dy;
1448 } while (--count != 0);
1449#endif
1450 } else {
1451 SkASSERT(proc == repeat_tileproc);
1452 do {
1453 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1454 SkFixedSquare(fy);
1455 if (magnitudeSquared < 0) // Overflow.
1456 magnitudeSquared = SK_FixedMax;
1457 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1458 unsigned fi = repeat_tileproc(dist);
1459 SkASSERT(fi <= 0xFFFF);
1460 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1461 fx += dx;
1462 fy += dy;
1463 } while (--count != 0);
1464 }
1465 } else { // perspective case
1466 SkScalar dstX = SkIntToScalar(x);
1467 SkScalar dstY = SkIntToScalar(y);
1468 do {
1469 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1470 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1471 SkASSERT(fi <= 0xFFFF);
1472 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1473 dstX += SK_Scalar1;
1474 } while (--count != 0);
1475 }
1476}
1477
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001478/* Two-point radial gradients are specified by two circles, each with a center
1479 point and radius. The gradient can be considered to be a series of
1480 concentric circles, with the color interpolated from the start circle
1481 (at t=0) to the end circle (at t=1).
1482
1483 For each point (x, y) in the span, we want to find the
1484 interpolated circle that intersects that point. The center
1485 of the desired circle (Cx, Cy) falls at some distance t
1486 along the line segment between the start point (Sx, Sy) and
1487 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001488
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001489 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1490 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001491
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001492 The radius of the desired circle (r) is also a linear interpolation t
1493 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001494
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001495 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001496
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001497 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001498
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001499 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001500
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001501 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001502
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001503 (x - ((1 - t) * Sx + t * Ex))^2
1504 + (y - ((1 - t) * Sy + t * Ey))^2
1505 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001506
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001507 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001508
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001509 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1510 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1511 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001512
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001513 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1514
1515 [Dx^2 + Dy^2 - Dr^2)] * t^2
1516 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1517 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001518
1519 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001520 possible circles on which the point may fall. Solving for t yields
1521 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001522
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001523 If a<0, the start circle is entirely contained in the
1524 end circle, and one of the roots will be <0 or >1 (off the line
1525 segment). If a>0, the start circle falls at least partially
1526 outside the end circle (or vice versa), and the gradient
1527 defines a "tube" where a point may be on one circle (on the
1528 inside of the tube) or the other (outside of the tube). We choose
1529 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001530
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001531 In order to keep the math to within the limits of fixed point,
1532 we divide the entire quadratic by Dr^2, and replace
1533 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001534
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001535 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1536 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1537 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001538
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001539 (x' and y' are computed by appending the subtract and scale to the
1540 fDstToIndex matrix in the constructor).
1541
1542 Since the 'A' component of the quadratic is independent of x' and y', it
1543 is precomputed in the constructor. Since the 'B' component is linear in
1544 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001545 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001546 a perspective projection), it must be computed in the loop.
1547
1548*/
1549
reed@google.com84e9c082011-04-13 17:44:24 +00001550static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1551 SkScalar sr2d2, SkScalar foura,
1552 SkScalar oneOverTwoA, bool posRoot) {
1553 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001554 if (0 == foura) {
1555 return SkScalarToFixed(SkScalarDiv(-c, b));
1556 }
1557
reed@google.com84e9c082011-04-13 17:44:24 +00001558 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001559 if (discrim < 0) {
1560 discrim = -discrim;
1561 }
reed@google.com84e9c082011-04-13 17:44:24 +00001562 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1563 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001564 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001565 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001566 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001567 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001568 }
reed@google.com84e9c082011-04-13 17:44:24 +00001569 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001570}
1571
1572class Two_Point_Radial_Gradient : public Gradient_Shader {
1573public:
1574 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1575 const SkPoint& end, SkScalar endRadius,
1576 const SkColor colors[], const SkScalar pos[],
1577 int colorCount, SkShader::TileMode mode,
1578 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001579 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1580 fCenter1(start),
1581 fCenter2(end),
1582 fRadius1(startRadius),
1583 fRadius2(endRadius) {
1584 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001585 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001586
1587 virtual BitmapType asABitmap(SkBitmap* bitmap,
1588 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001589 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001590 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001591 if (bitmap) {
1592 this->commonAsABitmap(bitmap);
1593 }
1594 SkScalar diffL = 0; // just to avoid gcc warning
1595 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001596 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001597 SkScalarSquare(fDiff.fY));
1598 }
1599 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001600 if (diffL) {
1601 SkScalar invDiffL = SkScalarInvert(diffL);
1602 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1603 SkScalarMul(invDiffL, fDiff.fX));
1604 } else {
1605 matrix->reset();
1606 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001607 matrix->preConcat(fPtsToUnit);
1608 }
1609 if (xy) {
1610 xy[0] = fTileMode;
1611 xy[1] = kClamp_TileMode;
1612 }
1613 if (NULL != twoPointRadialParams) {
1614 twoPointRadialParams[0] = diffL;
1615 twoPointRadialParams[1] = fStartRadius;
1616 twoPointRadialParams[2] = fDiffRadius;
1617 }
1618 return kTwoPointRadial_BitmapType;
1619 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001620
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001621 virtual GradientType asAGradient(GradientInfo* info) const {
1622 if (info) {
1623 commonAsAGradient(info);
1624 info->fPoint[0] = fCenter1;
1625 info->fPoint[1] = fCenter2;
1626 info->fRadius[0] = fRadius1;
1627 info->fRadius[1] = fRadius2;
1628 }
1629 return kRadial2_GradientType;
1630 }
1631
reed@google.comdd5bd672011-09-20 15:56:13 +00001632 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001633 SkASSERT(count > 0);
1634
1635 // Zero difference between radii: fill with transparent black.
1636 if (fDiffRadius == 0) {
1637 sk_bzero(dstC, count * sizeof(*dstC));
1638 return;
1639 }
1640 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1641 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001642 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001643
1644 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001645 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001646 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001647 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001648 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1649 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001650 SkScalar dx, fx = srcPt.fX;
1651 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001652
reed@google.com61eb0402011-04-15 12:11:12 +00001653 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001654 SkFixed fixedX, fixedY;
1655 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1656 dx = SkFixedToScalar(fixedX);
1657 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001658 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001659 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001660 dx = fDstToIndex.getScaleX();
1661 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001662 }
reed@google.com84e9c082011-04-13 17:44:24 +00001663 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1664 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1665 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1666 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001667 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001668 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001669 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001670 SkFixed index = SkClampMax(t, 0xFFFF);
1671 SkASSERT(index <= 0xFFFF);
1672 *dstC++ = cache[index >> (16 - kCache32Bits)];
1673 fx += dx;
1674 fy += dy;
1675 b += db;
1676 }
reed@google.com61eb0402011-04-15 12:11:12 +00001677 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001678 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001679 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001680 SkFixed index = mirror_tileproc(t);
1681 SkASSERT(index <= 0xFFFF);
1682 *dstC++ = cache[index >> (16 - kCache32Bits)];
1683 fx += dx;
1684 fy += dy;
1685 b += db;
1686 }
reed@google.com61eb0402011-04-15 12:11:12 +00001687 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001688 SkASSERT(proc == repeat_tileproc);
1689 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001690 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001691 SkFixed index = repeat_tileproc(t);
1692 SkASSERT(index <= 0xFFFF);
1693 *dstC++ = cache[index >> (16 - kCache32Bits)];
1694 fx += dx;
1695 fy += dy;
1696 b += db;
1697 }
1698 }
reed@google.com61eb0402011-04-15 12:11:12 +00001699 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001700 SkScalar dstX = SkIntToScalar(x);
1701 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001702 for (; count > 0; --count) {
1703 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001704 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001705 SkScalar fx = srcPt.fX;
1706 SkScalar fy = srcPt.fY;
1707 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1708 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1709 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001710 SkFixed index = proc(t);
1711 SkASSERT(index <= 0xFFFF);
1712 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001713 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001714 }
1715 }
1716 }
1717
reed@android.com6c59a172009-09-22 20:24:05 +00001718 virtual bool setContext(const SkBitmap& device,
1719 const SkPaint& paint,
1720 const SkMatrix& matrix) {
1721 if (!this->INHERITED::setContext(device, paint, matrix)) {
1722 return false;
1723 }
1724
1725 // we don't have a span16 proc
1726 fFlags &= ~kHasSpan16_Flag;
1727 return true;
1728 }
1729
reed@google.com55b8e8c2011-01-13 16:22:35 +00001730 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001731 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1732 }
1733
reed@android.combcfc7332009-11-10 16:19:39 +00001734 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1735 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001736 buffer.writeScalar(fCenter1.fX);
1737 buffer.writeScalar(fCenter1.fY);
1738 buffer.writeScalar(fCenter2.fX);
1739 buffer.writeScalar(fCenter2.fY);
1740 buffer.writeScalar(fRadius1);
1741 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001742 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001743
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001744protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001745 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001746 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001747 fCenter1(unflatten_point(buffer)),
1748 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001749 fRadius1(buffer.readScalar()),
1750 fRadius2(buffer.readScalar()) {
1751 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001752 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001753 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001754
1755private:
1756 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001757 const SkPoint fCenter1;
1758 const SkPoint fCenter2;
1759 const SkScalar fRadius1;
1760 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001761 SkPoint fDiff;
1762 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001763
1764 void init() {
1765 fDiff = fCenter1 - fCenter2;
1766 fDiffRadius = fRadius2 - fRadius1;
1767 SkScalar inv = SkScalarInvert(fDiffRadius);
1768 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1769 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1770 fStartRadius = SkScalarMul(fRadius1, inv);
1771 fSr2D2 = SkScalarSquare(fStartRadius);
1772 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00001773 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001774
1775 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1776 fPtsToUnit.postScale(inv, inv);
1777 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001778};
1779
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780///////////////////////////////////////////////////////////////////////////////
1781
1782class Sweep_Gradient : public Gradient_Shader {
1783public:
1784 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1785 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001786 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1787 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001788 {
1789 fPtsToUnit.setTranslate(-cx, -cy);
1790 }
1791 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1792 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001793
1794 virtual BitmapType asABitmap(SkBitmap* bitmap,
1795 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001796 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001797 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001798 if (bitmap) {
1799 this->commonAsABitmap(bitmap);
1800 }
1801 if (matrix) {
1802 *matrix = fPtsToUnit;
1803 }
1804 if (xy) {
1805 xy[0] = fTileMode;
1806 xy[1] = kClamp_TileMode;
1807 }
1808 return kSweep_BitmapType;
1809 }
1810
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001811 virtual GradientType asAGradient(GradientInfo* info) const {
1812 if (info) {
1813 commonAsAGradient(info);
1814 info->fPoint[0] = fCenter;
1815 }
1816 return kSweep_GradientType;
1817 }
1818
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1820 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1821 }
1822
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001823 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1824 this->INHERITED::flatten(buffer);
1825 buffer.writeScalar(fCenter.fX);
1826 buffer.writeScalar(fCenter.fY);
1827 }
1828
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001830 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1831 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001832 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001833 }
1834
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835 virtual Factory getFactory() { return CreateProc; }
1836
1837private:
1838 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001839 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840};
1841
1842#ifdef COMPUTE_SWEEP_TABLE
1843#define PI 3.14159265
1844static bool gSweepTableReady;
1845static uint8_t gSweepTable[65];
1846
1847/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1848 We scale the results to [0..32]
1849*/
reed@google.com61eb0402011-04-15 12:11:12 +00001850static const uint8_t* build_sweep_table() {
1851 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 const int N = 65;
1853 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001854
reed@android.com8a1c16f2008-12-17 15:59:43 +00001855 for (int i = 0; i < N; i++)
1856 {
1857 double arg = i / DENOM;
1858 double v = atan(arg);
1859 int iv = (int)round(v * DENOM * 2 / PI);
1860// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1861 printf("%d, ", iv);
1862 gSweepTable[i] = iv;
1863 }
1864 gSweepTableReady = true;
1865 }
1866 return gSweepTable;
1867}
1868#else
1869static const uint8_t gSweepTable[] = {
1870 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1871 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1872 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1873 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1874 32
1875};
1876static const uint8_t* build_sweep_table() { return gSweepTable; }
1877#endif
1878
1879// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1880// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1881// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1882
1883//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001884static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001885 SkASSERT(numer <= denom);
1886 SkASSERT(numer > 0);
1887 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001888
reed@android.com8a1c16f2008-12-17 15:59:43 +00001889 int nbits = SkCLZ(numer);
1890 int dbits = SkCLZ(denom);
1891 int bits = 6 - nbits + dbits;
1892 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001893
reed@google.com61eb0402011-04-15 12:11:12 +00001894 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001896 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897
1898 denom <<= dbits - 1;
1899 numer <<= nbits - 1;
1900
1901 unsigned result = 0;
1902
1903 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001904 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001905 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001906 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001908 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001909
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001911 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912 // make room for the rest of the answer bits
1913 result <<= bits;
1914 switch (bits) {
1915 case 6:
1916 if ((numer = (numer << 1) - denom) >= 0)
1917 result |= 32;
1918 else
1919 numer += denom;
1920 case 5:
1921 if ((numer = (numer << 1) - denom) >= 0)
1922 result |= 16;
1923 else
1924 numer += denom;
1925 case 4:
1926 if ((numer = (numer << 1) - denom) >= 0)
1927 result |= 8;
1928 else
1929 numer += denom;
1930 case 3:
1931 if ((numer = (numer << 1) - denom) >= 0)
1932 result |= 4;
1933 else
1934 numer += denom;
1935 case 2:
1936 if ((numer = (numer << 1) - denom) >= 0)
1937 result |= 2;
1938 else
1939 numer += denom;
1940 case 1:
1941 default: // not strictly need, but makes GCC make better ARM code
1942 if ((numer = (numer << 1) - denom) >= 0)
1943 result |= 1;
1944 else
1945 numer += denom;
1946 }
1947 }
1948 return result;
1949}
1950
1951// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001952static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001953#ifdef SK_DEBUG
1954 {
1955 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001956 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 gOnce = true;
1958 SkASSERT(div_64(55, 55) == 64);
1959 SkASSERT(div_64(128, 256) == 32);
1960 SkASSERT(div_64(2326528, 4685824) == 31);
1961 SkASSERT(div_64(753664, 5210112) == 9);
1962 SkASSERT(div_64(229376, 4882432) == 3);
1963 SkASSERT(div_64(2, 64) == 2);
1964 SkASSERT(div_64(1, 64) == 1);
1965 // test that we handle underflow correctly
1966 SkASSERT(div_64(12345, 0x54321234) == 0);
1967 }
1968 }
1969#endif
1970
1971 SkASSERT(y > 0 && x > 0);
1972 const uint8_t* table = build_sweep_table();
1973
1974 unsigned result;
1975 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00001976 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 // first part of the atan(v) = PI/2 - atan(1/v) identity
1978 // since our div_64 and table want v <= 1, where v = y/x
1979 SkTSwap<SkFixed>(x, y);
1980 }
1981
1982 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001983
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984#ifdef SK_DEBUG
1985 {
1986 unsigned result2 = SkDivBits(y, x, 6);
1987 SkASSERT(result2 == result ||
1988 (result == 1 && result2 == 0));
1989 }
1990#endif
1991
1992 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1993 result = table[result];
1994
reed@google.com61eb0402011-04-15 12:11:12 +00001995 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 // complete the atan(v) = PI/2 - atan(1/v) identity
1997 result = 64 - result;
1998 // pin to 63
1999 result -= result >> 6;
2000 }
2001
2002 SkASSERT(result <= 63);
2003 return result;
2004}
2005
2006// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002007#ifdef SK_SCALAR_IS_FLOAT
2008static unsigned SkATan2_255(float y, float x) {
2009 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2010 static const float g255Over2PI = 40.584510488433314f;
2011
2012 float result = sk_float_atan2(y, x);
2013 if (result < 0) {
2014 result += 2 * SK_ScalarPI;
2015 }
2016 SkASSERT(result >= 0);
2017 // since our value is always >= 0, we can cast to int, which is faster than
2018 // calling floorf()
2019 int ir = (int)(result * g255Over2PI);
2020 SkASSERT(ir >= 0 && ir <= 255);
2021 return ir;
2022}
2023#else
reed@google.com61eb0402011-04-15 12:11:12 +00002024static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2025 if (x == 0) {
2026 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002027 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002028 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029 return y < 0 ? 192 : 64;
2030 }
reed@google.com61eb0402011-04-15 12:11:12 +00002031 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002033 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002034
reed@android.com8a1c16f2008-12-17 15:59:43 +00002035 /* Find the right quadrant for x,y
2036 Since atan_0_90 only handles the first quadrant, we rotate x,y
2037 appropriately before calling it, and then add the right amount
2038 to account for the real quadrant.
2039 quadrant 0 : add 0 | x > 0 && y > 0
2040 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2041 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2042 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002043
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 map x<0 to (1 << 6)
2045 map y<0 to (3 << 6)
2046 add = map_x ^ map_y
2047 */
2048 int xsign = x >> 31;
2049 int ysign = y >> 31;
2050 int add = ((-xsign) ^ (ysign & 3)) << 6;
2051
2052#ifdef SK_DEBUG
2053 if (0 == add)
2054 SkASSERT(x > 0 && y > 0);
2055 else if (64 == add)
2056 SkASSERT(x < 0 && y > 0);
2057 else if (128 == add)
2058 SkASSERT(x < 0 && y < 0);
2059 else if (192 == add)
2060 SkASSERT(x > 0 && y < 0);
2061 else
2062 SkASSERT(!"bad value for add");
2063#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002064
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2066 where we need to rotate x,y by 90 or -90
2067 */
2068 x = (x ^ xsign) - xsign;
2069 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002070 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002071 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002072 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073
2074 unsigned result = add + atan_0_90(y, x);
2075 SkASSERT(result < 256);
2076 return result;
2077}
reed@google.com51baf5a2011-09-21 13:38:36 +00002078#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002079
reed@google.comdd5bd672011-09-20 15:56:13 +00002080void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002081 SkMatrix::MapXYProc proc = fDstToIndexProc;
2082 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002083 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002084 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002085
reed@google.com61eb0402011-04-15 12:11:12 +00002086 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2088 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002089 SkScalar dx, fx = srcPt.fX;
2090 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002091
reed@google.com61eb0402011-04-15 12:11:12 +00002092 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093 SkFixed storage[2];
2094 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2095 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002096 dx = SkFixedToScalar(storage[0]);
2097 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002098 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002100 dx = matrix.getScaleX();
2101 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002103
reed@google.com61eb0402011-04-15 12:11:12 +00002104 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105 *dstC++ = cache[SkATan2_255(fy, fx)];
2106 fx += dx;
2107 fy += dy;
2108 }
reed@google.com61eb0402011-04-15 12:11:12 +00002109 } else { // perspective case
2110 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002111 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002112 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2113 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002114 }
2115 }
2116}
2117
reed@google.comdd5bd672011-09-20 15:56:13 +00002118void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119 SkMatrix::MapXYProc proc = fDstToIndexProc;
2120 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002121 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122 int toggle = ((x ^ y) & 1) << kCache16Bits;
2123 SkPoint srcPt;
2124
reed@google.com61eb0402011-04-15 12:11:12 +00002125 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2127 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002128 SkScalar dx, fx = srcPt.fX;
2129 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002130
reed@google.com61eb0402011-04-15 12:11:12 +00002131 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 SkFixed storage[2];
2133 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2134 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002135 dx = SkFixedToScalar(storage[0]);
2136 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002137 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002139 dx = matrix.getScaleX();
2140 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002142
reed@google.com61eb0402011-04-15 12:11:12 +00002143 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2145 *dstC++ = cache[toggle + index];
2146 toggle ^= (1 << kCache16Bits);
2147 fx += dx;
2148 fy += dy;
2149 }
reed@google.com61eb0402011-04-15 12:11:12 +00002150 } else { // perspective case
2151 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002152 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2153 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002154
reed@google.com51baf5a2011-09-21 13:38:36 +00002155 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 index >>= (8 - kCache16Bits);
2157 *dstC++ = cache[toggle + index];
2158 toggle ^= (1 << kCache16Bits);
2159 }
2160 }
2161}
2162
reed@google.com61eb0402011-04-15 12:11:12 +00002163///////////////////////////////////////////////////////////////////////////////
2164///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165
2166// assumes colors is SkColor* and pos is SkScalar*
2167#define EXPAND_1_COLOR(count) \
2168 SkColor tmp[2]; \
2169 do { \
2170 if (1 == count) { \
2171 tmp[0] = tmp[1] = colors[0]; \
2172 colors = tmp; \
2173 pos = NULL; \
2174 count = 2; \
2175 } \
2176 } while (0)
2177
reed@google.com61eb0402011-04-15 12:11:12 +00002178SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2179 const SkColor colors[],
2180 const SkScalar pos[], int colorCount,
2181 SkShader::TileMode mode,
2182 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183 if (NULL == pts || NULL == colors || colorCount < 1) {
2184 return NULL;
2185 }
2186 EXPAND_1_COLOR(colorCount);
2187
reed@android.comab840b82009-07-01 17:00:03 +00002188 return SkNEW_ARGS(Linear_Gradient,
2189 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190}
2191
reed@google.com61eb0402011-04-15 12:11:12 +00002192SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2193 const SkColor colors[],
2194 const SkScalar pos[], int colorCount,
2195 SkShader::TileMode mode,
2196 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197 if (radius <= 0 || NULL == colors || colorCount < 1) {
2198 return NULL;
2199 }
2200 EXPAND_1_COLOR(colorCount);
2201
reed@android.comab840b82009-07-01 17:00:03 +00002202 return SkNEW_ARGS(Radial_Gradient,
2203 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002204}
2205
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002206SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2207 SkScalar startRadius,
2208 const SkPoint& end,
2209 SkScalar endRadius,
2210 const SkColor colors[],
2211 const SkScalar pos[],
2212 int colorCount,
2213 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002214 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002215 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2216 return NULL;
2217 }
2218 EXPAND_1_COLOR(colorCount);
2219
2220 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002221 (start, startRadius, end, endRadius, colors, pos,
2222 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002223}
2224
reed@android.com8a1c16f2008-12-17 15:59:43 +00002225SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2226 const SkColor colors[],
2227 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002228 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002229 if (NULL == colors || count < 1) {
2230 return NULL;
2231 }
2232 EXPAND_1_COLOR(count);
2233
2234 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2235}
2236
2237static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2238 Linear_Gradient::CreateProc);
2239
2240static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2241 Radial_Gradient::CreateProc);
2242
2243static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2244 Sweep_Gradient::CreateProc);
tomhudson@google.com938d6042011-04-27 13:57:03 +00002245
2246static SkFlattenable::Registrar
2247 gTwoPointRadialGradientReg("Two_Point_Radial_Gradient",
2248 Two_Point_Radial_Gradient::CreateProc);