blob: af357a624bd3e16e25294aa445272a742a9010a5 [file] [log] [blame]
reed@google.com0e734bd2011-12-08 17:24:44 +00001
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002/*
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.com0e734bd2011-12-08 17:24:44 +000022#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
23 #define USE_DITHER_32BIT_GRADIENT
24#endif
25
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
reed@google.com7716afb2011-12-07 15:17:50 +0000120 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
121 virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000122 virtual bool isOpaque() const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000123
124protected:
125 Gradient_Shader(SkFlattenableReadBuffer& );
126 SkUnitMapper* fMapper;
127 SkMatrix fPtsToUnit; // set by subclass
128 SkMatrix fDstToIndex;
129 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130 TileMode fTileMode;
131 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000132 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133 uint8_t fDstToIndexClass;
134 uint8_t fFlags;
135
136 struct Rec {
137 SkFixed fPos; // 0...1
138 uint32_t fScale; // (1 << 24) / range
139 };
140 Rec* fRecs;
141
142 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000143 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000145 kCache16Mask = kCache16Count - 1,
146 kCache16Shift = 16 - kCache16Bits,
147
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148 kCache32Bits = 8, // pretty much should always be 8
149 kCache32Count = 1 << kCache32Bits
150 };
151 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000152 const uint16_t* getCache16() const;
153 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154
reed@google.com7c2f27d2011-03-07 19:29:00 +0000155 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000156 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000157
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158private:
159 enum {
160 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
161
reed@android.com1c12abe2009-07-02 15:01:02 +0000162 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163 };
164 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000165 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
166 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167
reed@google.com7c2f27d2011-03-07 19:29:00 +0000168 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
169 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170
reed@google.com7c2f27d2011-03-07 19:29:00 +0000171 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
172 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000173 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 +0000174
reed@android.com512a8762009-12-14 15:25:36 +0000175 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000176 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
177 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000178 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000179 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000180
reed@android.com8a1c16f2008-12-17 15:59:43 +0000181 typedef SkShader INHERITED;
182};
183
reed@android.com41bccf52009-04-03 13:33:51 +0000184static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185 SkASSERT(x >= 0 && x <= SK_Scalar1);
186
187#ifdef SK_SCALAR_IS_FLOAT
188 return (unsigned)(x * 0xFFFF);
189#else
190 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
191#endif
192}
193
reed@android.com41bccf52009-04-03 13:33:51 +0000194Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
195 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196 SkASSERT(colorCount > 1);
197
198 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
199
200 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000201 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
204 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
205 fTileMode = mode;
206 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000207
reed@android.com41bccf52009-04-03 13:33:51 +0000208 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000209 fCache32 = NULL;
210 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000211
reed@android.com41bccf52009-04-03 13:33:51 +0000212 /* Note: we let the caller skip the first and/or last position.
213 i.e. pos[0] = 0.3, pos[1] = 0.7
214 In these cases, we insert dummy entries to ensure that the final data
215 will be bracketed by [0, 1].
216 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
217
218 Thus colorCount (the caller's value, and fColorCount (our value) may
219 differ by up to 2. In the above example:
220 colorCount = 2
221 fColorCount = 4
222 */
223 fColorCount = colorCount;
224 // check if we need to add in dummy start and/or end position/colors
225 bool dummyFirst = false;
226 bool dummyLast = false;
227 if (pos) {
228 dummyFirst = pos[0] != 0;
229 dummyLast = pos[colorCount - 1] != SK_Scalar1;
230 fColorCount += dummyFirst + dummyLast;
231 }
232
233 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000234 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000235 fOrigColors = reinterpret_cast<SkColor*>(
236 sk_malloc_throw(size * fColorCount));
237 }
238 else {
239 fOrigColors = fStorage;
240 }
241
242 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 {
reed@android.com41bccf52009-04-03 13:33:51 +0000244 SkColor* origColors = fOrigColors;
245 if (dummyFirst) {
246 *origColors++ = colors[0];
247 }
248 memcpy(origColors, colors, colorCount * sizeof(SkColor));
249 if (dummyLast) {
250 origColors += colorCount;
251 *origColors = colors[colorCount - 1];
252 }
253 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000254
reed@android.com1c12abe2009-07-02 15:01:02 +0000255 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000256 if (fColorCount > 2) {
257 Rec* recs = fRecs;
258 recs->fPos = 0;
259 // recs->fScale = 0; // unused;
260 recs += 1;
261 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000262 /* We need to convert the user's array of relative positions into
263 fixed-point positions and scale factors. We need these results
264 to be strictly monotonic (no two values equal or out of order).
265 Hence this complex loop that just jams a zero for the scale
266 value if it sees a segment out of order, and it assures that
267 we start at 0 and end at 1.0
268 */
269 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000270 int startIndex = dummyFirst ? 0 : 1;
271 int count = colorCount + dummyLast;
272 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000273 // force the last value to be 1.0
274 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000275 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000276 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000277 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000278 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000279 }
reed@android.com41bccf52009-04-03 13:33:51 +0000280 // pin curr withing range
281 if (curr < 0) {
282 curr = 0;
283 } else if (curr > SK_Fixed1) {
284 curr = SK_Fixed1;
285 }
286 recs->fPos = curr;
287 if (curr > prev) {
288 recs->fScale = (1 << 24) / (curr - prev);
289 } else {
290 recs->fScale = 0; // ignore this segment
291 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000292 // get ready for the next value
293 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000294 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 }
reed@android.com41bccf52009-04-03 13:33:51 +0000296 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 SkFixed dp = SK_Fixed1 / (colorCount - 1);
298 SkFixed p = dp;
299 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000300 for (int i = 1; i < colorCount; i++) {
301 recs->fPos = p;
302 recs->fScale = scale;
303 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 p += dp;
305 }
306 }
307 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000308 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000309}
310
311Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000312 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313 fCacheAlpha = 256;
314
315 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
316
317 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000318 fCache32 = NULL;
319 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320
reed@android.com41bccf52009-04-03 13:33:51 +0000321 int colorCount = fColorCount = buffer.readU32();
322 if (colorCount > kColorStorageCount) {
323 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
324 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
325 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000327 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329
330 fTileMode = (TileMode)buffer.readU8();
331 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000332 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333 if (colorCount > 2) {
334 Rec* recs = fRecs;
335 recs[0].fPos = 0;
336 for (int i = 1; i < colorCount; i++) {
337 recs[i].fPos = buffer.readS32();
338 recs[i].fScale = buffer.readU32();
339 }
340 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000341 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000342 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343}
344
reed@android.com41bccf52009-04-03 13:33:51 +0000345Gradient_Shader::~Gradient_Shader() {
346 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000348 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000349 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000350 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000352 }
reed@google.com82065d62011-02-07 15:30:46 +0000353 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354}
355
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000356void Gradient_Shader::initCommon() {
357 fFlags = 0;
358 unsigned colorAlpha = 0xFF;
359 for (int i = 0; i < fColorCount; i++) {
360 colorAlpha &= SkColorGetA(fOrigColors[i]);
361 }
362 fColorsAreOpaque = colorAlpha == 0xFF;
363}
364
reed@android.com41bccf52009-04-03 13:33:51 +0000365void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366 this->INHERITED::flatten(buffer);
367 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000368 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
370 buffer.write8(fTileMode);
371 if (fColorCount > 2) {
372 Rec* recs = fRecs;
373 for (int i = 1; i < fColorCount; i++) {
374 buffer.write32(recs[i].fPos);
375 buffer.write32(recs[i].fScale);
376 }
377 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000378 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379}
380
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000381bool Gradient_Shader::isOpaque() const {
382 return fColorsAreOpaque;
383}
384
reed@android.com8a1c16f2008-12-17 15:59:43 +0000385bool Gradient_Shader::setContext(const SkBitmap& device,
386 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000387 const SkMatrix& matrix) {
388 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000390 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391
392 const SkMatrix& inverse = this->getTotalInverse();
393
394 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
395 return false;
396 }
397
398 fDstToIndexProc = fDstToIndex.getMapXYProc();
399 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
400
401 // now convert our colors in to PMColors
402 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000403
404 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000405 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406 fFlags |= kOpaqueAlpha_Flag;
407 }
408 // we can do span16 as long as our individual colors are opaque,
409 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000410 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411 fFlags |= kHasSpan16_Flag;
412 }
413
reed@google.com95eed982011-07-05 17:01:56 +0000414 this->setCacheAlpha(paintAlpha);
415 return true;
416}
417
418void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419 // if the new alpha differs from the previous time we were called, inval our cache
420 // this will trigger the cache to be rebuilt.
421 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000422 if (fCacheAlpha != alpha) {
423 fCache16 = NULL; // inval the cache
424 fCache32 = NULL; // inval the cache
425 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000426 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000427 if (fCache32PixelRef) {
428 fCache32PixelRef->notifyPixelsChanged();
429 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431}
432
reed@android.com41bccf52009-04-03 13:33:51 +0000433static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434 SkASSERT(a == SkToU8(a));
435 SkASSERT(b == SkToU8(b));
436 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000437 return a + ((b - a) * scale >> 8);
438}
439
reed@android.com41bccf52009-04-03 13:33:51 +0000440static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
441 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000442#if 0
443 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
444 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
445 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
446 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
447
448 return SkPackARGB32(a, r, g, b);
449#else
450 int otherBlend = 256 - blend;
451
452#if 0
453 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
454 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
455 SkASSERT((t0 & t1) == 0);
456 return t0 | t1;
457#else
458 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
459 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
460#endif
461
462#endif
463}
464
465#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
466
reed@android.com41bccf52009-04-03 13:33:51 +0000467/** We take the original colors, not our premultiplied PMColors, since we can
468 build a 16bit table as long as the original colors are opaque, even if the
469 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470*/
reed@android.com512a8762009-12-14 15:25:36 +0000471void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
472 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000473 SkASSERT(count > 1);
474 SkASSERT(SkColorGetA(c0) == 0xFF);
475 SkASSERT(SkColorGetA(c1) == 0xFF);
476
477 SkFixed r = SkColorGetR(c0);
478 SkFixed g = SkColorGetG(c0);
479 SkFixed b = SkColorGetB(c0);
480
481 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
482 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
483 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
484
485 r = SkIntToFixed(r) + 0x8000;
486 g = SkIntToFixed(g) + 0x8000;
487 b = SkIntToFixed(b) + 0x8000;
488
489 do {
490 unsigned rr = r >> 16;
491 unsigned gg = g >> 16;
492 unsigned bb = b >> 16;
493 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000494 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000495 cache += 1;
496 r += dr;
497 g += dg;
498 b += db;
499 } while (--count != 0);
500}
501
reed@google.com55b8e8c2011-01-13 16:22:35 +0000502/*
503 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
504 * semantics of how we 2x2 dither 32->16
505 */
506static inline U8CPU dither_fixed_to_8(SkFixed n) {
507 n >>= 8;
508 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
509}
510
511/*
512 * For dithering with premultiply, we want to ceiling the alpha component,
513 * to ensure that it is always >= any color component.
514 */
515static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
516 n >>= 8;
517 return ((n << 1) - (n | (n >> 8))) >> 8;
518}
519
520void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
521 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 SkASSERT(count > 1);
523
reed@android.com1c12abe2009-07-02 15:01:02 +0000524 // need to apply paintAlpha to our two endpoints
525 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
526 SkFixed da;
527 {
528 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
529 da = SkIntToFixed(tmp - a) / (count - 1);
530 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531
reed@android.com1c12abe2009-07-02 15:01:02 +0000532 SkFixed r = SkColorGetR(c0);
533 SkFixed g = SkColorGetG(c0);
534 SkFixed b = SkColorGetB(c0);
535 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
536 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
537 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000538
539 a = SkIntToFixed(a) + 0x8000;
540 r = SkIntToFixed(r) + 0x8000;
541 g = SkIntToFixed(g) + 0x8000;
542 b = SkIntToFixed(b) + 0x8000;
543
544 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000545 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
546 cache[kCache32Count] = SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
547 dither_fixed_to_8(r),
548 dither_fixed_to_8(g),
549 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000550 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000551 a += da;
552 r += dr;
553 g += dg;
554 b += db;
555 } while (--count != 0);
556}
557
reed@android.com41bccf52009-04-03 13:33:51 +0000558static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000559 SkASSERT((unsigned)x <= SK_Fixed1);
560 return x - (x >> 16);
561}
562
reed@android.com200645d2009-12-14 16:41:57 +0000563static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000564 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000565 if (6 == bits) {
566 return (x << 10) | (x << 4) | (x >> 2);
567 }
568 if (8 == bits) {
569 return (x << 8) | x;
570 }
571 sk_throw();
572 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573}
574
reed@google.com7c2f27d2011-03-07 19:29:00 +0000575const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000576 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000577 // double the count for dither entries
578 const int entryCount = kCache16Count * 2;
579 const size_t allocSize = sizeof(uint16_t) * entryCount;
580
reed@android.com3c9b2a42009-08-27 19:28:37 +0000581 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000582 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000583 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000585 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000586 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000587 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588 Rec* rec = fRecs;
589 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000590 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000591 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000592 SkASSERT(nextIndex < kCache16Count);
593
594 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000595 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 prevIndex = nextIndex;
597 }
598 SkASSERT(prevIndex == kCache16Count - 1);
599 }
600
reed@android.com41bccf52009-04-03 13:33:51 +0000601 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000602 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603 uint16_t* linear = fCache16; // just computed linear data
604 uint16_t* mapped = fCache16Storage; // storage for mapped data
605 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000606 for (int i = 0; i < kCache16Count; i++) {
607 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000608 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000609 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000610 }
611 sk_free(fCache16);
612 fCache16 = fCache16Storage;
613 }
614 }
615 return fCache16;
616}
617
reed@google.com7c2f27d2011-03-07 19:29:00 +0000618const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000619 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000620 // double the count for dither entries
621 const int entryCount = kCache32Count * 2;
622 const size_t allocSize = sizeof(SkPMColor) * entryCount;
623
reed@google.comdc731fd2010-12-23 15:19:47 +0000624 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000625 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
626 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000627 }
628 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000629 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000630 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
631 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000632 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 Rec* rec = fRecs;
634 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000635 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
637 SkASSERT(nextIndex < kCache32Count);
638
639 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000640 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
641 fOrigColors[i],
642 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643 prevIndex = nextIndex;
644 }
645 SkASSERT(prevIndex == kCache32Count - 1);
646 }
647
reed@android.com41bccf52009-04-03 13:33:51 +0000648 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000649 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000650 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000652 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000654 for (int i = 0; i < kCache32Count; i++) {
655 int index = map->mapUnit16((i << 8) | i) >> 8;
656 mapped[i] = linear[index];
657 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000658 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000659 fCache32PixelRef->unref();
660 fCache32PixelRef = newPR;
661 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 }
663 }
664 return fCache32;
665}
666
reed@google.comdc731fd2010-12-23 15:19:47 +0000667/*
668 * Because our caller might rebuild the same (logically the same) gradient
669 * over and over, we'd like to return exactly the same "bitmap" if possible,
670 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
671 * To do that, we maintain a private cache of built-bitmaps, based on our
672 * colors and positions. Note: we don't try to flatten the fMapper, so if one
673 * is present, we skip the cache for now.
674 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000675void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000676 // our caller assumes no external alpha, so we ensure that our cache is
677 // built with 0xFF
678 this->setCacheAlpha(0xFF);
679
reed@google.comdc731fd2010-12-23 15:19:47 +0000680 // don't have a way to put the mapper into our cache-key yet
681 if (fMapper) {
682 // force our cahce32pixelref to be built
683 (void)this->getCache32();
684 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
685 bitmap->setPixelRef(fCache32PixelRef);
686 return;
687 }
688
689 // build our key: [numColors + colors[] + {positions[]} ]
690 int count = 1 + fColorCount;
691 if (fColorCount > 2) {
692 count += fColorCount - 1; // fRecs[].fPos
693 }
694
695 SkAutoSTMalloc<16, int32_t> storage(count);
696 int32_t* buffer = storage.get();
697
698 *buffer++ = fColorCount;
699 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
700 buffer += fColorCount;
701 if (fColorCount > 2) {
702 for (int i = 1; i < fColorCount; i++) {
703 *buffer++ = fRecs[i].fPos;
704 }
705 }
706 SkASSERT(buffer - storage.get() == count);
707
708 ///////////////////////////////////
709
710 static SkMutex gMutex;
711 static SkBitmapCache* gCache;
712 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
713 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
714 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000715
reed@google.comdc731fd2010-12-23 15:19:47 +0000716 if (NULL == gCache) {
717 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
718 }
719 size_t size = count * sizeof(int32_t);
720
721 if (!gCache->find(storage.get(), size, bitmap)) {
722 // force our cahce32pixelref to be built
723 (void)this->getCache32();
724 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
725 bitmap->setPixelRef(fCache32PixelRef);
726
727 gCache->add(storage.get(), size, *bitmap);
728 }
729}
730
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000731void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
732 if (info) {
733 if (info->fColorCount >= fColorCount) {
734 if (info->fColors) {
735 memcpy(info->fColors, fOrigColors,
736 fColorCount * sizeof(SkColor));
737 }
738 if (info->fColorOffsets) {
739 if (fColorCount == 2) {
740 info->fColorOffsets[0] = 0;
741 info->fColorOffsets[1] = SK_Scalar1;
742 } else if (fColorCount > 2) {
743 for (int i = 0; i < fColorCount; i++)
744 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
745 }
746 }
747 }
748 info->fColorCount = fColorCount;
749 info->fTileMode = fTileMode;
750 }
751}
752
reed@google.com61eb0402011-04-15 12:11:12 +0000753///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000754
reed@android.com41bccf52009-04-03 13:33:51 +0000755static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000756 SkVector vec = pts[1] - pts[0];
757 SkScalar mag = vec.length();
758 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
759
760 vec.scale(inv);
761 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
762 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
763 matrix->postScale(inv, inv);
764}
765
766///////////////////////////////////////////////////////////////////////////////
767
768class Linear_Gradient : public Gradient_Shader {
769public:
770 Linear_Gradient(const SkPoint pts[2],
771 const SkColor colors[], const SkScalar pos[], int colorCount,
772 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000773 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
774 fStart(pts[0]),
775 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 {
777 pts_to_unit_matrix(pts, &fPtsToUnit);
778 }
reed@android.com9b46e772009-06-05 12:24:41 +0000779
reed@google.com7716afb2011-12-07 15:17:50 +0000780 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
781 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
782 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
783 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
784 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
785 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786
reed@google.com55b8e8c2011-01-13 16:22:35 +0000787 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788 return SkNEW_ARGS(Linear_Gradient, (buffer));
789 }
790
reed@google.com7716afb2011-12-07 15:17:50 +0000791 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000792 this->INHERITED::flatten(buffer);
793 buffer.writeScalar(fStart.fX);
794 buffer.writeScalar(fStart.fY);
795 buffer.writeScalar(fEnd.fX);
796 buffer.writeScalar(fEnd.fY);
797 }
798
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000800 Linear_Gradient(SkFlattenableReadBuffer& buffer)
801 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000802 fStart(unflatten_point(buffer)),
803 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000804 }
reed@google.com7716afb2011-12-07 15:17:50 +0000805 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806
807private:
808 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000809 const SkPoint fStart;
810 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811};
812
reed@android.com5119bdb2009-06-12 21:27:03 +0000813bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
814 const SkMatrix& matrix) {
815 if (!this->INHERITED::setContext(device, paint, matrix)) {
816 return false;
817 }
818
819 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
820 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000821 fFlags |= SkShader::kConstInY32_Flag;
822 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
823 // only claim this if we do have a 16bit mode (i.e. none of our
824 // colors have alpha), and if we are not dithering (which obviously
825 // is not const in Y).
826 fFlags |= SkShader::kConstInY16_Flag;
827 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000828 }
829 return true;
830}
831
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@google.com61eb0402011-04-15 12:11:12 +0000833static inline bool no_need_for_clamp(int fx, int dx, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834 SkASSERT(count > 0);
835 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
836}
837
reed@google.com5eb158d2011-04-15 15:50:34 +0000838#include "SkClampRange.h"
839
840#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000841 do { \
842 unsigned fi = fx >> 8; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000843 SkASSERT(fi <= 0xFF); \
844 fx += dx; \
845 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000846 toggle ^= TOGGLE_MASK; \
847 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000848
849
reed@google.comdd5bd672011-09-20 15:56:13 +0000850void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851 SkASSERT(count > 0);
852
853 SkPoint srcPt;
854 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
855 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +0000856 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +0000857#ifdef USE_DITHER_32BIT_GRADIENT
reed@google.com55b8e8c2011-01-13 16:22:35 +0000858 int toggle = ((x ^ y) & 1) << kCache32Bits;
859 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@google.com0e734bd2011-12-08 17:24:44 +0000860#else
861 int toggle = 0;
862 const int TOGGLE_MASK = 0;
863#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864
reed@android.comc552a432009-06-12 20:02:50 +0000865 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000866 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
867 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
869
reed@android.comc552a432009-06-12 20:02:50 +0000870 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871 SkFixed dxStorage[1];
872 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
873 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000874 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
876 dx = SkScalarToFixed(fDstToIndex.getScaleX());
877 }
878
reed@android.comc552a432009-06-12 20:02:50 +0000879 if (SkFixedNearlyZero(dx)) {
880 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 unsigned fi = proc(fx);
882 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000883 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000884 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000885 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000886 SkClampRange range;
887 range.init(fx, dx, count, 0, 0xFF);
888
889 if ((count = range.fCount0) > 0) {
890 sk_memset32_dither(dstC,
891 cache[toggle + range.fV0],
892 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
893 count);
894 dstC += count;
895 }
896 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +0000897 int unroll = count >> 3;
898 fx = range.fFx1;
899 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000900 NO_CHECK_ITER; NO_CHECK_ITER;
901 NO_CHECK_ITER; NO_CHECK_ITER;
902 NO_CHECK_ITER; NO_CHECK_ITER;
903 NO_CHECK_ITER; NO_CHECK_ITER;
904 }
905 if ((count &= 7) > 0) {
906 do {
907 NO_CHECK_ITER;
908 } while (--count != 0);
909 }
910 }
911 if ((count = range.fCount2) > 0) {
912 sk_memset32_dither(dstC,
913 cache[toggle + range.fV1],
914 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
915 count);
916 }
reed@android.comc552a432009-06-12 20:02:50 +0000917 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000918 do {
919 unsigned fi = mirror_8bits(fx >> 8);
920 SkASSERT(fi <= 0xFF);
921 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000922 *dstC++ = cache[toggle + fi];
923 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000924 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000925 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000926 SkASSERT(proc == repeat_tileproc);
927 do {
928 unsigned fi = repeat_8bits(fx >> 8);
929 SkASSERT(fi <= 0xFF);
930 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000931 *dstC++ = cache[toggle + fi];
932 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000933 } while (--count != 0);
934 }
reed@android.comc552a432009-06-12 20:02:50 +0000935 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000936 SkScalar dstX = SkIntToScalar(x);
937 SkScalar dstY = SkIntToScalar(y);
938 do {
939 dstProc(fDstToIndex, dstX, dstY, &srcPt);
940 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
941 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000942 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
943 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000944 dstX += SK_Scalar1;
945 } while (--count != 0);
946 }
947}
948
reed@google.com55b8e8c2011-01-13 16:22:35 +0000949SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000950 SkMatrix* matrix,
951 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000952 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000954 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955 }
956 if (matrix) {
957 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
958 matrix->preConcat(fPtsToUnit);
959 }
960 if (xy) {
961 xy[0] = fTileMode;
962 xy[1] = kClamp_TileMode;
963 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000964 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965}
966
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000967SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
968 if (info) {
969 commonAsAGradient(info);
970 info->fPoint[0] = fStart;
971 info->fPoint[1] = fEnd;
972 }
973 return kLinear_GradientType;
974}
975
reed@android.com3c9b2a42009-08-27 19:28:37 +0000976static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
977 int count) {
978 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979 *dst++ = value;
980 count -= 1;
981 SkTSwap(value, other);
982 }
983
984 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000985
reed@android.com3c9b2a42009-08-27 19:28:37 +0000986 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000987 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000988 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000990
reed@google.com5eb158d2011-04-15 15:50:34 +0000991#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +0000992 do { \
993 unsigned fi = fx >> kCache16Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000994 SkASSERT(fi <= kCache16Mask); \
995 fx += dx; \
996 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000997 toggle ^= TOGGLE_MASK; \
998 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000999
1000
reed@google.comdd5bd672011-09-20 15:56:13 +00001001void Linear_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001002 SkASSERT(count > 0);
1003
1004 SkPoint srcPt;
1005 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1006 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001007 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@google.com5eb158d2011-04-15 15:50:34 +00001009 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001011 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001012 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1013 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1015
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001016 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017 SkFixed dxStorage[1];
1018 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1019 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001020 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001021 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1022 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1023 }
1024
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001025 if (SkFixedNearlyZero(dx)) {
1026 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +00001027 unsigned fi = proc(fx) >> kCache16Shift;
1028 SkASSERT(fi <= kCache16Mask);
reed@google.com5eb158d2011-04-15 15:50:34 +00001029 dither_memset16(dstC, cache[toggle + fi],
1030 cache[(toggle ^ TOGGLE_MASK) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001031 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001032 SkClampRange range;
1033 range.init(fx, dx, count, 0, kCache16Mask);
1034
1035 if ((count = range.fCount0) > 0) {
1036 dither_memset16(dstC,
1037 cache[toggle + range.fV0],
1038 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
1039 count);
1040 dstC += count;
1041 }
1042 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +00001043 int unroll = count >> 3;
1044 fx = range.fFx1;
1045 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001046 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1047 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1048 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1049 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1050 }
1051 if ((count &= 7) > 0) {
1052 do {
1053 NO_CHECK_ITER_16;
1054 } while (--count != 0);
1055 }
1056 }
1057 if ((count = range.fCount2) > 0) {
1058 dither_memset16(dstC,
1059 cache[toggle + range.fV1],
1060 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
1061 count);
1062 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001063 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001064 do {
reed@android.com200645d2009-12-14 16:41:57 +00001065 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001066 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001067 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001068 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001069 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001071 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072 SkASSERT(proc == repeat_tileproc);
1073 do {
reed@android.com200645d2009-12-14 16:41:57 +00001074 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001075 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001076 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001077 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001078 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001079 } while (--count != 0);
1080 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001081 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 SkScalar dstX = SkIntToScalar(x);
1083 SkScalar dstY = SkIntToScalar(y);
1084 do {
1085 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1086 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1087 SkASSERT(fi <= 0xFFFF);
1088
reed@android.com512a8762009-12-14 15:25:36 +00001089 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001090 *dstC++ = cache[toggle + index];
reed@google.com5eb158d2011-04-15 15:50:34 +00001091 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001092
1093 dstX += SK_Scalar1;
1094 } while (--count != 0);
1095 }
1096}
1097
1098///////////////////////////////////////////////////////////////////////////////
1099
1100#define kSQRT_TABLE_BITS 11
1101#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1102
1103#include "SkRadialGradient_Table.h"
1104
1105#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1106
1107#include <stdio.h>
1108
reed@google.com61eb0402011-04-15 12:11:12 +00001109void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001110 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1111
1112 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1113 SkASSERT(file);
1114 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1115
reed@google.com61eb0402011-04-15 12:11:12 +00001116 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1117 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001119 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120
1121 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1122
1123 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001124 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001125 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001126 }
1127 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001129 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001130 }
1131 ::fprintf(file, "};\n");
1132 ::fclose(file);
1133}
1134
1135#endif
1136
1137
reed@google.com61eb0402011-04-15 12:11:12 +00001138static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1139 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001140 SkScalar inv = SkScalarInvert(radius);
1141
1142 matrix->setTranslate(-center.fX, -center.fY);
1143 matrix->postScale(inv, inv);
1144}
1145
1146class Radial_Gradient : public Gradient_Shader {
1147public:
1148 Radial_Gradient(const SkPoint& center, SkScalar radius,
1149 const SkColor colors[], const SkScalar pos[], int colorCount,
1150 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001151 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1152 fCenter(center),
1153 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001154 {
1155 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1156 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1157
1158 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1159 }
reed@google.com61eb0402011-04-15 12:11:12 +00001160
reed@google.com8e6d9142011-12-07 15:30:34 +00001161 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count) SK_OVERRIDE;
reed@google.com7716afb2011-12-07 15:17:50 +00001162 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001163 SkASSERT(count > 0);
1164
1165 SkPoint srcPt;
1166 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1167 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001168 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001169 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001170
reed@android.com3c9b2a42009-08-27 19:28:37 +00001171 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001172 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1173 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001174 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1175 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1176
reed@android.com3c9b2a42009-08-27 19:28:37 +00001177 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001178 SkFixed storage[2];
1179 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1180 dx = storage[0];
1181 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001182 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001183 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1184 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1185 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1186 }
1187
reed@android.com3c9b2a42009-08-27 19:28:37 +00001188 if (proc == clamp_tileproc) {
reed@google.comdd5bd672011-09-20 15:56:13 +00001189 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001190
1191 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1192 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1193 precision, but that appears to be visually OK. If we decide this is OK for
1194 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1195 to avoid having to do these extra shifts each time.
1196 */
1197 fx >>= 1;
1198 dx >>= 1;
1199 fy >>= 1;
1200 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001201 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 +00001202 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1203 fy *= fy;
1204 do {
1205 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1206 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1207 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1208 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001209 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1210 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001212 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 do {
1214 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1215 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1216 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1217 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1218 fx += dx;
1219 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1221 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 } while (--count != 0);
1223 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001224 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225 do {
1226 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1227 unsigned fi = mirror_tileproc(dist);
1228 SkASSERT(fi <= 0xFFFF);
1229 fx += dx;
1230 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1232 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001234 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 SkASSERT(proc == repeat_tileproc);
1236 do {
1237 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1238 unsigned fi = repeat_tileproc(dist);
1239 SkASSERT(fi <= 0xFFFF);
1240 fx += dx;
1241 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1243 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 } while (--count != 0);
1245 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001246 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001247 SkScalar dstX = SkIntToScalar(x);
1248 SkScalar dstY = SkIntToScalar(y);
1249 do {
1250 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1251 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1252 SkASSERT(fi <= 0xFFFF);
1253
1254 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255 *dstC++ = cache[toggle + index];
1256 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257
1258 dstX += SK_Scalar1;
1259 } while (--count != 0);
1260 }
1261 }
1262
reed@google.com55b8e8c2011-01-13 16:22:35 +00001263 virtual BitmapType asABitmap(SkBitmap* bitmap,
1264 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001265 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00001266 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001267 if (bitmap) {
1268 this->commonAsABitmap(bitmap);
1269 }
1270 if (matrix) {
1271 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1272 matrix->preConcat(fPtsToUnit);
1273 }
1274 if (xy) {
1275 xy[0] = fTileMode;
1276 xy[1] = kClamp_TileMode;
1277 }
1278 return kRadial_BitmapType;
1279 }
reed@google.com7716afb2011-12-07 15:17:50 +00001280 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001281 if (info) {
1282 commonAsAGradient(info);
1283 info->fPoint[0] = fCenter;
1284 info->fRadius[0] = fRadius;
1285 }
1286 return kRadial_GradientType;
1287 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001288
reed@google.com8e6d9142011-12-07 15:30:34 +00001289 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290 return SkNEW_ARGS(Radial_Gradient, (buffer));
1291 }
1292
reed@google.com7716afb2011-12-07 15:17:50 +00001293 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001294 this->INHERITED::flatten(buffer);
1295 buffer.writeScalar(fCenter.fX);
1296 buffer.writeScalar(fCenter.fY);
1297 buffer.writeScalar(fRadius);
1298 }
1299
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001301 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1302 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001303 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001304 fRadius(buffer.readScalar()) {
1305 }
reed@google.com7716afb2011-12-07 15:17:50 +00001306 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307
1308private:
1309 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001310 const SkPoint fCenter;
1311 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312};
1313
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001314static inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
1315 // fast, overly-conservative test: checks unit square instead
1316 // of unit circle
1317 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1318 (fx <= -SK_FixedHalf && dx <= 0);
1319 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1320 (fy <= -SK_FixedHalf && dy <= 0);
1321
1322 return xClamped || yClamped;
1323}
1324
1325// Return true if (fx * fy) is always inside the unit circle
1326// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1327// so it shouldn't be run if count is small.
1328static inline bool no_need_for_radial_pin(int fx, int dx,
1329 int fy, int dy, int count) {
1330 SkASSERT(count > 0);
1331 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1332 return false;
1333 }
1334 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1335 return false;
1336 }
1337 fx += (count - 1) * dx;
1338 fy += (count - 1) * dy;
1339 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1340 return false;
1341 }
1342 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1343}
1344
1345#define UNPINNED_RADIAL_STEP \
1346 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
1347 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)]; \
1348 fx += dx; \
1349 fy += dy;
1350
1351// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
1352static void radial_clamp(SkFixed fx, SkFixed fy, SkFixed dx, SkFixed dy,
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001353 SkPMColor* SK_RESTRICT dstC, int count,
1354 const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001355 const int kCache32Bits, const int kCache32Count) {
1356 // Floating point seems to be slower than fixed point,
1357 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001358 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001359 fx >>= 1;
1360 dx >>= 1;
1361 fy >>= 1;
1362 dy >>= 1;
1363 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
1364 sk_memset32(dstC, cache[kCache32Count - 1], count);
1365 } else if ((count > 4) &&
1366 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1367 unsigned fi;
1368 // 4x unroll appears to be no faster than 2x unroll on Linux
1369 while (count > 1) {
1370 UNPINNED_RADIAL_STEP;
1371 UNPINNED_RADIAL_STEP;
1372 count -= 2;
1373 }
1374 if (count) {
1375 UNPINNED_RADIAL_STEP;
1376 }
1377 }
1378 else {
1379 do {
1380 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1381 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1382 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1383 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1384 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1385 fx += dx;
1386 fy += dy;
1387 } while (--count != 0);
1388 }
1389}
1390
1391void Radial_Gradient::shadeSpan(int x, int y,
1392 SkPMColor* SK_RESTRICT dstC, int count) {
1393 SkASSERT(count > 0);
1394
1395 SkPoint srcPt;
1396 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1397 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001398 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001399
1400 if (fDstToIndexClass != kPerspective_MatrixClass) {
1401 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1402 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1403 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1404 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1405#ifdef SK_USE_FLOAT_SQRT
1406 float fdx, fdy;
1407#endif
1408
1409 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1410 SkFixed storage[2];
1411 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1412 dx = storage[0];
1413 dy = storage[1];
1414#ifdef SK_USE_FLOAT_SQRT
1415 fdx = SkFixedToFloat(storage[0]);
1416 fdy = SkFixedToFloat(storage[1]);
1417#endif
1418 } else {
1419 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1420 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1421 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1422#ifdef SK_USE_FLOAT_SQRT
1423 fdx = fDstToIndex.getScaleX();
1424 fdy = fDstToIndex.getSkewY();
1425#endif
1426 }
1427
1428 if (proc == clamp_tileproc) {
1429 radial_clamp(fx, fy, dx, dy, dstC, count, cache,
1430 kCache32Bits, kCache32Count);
1431 } else if (proc == mirror_tileproc) {
1432#ifdef SK_USE_FLOAT_SQRT
1433 float ffx = srcPt.fX;
1434 float ffy = srcPt.fY;
1435 do {
1436 float fdist = sk_float_sqrt(ffx*ffx + ffy*ffy);
1437 unsigned fi = mirror_tileproc(SkFloatToFixed(fdist));
1438 SkASSERT(fi <= 0xFFFF);
1439 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1440 ffx += fdx;
1441 ffy += fdy;
1442 } while (--count != 0);
1443#else
1444 do {
1445 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1446 SkFixedSquare(fy);
1447 if (magnitudeSquared < 0) // Overflow.
1448 magnitudeSquared = SK_FixedMax;
1449 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1450 unsigned fi = mirror_tileproc(dist);
1451 SkASSERT(fi <= 0xFFFF);
1452 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1453 fx += dx;
1454 fy += dy;
1455 } while (--count != 0);
1456#endif
1457 } else {
1458 SkASSERT(proc == repeat_tileproc);
1459 do {
1460 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1461 SkFixedSquare(fy);
1462 if (magnitudeSquared < 0) // Overflow.
1463 magnitudeSquared = SK_FixedMax;
1464 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1465 unsigned fi = repeat_tileproc(dist);
1466 SkASSERT(fi <= 0xFFFF);
1467 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1468 fx += dx;
1469 fy += dy;
1470 } while (--count != 0);
1471 }
1472 } else { // perspective case
1473 SkScalar dstX = SkIntToScalar(x);
1474 SkScalar dstY = SkIntToScalar(y);
1475 do {
1476 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1477 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1478 SkASSERT(fi <= 0xFFFF);
1479 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1480 dstX += SK_Scalar1;
1481 } while (--count != 0);
1482 }
1483}
1484
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001485/* Two-point radial gradients are specified by two circles, each with a center
1486 point and radius. The gradient can be considered to be a series of
1487 concentric circles, with the color interpolated from the start circle
1488 (at t=0) to the end circle (at t=1).
1489
1490 For each point (x, y) in the span, we want to find the
1491 interpolated circle that intersects that point. The center
1492 of the desired circle (Cx, Cy) falls at some distance t
1493 along the line segment between the start point (Sx, Sy) and
1494 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001495
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001496 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1497 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001498
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001499 The radius of the desired circle (r) is also a linear interpolation t
1500 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001501
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001502 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001503
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001504 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001505
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001506 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001507
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001508 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001509
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001510 (x - ((1 - t) * Sx + t * Ex))^2
1511 + (y - ((1 - t) * Sy + t * Ey))^2
1512 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001513
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001514 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001515
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001516 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1517 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1518 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001519
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001520 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1521
1522 [Dx^2 + Dy^2 - Dr^2)] * t^2
1523 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1524 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001525
1526 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001527 possible circles on which the point may fall. Solving for t yields
1528 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001529
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001530 If a<0, the start circle is entirely contained in the
1531 end circle, and one of the roots will be <0 or >1 (off the line
1532 segment). If a>0, the start circle falls at least partially
1533 outside the end circle (or vice versa), and the gradient
1534 defines a "tube" where a point may be on one circle (on the
1535 inside of the tube) or the other (outside of the tube). We choose
1536 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001537
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001538 In order to keep the math to within the limits of fixed point,
1539 we divide the entire quadratic by Dr^2, and replace
1540 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001541
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001542 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1543 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1544 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001545
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001546 (x' and y' are computed by appending the subtract and scale to the
1547 fDstToIndex matrix in the constructor).
1548
1549 Since the 'A' component of the quadratic is independent of x' and y', it
1550 is precomputed in the constructor. Since the 'B' component is linear in
1551 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001552 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001553 a perspective projection), it must be computed in the loop.
1554
1555*/
1556
reed@google.com84e9c082011-04-13 17:44:24 +00001557static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1558 SkScalar sr2d2, SkScalar foura,
1559 SkScalar oneOverTwoA, bool posRoot) {
1560 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001561 if (0 == foura) {
1562 return SkScalarToFixed(SkScalarDiv(-c, b));
1563 }
1564
reed@google.com84e9c082011-04-13 17:44:24 +00001565 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001566 if (discrim < 0) {
1567 discrim = -discrim;
1568 }
reed@google.com84e9c082011-04-13 17:44:24 +00001569 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1570 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001571 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001572 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001573 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001574 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001575 }
reed@google.com84e9c082011-04-13 17:44:24 +00001576 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001577}
1578
1579class Two_Point_Radial_Gradient : public Gradient_Shader {
1580public:
1581 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1582 const SkPoint& end, SkScalar endRadius,
1583 const SkColor colors[], const SkScalar pos[],
1584 int colorCount, SkShader::TileMode mode,
1585 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001586 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1587 fCenter1(start),
1588 fCenter2(end),
1589 fRadius1(startRadius),
1590 fRadius2(endRadius) {
1591 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001592 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001593
1594 virtual BitmapType asABitmap(SkBitmap* bitmap,
1595 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001596 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001597 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001598 if (bitmap) {
1599 this->commonAsABitmap(bitmap);
1600 }
1601 SkScalar diffL = 0; // just to avoid gcc warning
1602 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001603 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001604 SkScalarSquare(fDiff.fY));
1605 }
1606 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001607 if (diffL) {
1608 SkScalar invDiffL = SkScalarInvert(diffL);
1609 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1610 SkScalarMul(invDiffL, fDiff.fX));
1611 } else {
1612 matrix->reset();
1613 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001614 matrix->preConcat(fPtsToUnit);
1615 }
1616 if (xy) {
1617 xy[0] = fTileMode;
1618 xy[1] = kClamp_TileMode;
1619 }
1620 if (NULL != twoPointRadialParams) {
1621 twoPointRadialParams[0] = diffL;
1622 twoPointRadialParams[1] = fStartRadius;
1623 twoPointRadialParams[2] = fDiffRadius;
1624 }
1625 return kTwoPointRadial_BitmapType;
1626 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001627
reed@google.com8e6d9142011-12-07 15:30:34 +00001628 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001629 if (info) {
1630 commonAsAGradient(info);
1631 info->fPoint[0] = fCenter1;
1632 info->fPoint[1] = fCenter2;
1633 info->fRadius[0] = fRadius1;
1634 info->fRadius[1] = fRadius2;
1635 }
1636 return kRadial2_GradientType;
1637 }
1638
reed@google.com8e6d9142011-12-07 15:30:34 +00001639 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001640 SkASSERT(count > 0);
1641
1642 // Zero difference between radii: fill with transparent black.
1643 if (fDiffRadius == 0) {
1644 sk_bzero(dstC, count * sizeof(*dstC));
1645 return;
1646 }
1647 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1648 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001649 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001650
1651 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001652 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001653 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001654 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001655 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1656 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001657 SkScalar dx, fx = srcPt.fX;
1658 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001659
reed@google.com61eb0402011-04-15 12:11:12 +00001660 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001661 SkFixed fixedX, fixedY;
1662 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1663 dx = SkFixedToScalar(fixedX);
1664 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001665 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001666 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001667 dx = fDstToIndex.getScaleX();
1668 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001669 }
reed@google.com84e9c082011-04-13 17:44:24 +00001670 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1671 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1672 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1673 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001674 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001675 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001676 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001677 SkFixed index = SkClampMax(t, 0xFFFF);
1678 SkASSERT(index <= 0xFFFF);
1679 *dstC++ = cache[index >> (16 - kCache32Bits)];
1680 fx += dx;
1681 fy += dy;
1682 b += db;
1683 }
reed@google.com61eb0402011-04-15 12:11:12 +00001684 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001685 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001686 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001687 SkFixed index = mirror_tileproc(t);
1688 SkASSERT(index <= 0xFFFF);
1689 *dstC++ = cache[index >> (16 - kCache32Bits)];
1690 fx += dx;
1691 fy += dy;
1692 b += db;
1693 }
reed@google.com61eb0402011-04-15 12:11:12 +00001694 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001695 SkASSERT(proc == repeat_tileproc);
1696 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001697 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001698 SkFixed index = repeat_tileproc(t);
1699 SkASSERT(index <= 0xFFFF);
1700 *dstC++ = cache[index >> (16 - kCache32Bits)];
1701 fx += dx;
1702 fy += dy;
1703 b += db;
1704 }
1705 }
reed@google.com61eb0402011-04-15 12:11:12 +00001706 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001707 SkScalar dstX = SkIntToScalar(x);
1708 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001709 for (; count > 0; --count) {
1710 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001711 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001712 SkScalar fx = srcPt.fX;
1713 SkScalar fy = srcPt.fY;
1714 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1715 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1716 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001717 SkFixed index = proc(t);
1718 SkASSERT(index <= 0xFFFF);
1719 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001720 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001721 }
1722 }
1723 }
1724
reed@android.com6c59a172009-09-22 20:24:05 +00001725 virtual bool setContext(const SkBitmap& device,
1726 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00001727 const SkMatrix& matrix) SK_OVERRIDE {
1728 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00001729 return false;
1730 }
1731
1732 // we don't have a span16 proc
1733 fFlags &= ~kHasSpan16_Flag;
1734 return true;
1735 }
1736
reed@google.com8e6d9142011-12-07 15:30:34 +00001737 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001738 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1739 }
1740
reed@google.com7716afb2011-12-07 15:17:50 +00001741 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00001742 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001743 buffer.writeScalar(fCenter1.fX);
1744 buffer.writeScalar(fCenter1.fY);
1745 buffer.writeScalar(fCenter2.fX);
1746 buffer.writeScalar(fCenter2.fY);
1747 buffer.writeScalar(fRadius1);
1748 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001749 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001750
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001751protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001752 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001753 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001754 fCenter1(unflatten_point(buffer)),
1755 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001756 fRadius1(buffer.readScalar()),
1757 fRadius2(buffer.readScalar()) {
1758 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001759 };
reed@google.com7716afb2011-12-07 15:17:50 +00001760 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001761
1762private:
1763 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001764 const SkPoint fCenter1;
1765 const SkPoint fCenter2;
1766 const SkScalar fRadius1;
1767 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001768 SkPoint fDiff;
1769 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001770
1771 void init() {
1772 fDiff = fCenter1 - fCenter2;
1773 fDiffRadius = fRadius2 - fRadius1;
1774 SkScalar inv = SkScalarInvert(fDiffRadius);
1775 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1776 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1777 fStartRadius = SkScalarMul(fRadius1, inv);
1778 fSr2D2 = SkScalarSquare(fStartRadius);
1779 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00001780 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001781
1782 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1783 fPtsToUnit.postScale(inv, inv);
1784 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001785};
1786
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787///////////////////////////////////////////////////////////////////////////////
1788
1789class Sweep_Gradient : public Gradient_Shader {
1790public:
1791 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1792 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001793 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1794 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 {
1796 fPtsToUnit.setTranslate(-cx, -cy);
1797 }
reed@google.com7716afb2011-12-07 15:17:50 +00001798 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
1799 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001800
1801 virtual BitmapType asABitmap(SkBitmap* bitmap,
1802 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001803 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00001804 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001805 if (bitmap) {
1806 this->commonAsABitmap(bitmap);
1807 }
1808 if (matrix) {
1809 *matrix = fPtsToUnit;
1810 }
1811 if (xy) {
1812 xy[0] = fTileMode;
1813 xy[1] = kClamp_TileMode;
1814 }
1815 return kSweep_BitmapType;
1816 }
1817
reed@google.com7716afb2011-12-07 15:17:50 +00001818 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001819 if (info) {
1820 commonAsAGradient(info);
1821 info->fPoint[0] = fCenter;
1822 }
1823 return kSweep_GradientType;
1824 }
1825
reed@google.com8e6d9142011-12-07 15:30:34 +00001826 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1828 }
1829
reed@google.com7716afb2011-12-07 15:17:50 +00001830 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001831 this->INHERITED::flatten(buffer);
1832 buffer.writeScalar(fCenter.fX);
1833 buffer.writeScalar(fCenter.fY);
1834 }
1835
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001837 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1838 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001839 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001840 }
1841
reed@google.com7716afb2011-12-07 15:17:50 +00001842 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001843
1844private:
1845 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001846 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847};
1848
1849#ifdef COMPUTE_SWEEP_TABLE
1850#define PI 3.14159265
1851static bool gSweepTableReady;
1852static uint8_t gSweepTable[65];
1853
1854/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1855 We scale the results to [0..32]
1856*/
reed@google.com61eb0402011-04-15 12:11:12 +00001857static const uint8_t* build_sweep_table() {
1858 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859 const int N = 65;
1860 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001861
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 for (int i = 0; i < N; i++)
1863 {
1864 double arg = i / DENOM;
1865 double v = atan(arg);
1866 int iv = (int)round(v * DENOM * 2 / PI);
1867// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1868 printf("%d, ", iv);
1869 gSweepTable[i] = iv;
1870 }
1871 gSweepTableReady = true;
1872 }
1873 return gSweepTable;
1874}
1875#else
1876static const uint8_t gSweepTable[] = {
1877 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1878 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1879 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1880 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1881 32
1882};
1883static const uint8_t* build_sweep_table() { return gSweepTable; }
1884#endif
1885
1886// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1887// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1888// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1889
1890//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001891static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001892 SkASSERT(numer <= denom);
1893 SkASSERT(numer > 0);
1894 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001895
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896 int nbits = SkCLZ(numer);
1897 int dbits = SkCLZ(denom);
1898 int bits = 6 - nbits + dbits;
1899 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001900
reed@google.com61eb0402011-04-15 12:11:12 +00001901 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001903 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904
1905 denom <<= dbits - 1;
1906 numer <<= nbits - 1;
1907
1908 unsigned result = 0;
1909
1910 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001911 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001913 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001915 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001916
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001918 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 // make room for the rest of the answer bits
1920 result <<= bits;
1921 switch (bits) {
1922 case 6:
1923 if ((numer = (numer << 1) - denom) >= 0)
1924 result |= 32;
1925 else
1926 numer += denom;
1927 case 5:
1928 if ((numer = (numer << 1) - denom) >= 0)
1929 result |= 16;
1930 else
1931 numer += denom;
1932 case 4:
1933 if ((numer = (numer << 1) - denom) >= 0)
1934 result |= 8;
1935 else
1936 numer += denom;
1937 case 3:
1938 if ((numer = (numer << 1) - denom) >= 0)
1939 result |= 4;
1940 else
1941 numer += denom;
1942 case 2:
1943 if ((numer = (numer << 1) - denom) >= 0)
1944 result |= 2;
1945 else
1946 numer += denom;
1947 case 1:
1948 default: // not strictly need, but makes GCC make better ARM code
1949 if ((numer = (numer << 1) - denom) >= 0)
1950 result |= 1;
1951 else
1952 numer += denom;
1953 }
1954 }
1955 return result;
1956}
1957
1958// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001959static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960#ifdef SK_DEBUG
1961 {
1962 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001963 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 gOnce = true;
1965 SkASSERT(div_64(55, 55) == 64);
1966 SkASSERT(div_64(128, 256) == 32);
1967 SkASSERT(div_64(2326528, 4685824) == 31);
1968 SkASSERT(div_64(753664, 5210112) == 9);
1969 SkASSERT(div_64(229376, 4882432) == 3);
1970 SkASSERT(div_64(2, 64) == 2);
1971 SkASSERT(div_64(1, 64) == 1);
1972 // test that we handle underflow correctly
1973 SkASSERT(div_64(12345, 0x54321234) == 0);
1974 }
1975 }
1976#endif
1977
1978 SkASSERT(y > 0 && x > 0);
1979 const uint8_t* table = build_sweep_table();
1980
1981 unsigned result;
1982 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00001983 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 // first part of the atan(v) = PI/2 - atan(1/v) identity
1985 // since our div_64 and table want v <= 1, where v = y/x
1986 SkTSwap<SkFixed>(x, y);
1987 }
1988
1989 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001990
reed@android.com8a1c16f2008-12-17 15:59:43 +00001991#ifdef SK_DEBUG
1992 {
1993 unsigned result2 = SkDivBits(y, x, 6);
1994 SkASSERT(result2 == result ||
1995 (result == 1 && result2 == 0));
1996 }
1997#endif
1998
1999 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2000 result = table[result];
2001
reed@google.com61eb0402011-04-15 12:11:12 +00002002 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 // complete the atan(v) = PI/2 - atan(1/v) identity
2004 result = 64 - result;
2005 // pin to 63
2006 result -= result >> 6;
2007 }
2008
2009 SkASSERT(result <= 63);
2010 return result;
2011}
2012
2013// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002014#ifdef SK_SCALAR_IS_FLOAT
2015static unsigned SkATan2_255(float y, float x) {
2016 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2017 static const float g255Over2PI = 40.584510488433314f;
2018
2019 float result = sk_float_atan2(y, x);
2020 if (result < 0) {
2021 result += 2 * SK_ScalarPI;
2022 }
2023 SkASSERT(result >= 0);
2024 // since our value is always >= 0, we can cast to int, which is faster than
2025 // calling floorf()
2026 int ir = (int)(result * g255Over2PI);
2027 SkASSERT(ir >= 0 && ir <= 255);
2028 return ir;
2029}
2030#else
reed@google.com61eb0402011-04-15 12:11:12 +00002031static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2032 if (x == 0) {
2033 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002035 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 return y < 0 ? 192 : 64;
2037 }
reed@google.com61eb0402011-04-15 12:11:12 +00002038 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002040 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002041
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 /* Find the right quadrant for x,y
2043 Since atan_0_90 only handles the first quadrant, we rotate x,y
2044 appropriately before calling it, and then add the right amount
2045 to account for the real quadrant.
2046 quadrant 0 : add 0 | x > 0 && y > 0
2047 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2048 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2049 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002050
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 map x<0 to (1 << 6)
2052 map y<0 to (3 << 6)
2053 add = map_x ^ map_y
2054 */
2055 int xsign = x >> 31;
2056 int ysign = y >> 31;
2057 int add = ((-xsign) ^ (ysign & 3)) << 6;
2058
2059#ifdef SK_DEBUG
2060 if (0 == add)
2061 SkASSERT(x > 0 && y > 0);
2062 else if (64 == add)
2063 SkASSERT(x < 0 && y > 0);
2064 else if (128 == add)
2065 SkASSERT(x < 0 && y < 0);
2066 else if (192 == add)
2067 SkASSERT(x > 0 && y < 0);
2068 else
2069 SkASSERT(!"bad value for add");
2070#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002071
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2073 where we need to rotate x,y by 90 or -90
2074 */
2075 x = (x ^ xsign) - xsign;
2076 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002077 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002078 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002079 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002080
2081 unsigned result = add + atan_0_90(y, x);
2082 SkASSERT(result < 256);
2083 return result;
2084}
reed@google.com51baf5a2011-09-21 13:38:36 +00002085#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002086
reed@google.comdd5bd672011-09-20 15:56:13 +00002087void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088 SkMatrix::MapXYProc proc = fDstToIndexProc;
2089 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002090 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002091 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002092
reed@google.com61eb0402011-04-15 12:11:12 +00002093 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002094 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2095 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002096 SkScalar dx, fx = srcPt.fX;
2097 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002098
reed@google.com61eb0402011-04-15 12:11:12 +00002099 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002100 SkFixed storage[2];
2101 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2102 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002103 dx = SkFixedToScalar(storage[0]);
2104 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002105 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002106 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002107 dx = matrix.getScaleX();
2108 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002109 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002110
reed@google.com61eb0402011-04-15 12:11:12 +00002111 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002112 *dstC++ = cache[SkATan2_255(fy, fx)];
2113 fx += dx;
2114 fy += dy;
2115 }
reed@google.com61eb0402011-04-15 12:11:12 +00002116 } else { // perspective case
2117 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002119 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2120 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121 }
2122 }
2123}
2124
reed@google.comdd5bd672011-09-20 15:56:13 +00002125void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 SkMatrix::MapXYProc proc = fDstToIndexProc;
2127 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002128 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129 int toggle = ((x ^ y) & 1) << kCache16Bits;
2130 SkPoint srcPt;
2131
reed@google.com61eb0402011-04-15 12:11:12 +00002132 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2134 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002135 SkScalar dx, fx = srcPt.fX;
2136 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002137
reed@google.com61eb0402011-04-15 12:11:12 +00002138 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 SkFixed storage[2];
2140 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2141 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002142 dx = SkFixedToScalar(storage[0]);
2143 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002144 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002146 dx = matrix.getScaleX();
2147 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002149
reed@google.com61eb0402011-04-15 12:11:12 +00002150 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2152 *dstC++ = cache[toggle + index];
2153 toggle ^= (1 << kCache16Bits);
2154 fx += dx;
2155 fy += dy;
2156 }
reed@google.com61eb0402011-04-15 12:11:12 +00002157 } else { // perspective case
2158 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002159 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2160 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002161
reed@google.com51baf5a2011-09-21 13:38:36 +00002162 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 index >>= (8 - kCache16Bits);
2164 *dstC++ = cache[toggle + index];
2165 toggle ^= (1 << kCache16Bits);
2166 }
2167 }
2168}
2169
reed@google.com61eb0402011-04-15 12:11:12 +00002170///////////////////////////////////////////////////////////////////////////////
2171///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002172
2173// assumes colors is SkColor* and pos is SkScalar*
2174#define EXPAND_1_COLOR(count) \
2175 SkColor tmp[2]; \
2176 do { \
2177 if (1 == count) { \
2178 tmp[0] = tmp[1] = colors[0]; \
2179 colors = tmp; \
2180 pos = NULL; \
2181 count = 2; \
2182 } \
2183 } while (0)
2184
reed@google.com61eb0402011-04-15 12:11:12 +00002185SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2186 const SkColor colors[],
2187 const SkScalar pos[], int colorCount,
2188 SkShader::TileMode mode,
2189 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190 if (NULL == pts || NULL == colors || colorCount < 1) {
2191 return NULL;
2192 }
2193 EXPAND_1_COLOR(colorCount);
2194
reed@android.comab840b82009-07-01 17:00:03 +00002195 return SkNEW_ARGS(Linear_Gradient,
2196 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197}
2198
reed@google.com61eb0402011-04-15 12:11:12 +00002199SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2200 const SkColor colors[],
2201 const SkScalar pos[], int colorCount,
2202 SkShader::TileMode mode,
2203 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002204 if (radius <= 0 || NULL == colors || colorCount < 1) {
2205 return NULL;
2206 }
2207 EXPAND_1_COLOR(colorCount);
2208
reed@android.comab840b82009-07-01 17:00:03 +00002209 return SkNEW_ARGS(Radial_Gradient,
2210 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002211}
2212
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002213SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2214 SkScalar startRadius,
2215 const SkPoint& end,
2216 SkScalar endRadius,
2217 const SkColor colors[],
2218 const SkScalar pos[],
2219 int colorCount,
2220 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002221 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002222 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2223 return NULL;
2224 }
2225 EXPAND_1_COLOR(colorCount);
2226
2227 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002228 (start, startRadius, end, endRadius, colors, pos,
2229 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002230}
2231
reed@android.com8a1c16f2008-12-17 15:59:43 +00002232SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2233 const SkColor colors[],
2234 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002235 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002236 if (NULL == colors || count < 1) {
2237 return NULL;
2238 }
2239 EXPAND_1_COLOR(count);
2240
2241 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2242}
2243
2244static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2245 Linear_Gradient::CreateProc);
2246
2247static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2248 Radial_Gradient::CreateProc);
2249
2250static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2251 Sweep_Gradient::CreateProc);
tomhudson@google.com938d6042011-04-27 13:57:03 +00002252
2253static SkFlattenable::Registrar
2254 gTwoPointRadialGradientReg("Two_Point_Radial_Gradient",
2255 Two_Point_Radial_Gradient::CreateProc);