blob: 0fcb514585440d47df6b7be975467d327e523bcd [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"
tomhudson@google.come8c984d2012-01-09 13:45:36 +000011#include "SkClampRange.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000013#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000014#include "SkUnitMapper.h"
15#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000016#include "SkTemplates.h"
17#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000018
reed@google.com0e734bd2011-12-08 17:24:44 +000019#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
20 #define USE_DITHER_32BIT_GRADIENT
21#endif
22
reed@google.com5eb158d2011-04-15 15:50:34 +000023static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
24 int count) {
25 if (count > 0) {
26 if (v0 == v1) {
27 sk_memset32(dst, v0, count);
28 } else {
29 int pairs = count >> 1;
30 for (int i = 0; i < pairs; i++) {
31 *dst++ = v0;
32 *dst++ = v1;
33 }
34 if (count & 1) {
35 *dst = v0;
36 }
37 }
38 }
39}
40
reed@google.com61eb0402011-04-15 12:11:12 +000041///////////////////////////////////////////////////////////////////////////////
tomhudson@google.com9ce767c2011-04-25 20:49:39 +000042// Can't use a two-argument function with side effects like this in a
43// constructor's initializer's argument list because the order of
44// evaluations in that context is undefined (and backwards on linux/gcc).
45static SkPoint unflatten_point(SkReader32& buffer) {
46 SkPoint retval;
47 retval.fX = buffer.readScalar();
48 retval.fY = buffer.readScalar();
49 return retval;
50}
51
reed@google.comc98a0aa2012-02-02 19:33:08 +000052// Clamp
reed@android.com8a1c16f2008-12-17 15:59:43 +000053
reed@android.com41bccf52009-04-03 13:33:51 +000054static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000055 return SkClampMax(x, 0xFFFF);
56}
57
reed@google.comc98a0aa2012-02-02 19:33:08 +000058// Repeat
59
reed@android.com41bccf52009-04-03 13:33:51 +000060static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 return x & 0xFFFF;
62}
63
reed@google.comc98a0aa2012-02-02 19:33:08 +000064static inline int repeat_bits(int x, const int bits) {
65 return x & ((1 << bits) - 1);
66}
67
68static inline int repeat_8bits(int x) {
69 return x & 0xFF;
70}
71
72// Mirror
73
epoger@google.com5468c902012-02-02 20:41:45 +000074// Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly.
75// See http://code.google.com/p/skia/issues/detail?id=472
76#if defined(_MSC_VER) && (_MSC_VER >= 1600)
77#pragma optimize("", off)
78#endif
79
reed@android.com41bccf52009-04-03 13:33:51 +000080static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000081 int s = x << 15 >> 31;
82 return (x ^ s) & 0xFFFF;
83}
84
reed@android.com200645d2009-12-14 16:41:57 +000085static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000086#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000087 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000089 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000090#else
reed@android.com200645d2009-12-14 16:41:57 +000091 int s = x << (31 - bits) >> 31;
92 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000093#endif
94}
95
reed@android.com41bccf52009-04-03 13:33:51 +000096static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000097#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000098 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +0000100 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 return x & 255;
102#else
103 int s = x << 23 >> 31;
104 return (x ^ s) & 0xFF;
105#endif
106}
107
epoger@google.com5468c902012-02-02 20:41:45 +0000108#if defined(_MSC_VER) && (_MSC_VER >= 1600)
109#pragma optimize("", on)
110#endif
111
reed@google.com61eb0402011-04-15 12:11:12 +0000112///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113
reed@google.comc98a0aa2012-02-02 19:33:08 +0000114typedef SkFixed (*TileProc)(SkFixed);
115
116static const TileProc gTileProcs[] = {
117 clamp_tileproc,
118 repeat_tileproc,
119 mirror_tileproc
120};
121
122///////////////////////////////////////////////////////////////////////////////
123///////////////////////////////////////////////////////////////////////////////
124
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125class Gradient_Shader : public SkShader {
126public:
127 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000128 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 virtual ~Gradient_Shader();
130
131 // overrides
reed@google.com7716afb2011-12-07 15:17:50 +0000132 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
133 virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000134 virtual bool isOpaque() const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000135
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000136 enum {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000137 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
138 /// it, use a larger cache.
139 kCache16Bits = 8,
140 kGradient16Length = (1 << kCache16Bits),
141 /// Each cache gets 1 extra entry at the end so we don't have to
142 /// test for end-of-cache in lerps. This is also the value used
143 /// to stride *writes* into the dither cache; it must not be zero.
144 /// Total space for a cache is 2x kCache16Count entries: one
145 /// regular cache, one for dithering.
146 kCache16Count = kGradient16Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000147 kCache16Shift = 16 - kCache16Bits,
148 kSqrt16Shift = 8 - kCache16Bits,
149
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000150 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
151 /// it, use a larger cache.
152 kCache32Bits = 8,
153 kGradient32Length = (1 << kCache32Bits),
154 /// Each cache gets 1 extra entry at the end so we don't have to
155 /// test for end-of-cache in lerps. This is also the value used
156 /// to stride *writes* into the dither cache; it must not be zero.
157 /// Total space for a cache is 2x kCache32Count entries: one
158 /// regular cache, one for dithering.
159 kCache32Count = kGradient32Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000160 kCache32Shift = 16 - kCache32Bits,
161 kSqrt32Shift = 8 - kCache32Bits,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000162
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000163 /// This value is used to *read* the dither cache; it may be 0
164 /// if dithering is disabled.
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000165#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000166 kDitherStride32 = kCache32Count,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000167#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000168 kDitherStride32 = 0,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000169#endif
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000170 kDitherStride16 = kCache16Count,
171 kLerpRemainderMask32 = (1 << (16 - kCache32Bits)) - 1
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000172 };
173
174
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175protected:
176 Gradient_Shader(SkFlattenableReadBuffer& );
177 SkUnitMapper* fMapper;
178 SkMatrix fPtsToUnit; // set by subclass
179 SkMatrix fDstToIndex;
180 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000181 TileMode fTileMode;
182 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000183 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184 uint8_t fDstToIndexClass;
185 uint8_t fFlags;
186
187 struct Rec {
188 SkFixed fPos; // 0...1
189 uint32_t fScale; // (1 << 24) / range
190 };
191 Rec* fRecs;
192
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000194 const uint16_t* getCache16() const;
195 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196
reed@google.com7c2f27d2011-03-07 19:29:00 +0000197 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000198 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000199
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200private:
201 enum {
202 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
203
reed@android.com1c12abe2009-07-02 15:01:02 +0000204 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000205 };
206 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000207 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
208 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209
reed@google.com7c2f27d2011-03-07 19:29:00 +0000210 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
211 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212
reed@google.com7c2f27d2011-03-07 19:29:00 +0000213 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
214 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000215 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 +0000216
reed@android.com512a8762009-12-14 15:25:36 +0000217 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000218 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
219 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000220 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000221 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000222
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223 typedef SkShader INHERITED;
224};
225
reed@android.com41bccf52009-04-03 13:33:51 +0000226static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000227 SkASSERT(x >= 0 && x <= SK_Scalar1);
228
229#ifdef SK_SCALAR_IS_FLOAT
230 return (unsigned)(x * 0xFFFF);
231#else
232 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
233#endif
234}
235
reed@android.com41bccf52009-04-03 13:33:51 +0000236Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
237 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000238 SkASSERT(colorCount > 1);
239
240 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
241
242 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000243 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
246 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
247 fTileMode = mode;
248 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000249
reed@android.com41bccf52009-04-03 13:33:51 +0000250 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000251 fCache32 = NULL;
252 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000253
reed@android.com41bccf52009-04-03 13:33:51 +0000254 /* Note: we let the caller skip the first and/or last position.
255 i.e. pos[0] = 0.3, pos[1] = 0.7
256 In these cases, we insert dummy entries to ensure that the final data
257 will be bracketed by [0, 1].
258 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
259
260 Thus colorCount (the caller's value, and fColorCount (our value) may
261 differ by up to 2. In the above example:
262 colorCount = 2
263 fColorCount = 4
264 */
265 fColorCount = colorCount;
266 // check if we need to add in dummy start and/or end position/colors
267 bool dummyFirst = false;
268 bool dummyLast = false;
269 if (pos) {
270 dummyFirst = pos[0] != 0;
271 dummyLast = pos[colorCount - 1] != SK_Scalar1;
272 fColorCount += dummyFirst + dummyLast;
273 }
274
275 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000276 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000277 fOrigColors = reinterpret_cast<SkColor*>(
278 sk_malloc_throw(size * fColorCount));
279 }
280 else {
281 fOrigColors = fStorage;
282 }
283
284 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285 {
reed@android.com41bccf52009-04-03 13:33:51 +0000286 SkColor* origColors = fOrigColors;
287 if (dummyFirst) {
288 *origColors++ = colors[0];
289 }
290 memcpy(origColors, colors, colorCount * sizeof(SkColor));
291 if (dummyLast) {
292 origColors += colorCount;
293 *origColors = colors[colorCount - 1];
294 }
295 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296
reed@android.com1c12abe2009-07-02 15:01:02 +0000297 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000298 if (fColorCount > 2) {
299 Rec* recs = fRecs;
300 recs->fPos = 0;
301 // recs->fScale = 0; // unused;
302 recs += 1;
303 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 /* We need to convert the user's array of relative positions into
305 fixed-point positions and scale factors. We need these results
306 to be strictly monotonic (no two values equal or out of order).
307 Hence this complex loop that just jams a zero for the scale
308 value if it sees a segment out of order, and it assures that
309 we start at 0 and end at 1.0
310 */
311 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000312 int startIndex = dummyFirst ? 0 : 1;
313 int count = colorCount + dummyLast;
314 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315 // force the last value to be 1.0
316 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000317 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000319 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 }
reed@android.com41bccf52009-04-03 13:33:51 +0000322 // pin curr withing range
323 if (curr < 0) {
324 curr = 0;
325 } else if (curr > SK_Fixed1) {
326 curr = SK_Fixed1;
327 }
328 recs->fPos = curr;
329 if (curr > prev) {
330 recs->fScale = (1 << 24) / (curr - prev);
331 } else {
332 recs->fScale = 0; // ignore this segment
333 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334 // get ready for the next value
335 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000336 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 }
reed@android.com41bccf52009-04-03 13:33:51 +0000338 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 SkFixed dp = SK_Fixed1 / (colorCount - 1);
340 SkFixed p = dp;
341 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000342 for (int i = 1; i < colorCount; i++) {
343 recs->fPos = p;
344 recs->fScale = scale;
345 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346 p += dp;
347 }
348 }
349 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000350 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351}
352
353Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000354 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 fCacheAlpha = 256;
356
357 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
358
359 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000360 fCache32 = NULL;
361 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362
reed@android.com41bccf52009-04-03 13:33:51 +0000363 int colorCount = fColorCount = buffer.readU32();
364 if (colorCount > kColorStorageCount) {
365 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
366 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
367 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000369 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000370 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371
372 fTileMode = (TileMode)buffer.readU8();
373 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000374 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000375 if (colorCount > 2) {
376 Rec* recs = fRecs;
377 recs[0].fPos = 0;
378 for (int i = 1; i < colorCount; i++) {
379 recs[i].fPos = buffer.readS32();
380 recs[i].fScale = buffer.readU32();
381 }
382 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000383 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000384 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000385}
386
reed@android.com41bccf52009-04-03 13:33:51 +0000387Gradient_Shader::~Gradient_Shader() {
388 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000390 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000391 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000392 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000393 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000394 }
reed@google.com82065d62011-02-07 15:30:46 +0000395 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000396}
397
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000398void Gradient_Shader::initCommon() {
399 fFlags = 0;
400 unsigned colorAlpha = 0xFF;
401 for (int i = 0; i < fColorCount; i++) {
402 colorAlpha &= SkColorGetA(fOrigColors[i]);
403 }
404 fColorsAreOpaque = colorAlpha == 0xFF;
405}
406
reed@android.com41bccf52009-04-03 13:33:51 +0000407void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000408 this->INHERITED::flatten(buffer);
409 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000410 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
412 buffer.write8(fTileMode);
413 if (fColorCount > 2) {
414 Rec* recs = fRecs;
415 for (int i = 1; i < fColorCount; i++) {
416 buffer.write32(recs[i].fPos);
417 buffer.write32(recs[i].fScale);
418 }
419 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000420 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000421}
422
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000423bool Gradient_Shader::isOpaque() const {
424 return fColorsAreOpaque;
425}
426
reed@android.com8a1c16f2008-12-17 15:59:43 +0000427bool Gradient_Shader::setContext(const SkBitmap& device,
428 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000429 const SkMatrix& matrix) {
430 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000432 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433
434 const SkMatrix& inverse = this->getTotalInverse();
435
436 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
437 return false;
438 }
439
440 fDstToIndexProc = fDstToIndex.getMapXYProc();
441 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
442
443 // now convert our colors in to PMColors
444 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000445
446 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000447 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000448 fFlags |= kOpaqueAlpha_Flag;
449 }
450 // we can do span16 as long as our individual colors are opaque,
451 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000452 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000453 fFlags |= kHasSpan16_Flag;
454 }
455
reed@google.com95eed982011-07-05 17:01:56 +0000456 this->setCacheAlpha(paintAlpha);
457 return true;
458}
459
460void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000461 // if the new alpha differs from the previous time we were called, inval our cache
462 // this will trigger the cache to be rebuilt.
463 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000464 if (fCacheAlpha != alpha) {
465 fCache16 = NULL; // inval the cache
466 fCache32 = NULL; // inval the cache
467 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000468 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000469 if (fCache32PixelRef) {
470 fCache32PixelRef->notifyPixelsChanged();
471 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000472 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000473}
474
reed@android.com41bccf52009-04-03 13:33:51 +0000475static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000476 SkASSERT(a == SkToU8(a));
477 SkASSERT(b == SkToU8(b));
478 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000479 return a + ((b - a) * scale >> 8);
480}
481
reed@android.com41bccf52009-04-03 13:33:51 +0000482static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
483 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000484#if 0
485 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
486 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
487 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
488 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
489
490 return SkPackARGB32(a, r, g, b);
491#else
492 int otherBlend = 256 - blend;
493
494#if 0
495 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
496 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
497 SkASSERT((t0 & t1) == 0);
498 return t0 | t1;
499#else
500 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
501 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
502#endif
503
504#endif
505}
506
507#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
508
reed@android.com41bccf52009-04-03 13:33:51 +0000509/** We take the original colors, not our premultiplied PMColors, since we can
510 build a 16bit table as long as the original colors are opaque, even if the
511 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000512*/
reed@android.com512a8762009-12-14 15:25:36 +0000513void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
514 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000515 SkASSERT(count > 1);
516 SkASSERT(SkColorGetA(c0) == 0xFF);
517 SkASSERT(SkColorGetA(c1) == 0xFF);
518
519 SkFixed r = SkColorGetR(c0);
520 SkFixed g = SkColorGetG(c0);
521 SkFixed b = SkColorGetB(c0);
522
523 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
524 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
525 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
526
527 r = SkIntToFixed(r) + 0x8000;
528 g = SkIntToFixed(g) + 0x8000;
529 b = SkIntToFixed(b) + 0x8000;
530
531 do {
532 unsigned rr = r >> 16;
533 unsigned gg = g >> 16;
534 unsigned bb = b >> 16;
535 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000536 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000537 cache += 1;
538 r += dr;
539 g += dg;
540 b += db;
541 } while (--count != 0);
542}
543
reed@google.com55b8e8c2011-01-13 16:22:35 +0000544/*
545 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
546 * semantics of how we 2x2 dither 32->16
547 */
548static inline U8CPU dither_fixed_to_8(SkFixed n) {
549 n >>= 8;
550 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
551}
552
553/*
554 * For dithering with premultiply, we want to ceiling the alpha component,
555 * to ensure that it is always >= any color component.
556 */
557static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
558 n >>= 8;
559 return ((n << 1) - (n | (n >> 8))) >> 8;
560}
561
562void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
563 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000564 SkASSERT(count > 1);
565
reed@android.com1c12abe2009-07-02 15:01:02 +0000566 // need to apply paintAlpha to our two endpoints
567 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
568 SkFixed da;
569 {
570 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
571 da = SkIntToFixed(tmp - a) / (count - 1);
572 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573
reed@android.com1c12abe2009-07-02 15:01:02 +0000574 SkFixed r = SkColorGetR(c0);
575 SkFixed g = SkColorGetG(c0);
576 SkFixed b = SkColorGetB(c0);
577 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
578 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
579 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000580
581 a = SkIntToFixed(a) + 0x8000;
582 r = SkIntToFixed(r) + 0x8000;
583 g = SkIntToFixed(g) + 0x8000;
584 b = SkIntToFixed(b) + 0x8000;
585
586 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000587 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000588 cache[kCache32Count] =
589 SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
590 dither_fixed_to_8(r),
591 dither_fixed_to_8(g),
592 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000593 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000594 a += da;
595 r += dr;
596 g += dg;
597 b += db;
598 } while (--count != 0);
599}
600
reed@android.com41bccf52009-04-03 13:33:51 +0000601static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000602 SkASSERT((unsigned)x <= SK_Fixed1);
603 return x - (x >> 16);
604}
605
reed@android.com200645d2009-12-14 16:41:57 +0000606static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000607 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000608 if (6 == bits) {
609 return (x << 10) | (x << 4) | (x >> 2);
610 }
611 if (8 == bits) {
612 return (x << 8) | x;
613 }
614 sk_throw();
615 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616}
617
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000618/** We duplicate the last value in each half of the cache so that
619 interpolation doesn't have to special-case being at the last point.
620*/
621static void complete_16bit_cache(uint16_t* cache, int stride) {
622 cache[stride - 1] = cache[stride - 2];
623 cache[2 * stride - 1] = cache[2 * stride - 2];
624}
625
reed@google.com7c2f27d2011-03-07 19:29:00 +0000626const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000627 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000628 // double the count for dither entries
629 const int entryCount = kCache16Count * 2;
630 const size_t allocSize = sizeof(uint16_t) * entryCount;
631
reed@android.com3c9b2a42009-08-27 19:28:37 +0000632 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000633 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000634 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000635 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000636 if (fColorCount == 2) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000637 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
638 kGradient16Length);
reed@android.com41bccf52009-04-03 13:33:51 +0000639 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000640 Rec* rec = fRecs;
641 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000642 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000643 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 SkASSERT(nextIndex < kCache16Count);
645
646 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000647 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 prevIndex = nextIndex;
649 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000650 // one extra space left over at the end for complete_16bit_cache()
651 SkASSERT(prevIndex == kGradient16Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 }
653
reed@android.com41bccf52009-04-03 13:33:51 +0000654 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000655 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 uint16_t* linear = fCache16; // just computed linear data
657 uint16_t* mapped = fCache16Storage; // storage for mapped data
658 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000659 for (int i = 0; i < kGradient16Length; i++) {
reed@android.com200645d2009-12-14 16:41:57 +0000660 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000662 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663 }
664 sk_free(fCache16);
665 fCache16 = fCache16Storage;
666 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000667 complete_16bit_cache(fCache16, kCache16Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668 }
669 return fCache16;
670}
671
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000672/** We duplicate the last value in each half of the cache so that
673 interpolation doesn't have to special-case being at the last point.
674*/
675static void complete_32bit_cache(SkPMColor* cache, int stride) {
676 cache[stride - 1] = cache[stride - 2];
677 cache[2 * stride - 1] = cache[2 * stride - 2];
678}
679
reed@google.com7c2f27d2011-03-07 19:29:00 +0000680const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000681 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000682 // double the count for dither entries
683 const int entryCount = kCache32Count * 2;
684 const size_t allocSize = sizeof(SkPMColor) * entryCount;
685
reed@google.comdc731fd2010-12-23 15:19:47 +0000686 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000687 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
688 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000689 }
690 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000691 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000692 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000693 kGradient32Length, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000694 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 Rec* rec = fRecs;
696 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000697 for (int i = 1; i < fColorCount; i++) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000698 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000699 SkASSERT(nextIndex < kGradient32Length);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700
701 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000702 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
703 fOrigColors[i],
704 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705 prevIndex = nextIndex;
706 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000707 SkASSERT(prevIndex == kGradient32Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708 }
709
reed@android.com41bccf52009-04-03 13:33:51 +0000710 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000711 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000712 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000714 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000716 for (int i = 0; i < kGradient32Length; i++) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000717 int index = map->mapUnit16((i << 8) | i) >> 8;
718 mapped[i] = linear[index];
719 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000720 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000721 fCache32PixelRef->unref();
722 fCache32PixelRef = newPR;
723 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000725 complete_32bit_cache(fCache32, kCache32Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726 }
727 return fCache32;
728}
729
reed@google.comdc731fd2010-12-23 15:19:47 +0000730/*
731 * Because our caller might rebuild the same (logically the same) gradient
732 * over and over, we'd like to return exactly the same "bitmap" if possible,
733 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
734 * To do that, we maintain a private cache of built-bitmaps, based on our
735 * colors and positions. Note: we don't try to flatten the fMapper, so if one
736 * is present, we skip the cache for now.
737 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000738void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000739 // our caller assumes no external alpha, so we ensure that our cache is
740 // built with 0xFF
741 this->setCacheAlpha(0xFF);
742
reed@google.comdc731fd2010-12-23 15:19:47 +0000743 // don't have a way to put the mapper into our cache-key yet
744 if (fMapper) {
745 // force our cahce32pixelref to be built
746 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000747 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000748 bitmap->setPixelRef(fCache32PixelRef);
749 return;
750 }
751
752 // build our key: [numColors + colors[] + {positions[]} ]
753 int count = 1 + fColorCount;
754 if (fColorCount > 2) {
755 count += fColorCount - 1; // fRecs[].fPos
756 }
757
758 SkAutoSTMalloc<16, int32_t> storage(count);
759 int32_t* buffer = storage.get();
760
761 *buffer++ = fColorCount;
762 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
763 buffer += fColorCount;
764 if (fColorCount > 2) {
765 for (int i = 1; i < fColorCount; i++) {
766 *buffer++ = fRecs[i].fPos;
767 }
768 }
769 SkASSERT(buffer - storage.get() == count);
770
771 ///////////////////////////////////
772
digit@google.com1771cbf2012-01-26 21:26:40 +0000773 SK_DECLARE_STATIC_MUTEX(gMutex);
reed@google.comdc731fd2010-12-23 15:19:47 +0000774 static SkBitmapCache* gCache;
775 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
776 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
777 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000778
reed@google.comdc731fd2010-12-23 15:19:47 +0000779 if (NULL == gCache) {
780 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
781 }
782 size_t size = count * sizeof(int32_t);
783
784 if (!gCache->find(storage.get(), size, bitmap)) {
785 // force our cahce32pixelref to be built
786 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000787 // Only expose the linear section of the cache; don't let the caller
788 // know about the padding at the end to make interpolation faster.
789 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000790 bitmap->setPixelRef(fCache32PixelRef);
791
792 gCache->add(storage.get(), size, *bitmap);
793 }
794}
795
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000796void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
797 if (info) {
798 if (info->fColorCount >= fColorCount) {
799 if (info->fColors) {
800 memcpy(info->fColors, fOrigColors,
801 fColorCount * sizeof(SkColor));
802 }
803 if (info->fColorOffsets) {
804 if (fColorCount == 2) {
805 info->fColorOffsets[0] = 0;
806 info->fColorOffsets[1] = SK_Scalar1;
807 } else if (fColorCount > 2) {
808 for (int i = 0; i < fColorCount; i++)
809 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
810 }
811 }
812 }
813 info->fColorCount = fColorCount;
814 info->fTileMode = fTileMode;
815 }
816}
817
reed@google.com61eb0402011-04-15 12:11:12 +0000818///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819
reed@android.com41bccf52009-04-03 13:33:51 +0000820static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000821 SkVector vec = pts[1] - pts[0];
822 SkScalar mag = vec.length();
823 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
824
825 vec.scale(inv);
826 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
827 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
828 matrix->postScale(inv, inv);
829}
830
831///////////////////////////////////////////////////////////////////////////////
832
833class Linear_Gradient : public Gradient_Shader {
834public:
835 Linear_Gradient(const SkPoint pts[2],
836 const SkColor colors[], const SkScalar pos[], int colorCount,
837 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000838 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
839 fStart(pts[0]),
840 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 {
842 pts_to_unit_matrix(pts, &fPtsToUnit);
843 }
reed@android.com9b46e772009-06-05 12:24:41 +0000844
reed@google.com7716afb2011-12-07 15:17:50 +0000845 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
846 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
847 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
848 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
849 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
850 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851
reed@google.com7716afb2011-12-07 15:17:50 +0000852 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000853 this->INHERITED::flatten(buffer);
854 buffer.writeScalar(fStart.fX);
855 buffer.writeScalar(fStart.fY);
856 buffer.writeScalar(fEnd.fX);
857 buffer.writeScalar(fEnd.fY);
858 }
859
djsollen@google.comba28d032012-03-26 17:57:35 +0000860 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Linear_Gradient)
861
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000863 Linear_Gradient(SkFlattenableReadBuffer& buffer)
864 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000865 fStart(unflatten_point(buffer)),
866 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000867 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868
869private:
870 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000871 const SkPoint fStart;
872 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873};
874
reed@android.com5119bdb2009-06-12 21:27:03 +0000875bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
876 const SkMatrix& matrix) {
877 if (!this->INHERITED::setContext(device, paint, matrix)) {
878 return false;
879 }
880
881 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
882 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000883 fFlags |= SkShader::kConstInY32_Flag;
884 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
885 // only claim this if we do have a 16bit mode (i.e. none of our
886 // colors have alpha), and if we are not dithering (which obviously
887 // is not const in Y).
888 fFlags |= SkShader::kConstInY16_Flag;
889 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000890 }
891 return true;
892}
893
reed@google.com5eb158d2011-04-15 15:50:34 +0000894#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000895 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000896 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000897 SkASSERT(fi <= 0xFF); \
898 fx += dx; \
899 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000900 toggle ^= Gradient_Shader::kDitherStride32; \
reed@google.com13659f12011-04-18 19:59:38 +0000901 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000902
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000903namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000904
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000905typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000906 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000907 int toggle, int count);
908
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000909// This function is deprecated, and will be replaced by
910// shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it.
911void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
912 SkPMColor* SK_RESTRICT dstC,
913 const SkPMColor* SK_RESTRICT cache,
914 int toggle, int count) {
915 // We're a vertical gradient, so no change in a span.
916 // If colors change sharply across the gradient, dithering is
917 // insufficient (it subsamples the color space) and we need to lerp.
918 unsigned fullIndex = proc(fx);
919 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
920 sk_memset32_dither(dstC,
921 cache[toggle + fi],
922 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
923 count);
924}
925
926// Linear interpolation (lerp) is unnecessary if there are no sharp
927// discontinuities in the gradient - which must be true if there are
928// only 2 colors - but it's cheap.
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000929void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
930 SkPMColor* SK_RESTRICT dstC,
931 const SkPMColor* SK_RESTRICT cache,
932 int toggle, int count) {
933 // We're a vertical gradient, so no change in a span.
934 // If colors change sharply across the gradient, dithering is
935 // insufficient (it subsamples the color space) and we need to lerp.
936 unsigned fullIndex = proc(fx);
937 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
938 unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32;
939 SkPMColor lerp =
940 SkFastFourByteInterp(
941 cache[toggle + fi + 1],
942 cache[toggle + fi], remainder);
943 SkPMColor dlerp =
944 SkFastFourByteInterp(
945 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1],
946 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], remainder);
947 sk_memset32_dither(dstC, lerp, dlerp, count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000948}
949
950void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
951 SkPMColor* SK_RESTRICT dstC,
952 const SkPMColor* SK_RESTRICT cache,
953 int toggle, int count) {
954 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000955 range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000956
957 if ((count = range.fCount0) > 0) {
958 sk_memset32_dither(dstC,
959 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000960 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000961 count);
962 dstC += count;
963 }
964 if ((count = range.fCount1) > 0) {
965 int unroll = count >> 3;
966 fx = range.fFx1;
967 for (int i = 0; i < unroll; i++) {
968 NO_CHECK_ITER; NO_CHECK_ITER;
969 NO_CHECK_ITER; NO_CHECK_ITER;
970 NO_CHECK_ITER; NO_CHECK_ITER;
971 NO_CHECK_ITER; NO_CHECK_ITER;
972 }
973 if ((count &= 7) > 0) {
974 do {
975 NO_CHECK_ITER;
976 } while (--count != 0);
977 }
978 }
979 if ((count = range.fCount2) > 0) {
980 sk_memset32_dither(dstC,
981 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000982 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000983 count);
984 }
985}
986
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000987void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
988 SkPMColor* SK_RESTRICT dstC,
989 const SkPMColor* SK_RESTRICT cache,
990 int toggle, int count) {
991 do {
992 unsigned fi = mirror_8bits(fx >> 8);
993 SkASSERT(fi <= 0xFF);
994 fx += dx;
995 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000996 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000997 } while (--count != 0);
998}
999
1000void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1001 SkPMColor* SK_RESTRICT dstC,
1002 const SkPMColor* SK_RESTRICT cache,
1003 int toggle, int count) {
1004 do {
1005 unsigned fi = repeat_8bits(fx >> 8);
1006 SkASSERT(fi <= 0xFF);
1007 fx += dx;
1008 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001009 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001010 } while (--count != 0);
1011}
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001012
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001013}
1014
1015void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1016 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017 SkASSERT(count > 0);
1018
1019 SkPoint srcPt;
1020 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1021 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001022 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +00001023#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001024 int toggle = ((x ^ y) & 1) * kDitherStride32;
reed@google.com0e734bd2011-12-08 17:24:44 +00001025#else
1026 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +00001027#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001028
reed@android.comc552a432009-06-12 20:02:50 +00001029 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001030 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1031 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001032 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1033
reed@android.comc552a432009-06-12 20:02:50 +00001034 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001035 SkFixed dxStorage[1];
1036 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1037 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +00001038 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001039 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1040 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1041 }
1042
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001043 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +00001044 if (SkFixedNearlyZero(dx)) {
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001045#ifdef SK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS
1046 if (fColorCount > 2) {
1047 shadeProc = shadeSpan_linear_vertical_lerp;
1048 } else {
1049 shadeProc = shadeSpan_linear_vertical;
1050 }
1051#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001052 shadeProc = shadeSpan_linear_vertical_lerp;
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001053#endif
reed@android.comc552a432009-06-12 20:02:50 +00001054 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001055 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +00001056 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001057 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +00001058 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001059 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001061 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +00001062 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 SkScalar dstX = SkIntToScalar(x);
1064 SkScalar dstY = SkIntToScalar(y);
1065 do {
1066 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1067 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1068 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001069 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001070 toggle ^= Gradient_Shader::kDitherStride32;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001071 dstX += SK_Scalar1;
1072 } while (--count != 0);
1073 }
1074}
1075
reed@google.com55b8e8c2011-01-13 16:22:35 +00001076SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +00001077 SkMatrix* matrix,
1078 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +00001079 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001080 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001081 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 }
1083 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001084 matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085 matrix->preConcat(fPtsToUnit);
1086 }
1087 if (xy) {
1088 xy[0] = fTileMode;
1089 xy[1] = kClamp_TileMode;
1090 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001091 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001092}
1093
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001094SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1095 if (info) {
1096 commonAsAGradient(info);
1097 info->fPoint[0] = fStart;
1098 info->fPoint[1] = fEnd;
1099 }
1100 return kLinear_GradientType;
1101}
1102
reed@android.com3c9b2a42009-08-27 19:28:37 +00001103static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1104 int count) {
1105 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001106 *dst++ = value;
1107 count -= 1;
1108 SkTSwap(value, other);
1109 }
1110
1111 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001112
reed@android.com3c9b2a42009-08-27 19:28:37 +00001113 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001114 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001115 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001116}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001117
reed@google.com5eb158d2011-04-15 15:50:34 +00001118#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001119 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001120 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001121 SkASSERT(fi < Gradient_Shader::kCache16Count); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001122 fx += dx; \
1123 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001124 toggle ^= Gradient_Shader::kDitherStride16; \
reed@google.com13659f12011-04-18 19:59:38 +00001125 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001126
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001127namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001128
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001129typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001130 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001131 int toggle, int count);
1132
1133void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1134 uint16_t* SK_RESTRICT dstC,
1135 const uint16_t* SK_RESTRICT cache,
1136 int toggle, int count) {
1137 // we're a vertical gradient, so no change in a span
1138 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001139 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001140 dither_memset16(dstC, cache[toggle + fi],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001141 cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001142
1143}
1144
1145void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1146 uint16_t* SK_RESTRICT dstC,
1147 const uint16_t* SK_RESTRICT cache,
1148 int toggle, int count) {
1149 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001150 range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001151
1152 if ((count = range.fCount0) > 0) {
1153 dither_memset16(dstC,
1154 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001155 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001156 count);
1157 dstC += count;
1158 }
1159 if ((count = range.fCount1) > 0) {
1160 int unroll = count >> 3;
1161 fx = range.fFx1;
1162 for (int i = 0; i < unroll; i++) {
1163 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1164 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1165 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1166 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1167 }
1168 if ((count &= 7) > 0) {
1169 do {
1170 NO_CHECK_ITER_16;
1171 } while (--count != 0);
1172 }
1173 }
1174 if ((count = range.fCount2) > 0) {
1175 dither_memset16(dstC,
1176 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001177 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001178 count);
1179 }
1180}
1181
1182void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1183 uint16_t* SK_RESTRICT dstC,
1184 const uint16_t* SK_RESTRICT cache,
1185 int toggle, int count) {
1186 do {
1187 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1188 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001189 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001190 fx += dx;
1191 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001192 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001193 } while (--count != 0);
1194}
1195
1196void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1197 uint16_t* SK_RESTRICT dstC,
1198 const uint16_t* SK_RESTRICT cache,
1199 int toggle, int count) {
1200 SkASSERT(proc == repeat_tileproc);
1201 do {
1202 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1203 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001204 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001205 fx += dx;
1206 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001207 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001208 } while (--count != 0);
1209}
1210}
1211
1212void Linear_Gradient::shadeSpan16(int x, int y,
1213 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 SkASSERT(count > 0);
1215
1216 SkPoint srcPt;
1217 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1218 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001219 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001220 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001222 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001223 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1224 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1226
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001227 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228 SkFixed dxStorage[1];
1229 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1230 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001231 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1233 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1234 }
1235
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001236 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001237 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001238 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001239 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001240 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001241 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001242 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001243 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001246 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001247 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 SkScalar dstX = SkIntToScalar(x);
1249 SkScalar dstY = SkIntToScalar(y);
1250 do {
1251 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1252 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1253 SkASSERT(fi <= 0xFFFF);
1254
reed@android.com512a8762009-12-14 15:25:36 +00001255 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001257 toggle ^= Gradient_Shader::kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258
1259 dstX += SK_Scalar1;
1260 } while (--count != 0);
1261 }
1262}
1263
1264///////////////////////////////////////////////////////////////////////////////
1265
1266#define kSQRT_TABLE_BITS 11
1267#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1268
1269#include "SkRadialGradient_Table.h"
1270
1271#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1272
1273#include <stdio.h>
1274
reed@google.com61eb0402011-04-15 12:11:12 +00001275void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1277
1278 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1279 SkASSERT(file);
1280 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1281
reed@google.com61eb0402011-04-15 12:11:12 +00001282 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1283 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001285 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286
1287 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1288
1289 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001290 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001292 }
1293 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001294 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001295 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 }
1297 ::fprintf(file, "};\n");
1298 ::fclose(file);
1299}
1300
1301#endif
1302
1303
reed@google.com61eb0402011-04-15 12:11:12 +00001304static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1305 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 SkScalar inv = SkScalarInvert(radius);
1307
1308 matrix->setTranslate(-center.fX, -center.fY);
1309 matrix->postScale(inv, inv);
1310}
1311
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001312
1313namespace {
1314
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001315typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
1316 SkScalar sfy, SkScalar sdy,
1317 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001318 int toggle, int count);
1319
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001320void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
1321 SkScalar sfy, SkScalar sdy,
1322 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001323 int toggle, int count) {
1324 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1325
1326 /* knock these down so we can pin against +- 0x7FFF, which is an
1327 immediate load, rather than 0xFFFF which is slower. This is a
1328 compromise, since it reduces our precision, but that appears
1329 to be visually OK. If we decide this is OK for all of our cases,
1330 we could (it seems) put this scale-down into fDstToIndex,
1331 to avoid having to do these extra shifts each time.
1332 */
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001333 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1334 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1335 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1336 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001337 // might perform this check for the other modes,
1338 // but the win will be a smaller % of the total
1339 if (dy == 0) {
1340 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1341 fy *= fy;
1342 do {
1343 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1344 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1345 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1346 fx += dx;
1347 *dstC++ = cache[toggle +
1348 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001349 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001350 } while (--count != 0);
1351 } else {
1352 do {
1353 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1354 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1355 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1356 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1357 fx += dx;
1358 fy += dy;
1359 *dstC++ = cache[toggle +
1360 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001361 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001362 } while (--count != 0);
1363 }
1364}
1365
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001366void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
1367 SkScalar sfy, SkScalar sdy,
1368 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001369 int toggle, int count) {
1370 do {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001371#ifdef SK_SCALAR_IS_FLOAT
1372 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1373 SkFixed dist = SkFloatToFixed(fdist);
1374#else
1375 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1376 SkFixedSquare(sfy);
1377 if (magnitudeSquared < 0) // Overflow.
1378 magnitudeSquared = SK_FixedMax;
1379 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1380#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001381 unsigned fi = mirror_tileproc(dist);
1382 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001383 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001384 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001385 sfx += sdx;
1386 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001387 } while (--count != 0);
1388}
1389
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001390void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
1391 SkScalar sfy, SkScalar sdy,
1392 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001393 int toggle, int count) {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001394 SkFixed fx = SkScalarToFixed(sfx);
1395 SkFixed dx = SkScalarToFixed(sdx);
1396 SkFixed fy = SkScalarToFixed(sfy);
1397 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001398 do {
1399 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1400 unsigned fi = repeat_tileproc(dist);
1401 SkASSERT(fi <= 0xFFFF);
1402 fx += dx;
1403 fy += dy;
1404 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001405 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001406 } while (--count != 0);
1407}
1408
1409}
1410
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411class Radial_Gradient : public Gradient_Shader {
1412public:
1413 Radial_Gradient(const SkPoint& center, SkScalar radius,
1414 const SkColor colors[], const SkScalar pos[], int colorCount,
1415 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001416 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1417 fCenter(center),
1418 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001419 {
1420 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1421 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1422
1423 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1424 }
reed@google.com61eb0402011-04-15 12:11:12 +00001425
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001426 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
1427 SK_OVERRIDE;
1428 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
1429 int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 SkASSERT(count > 0);
1431
1432 SkPoint srcPt;
1433 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1434 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001435 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001436 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437
reed@android.com3c9b2a42009-08-27 19:28:37 +00001438 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001439 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1440 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001441
1442 SkScalar sdx = fDstToIndex.getScaleX();
1443 SkScalar sdy = fDstToIndex.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001444
reed@android.com3c9b2a42009-08-27 19:28:37 +00001445 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446 SkFixed storage[2];
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001447 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1448 &storage[0], &storage[1]);
1449 sdx = SkFixedToScalar(storage[0]);
1450 sdy = SkFixedToScalar(storage[1]);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001451 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001453 }
1454
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001455 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001456 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001457 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001458 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001459 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001460 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 }
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001463 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
1464 cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001465 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466 SkScalar dstX = SkIntToScalar(x);
1467 SkScalar dstY = SkIntToScalar(y);
1468 do {
1469 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1470 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1471 SkASSERT(fi <= 0xFFFF);
1472
1473 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001475 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476
1477 dstX += SK_Scalar1;
1478 } while (--count != 0);
1479 }
1480 }
1481
reed@google.com55b8e8c2011-01-13 16:22:35 +00001482 virtual BitmapType asABitmap(SkBitmap* bitmap,
1483 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001484 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001485 SkScalar* twoPointRadialParams)
1486 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001487 if (bitmap) {
1488 this->commonAsABitmap(bitmap);
1489 }
1490 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001491 matrix->setScale(SkIntToScalar(kGradient32Length),
1492 SkIntToScalar(kGradient32Length));
reed@google.comdc731fd2010-12-23 15:19:47 +00001493 matrix->preConcat(fPtsToUnit);
1494 }
1495 if (xy) {
1496 xy[0] = fTileMode;
1497 xy[1] = kClamp_TileMode;
1498 }
1499 return kRadial_BitmapType;
1500 }
reed@google.com7716afb2011-12-07 15:17:50 +00001501 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001502 if (info) {
1503 commonAsAGradient(info);
1504 info->fPoint[0] = fCenter;
1505 info->fRadius[0] = fRadius;
1506 }
1507 return kRadial_GradientType;
1508 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001509
reed@google.com7716afb2011-12-07 15:17:50 +00001510 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001511 this->INHERITED::flatten(buffer);
1512 buffer.writeScalar(fCenter.fX);
1513 buffer.writeScalar(fCenter.fY);
1514 buffer.writeScalar(fRadius);
1515 }
1516
djsollen@google.comba28d032012-03-26 17:57:35 +00001517 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Radial_Gradient)
1518
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001520 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1521 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001522 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001523 fRadius(buffer.readScalar()) {
1524 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525
1526private:
1527 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001528 const SkPoint fCenter;
1529 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530};
1531
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001532namespace {
1533
1534inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001535 // fast, overly-conservative test: checks unit square instead
1536 // of unit circle
1537 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1538 (fx <= -SK_FixedHalf && dx <= 0);
1539 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1540 (fy <= -SK_FixedHalf && dy <= 0);
1541
1542 return xClamped || yClamped;
1543}
1544
1545// Return true if (fx * fy) is always inside the unit circle
1546// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1547// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001548inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001549 int fy, int dy, int count) {
1550 SkASSERT(count > 0);
1551 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1552 return false;
1553 }
1554 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1555 return false;
1556 }
1557 fx += (count - 1) * dx;
1558 fy += (count - 1) * dy;
1559 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1560 return false;
1561 }
1562 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1563}
1564
1565#define UNPINNED_RADIAL_STEP \
1566 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001567 *dstC++ = cache[toggle + \
1568 (sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift)]; \
1569 toggle ^= Gradient_Shader::kDitherStride32; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001570 fx += dx; \
1571 fy += dy;
1572
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001573typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1574 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001575 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001576 int count, int toggle);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001577
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001578// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001579void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
1580 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001581 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001582 int count, int toggle) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001583 // Floating point seems to be slower than fixed point,
1584 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001585 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001586 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1587 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1588 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1589 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001590 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001591 unsigned fi = Gradient_Shader::kGradient32Length;
1592 sk_memset32_dither(dstC,
1593 cache[toggle + fi],
1594 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
1595 count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001596 } else if ((count > 4) &&
1597 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1598 unsigned fi;
1599 // 4x unroll appears to be no faster than 2x unroll on Linux
1600 while (count > 1) {
1601 UNPINNED_RADIAL_STEP;
1602 UNPINNED_RADIAL_STEP;
1603 count -= 2;
1604 }
1605 if (count) {
1606 UNPINNED_RADIAL_STEP;
1607 }
1608 }
1609 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001610 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1611 if (dy == 0) {
1612 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1613 yy *= yy;
1614 do {
1615 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1616 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1617 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001618 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1619 Gradient_Shader::kSqrt32Shift)];
1620 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001621 fx += dx;
1622 } while (--count != 0);
1623 } else {
1624 do {
1625 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1626 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1627 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1628 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001629 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1630 Gradient_Shader::kSqrt32Shift)];
1631 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001632 fx += dx;
1633 fy += dy;
1634 } while (--count != 0);
1635 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001636 }
1637}
1638
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001639// Unrolling this loop doesn't seem to help (when float); we're stalling to
1640// get the results of the sqrt (?), and don't have enough extra registers to
1641// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001642void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1643 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001644 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001645 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001646 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001647#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001648 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1649 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001650#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001651 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1652 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001653 if (magnitudeSquared < 0) // Overflow.
1654 magnitudeSquared = SK_FixedMax;
1655 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001656#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001657 unsigned fi = mirror_tileproc(dist);
1658 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001659 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1660 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001661 sfx += sdx;
1662 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001663 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001664}
1665
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001666void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1667 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001668 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001669 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001670 SkFixed fx = SkScalarToFixed(sfx);
1671 SkFixed dx = SkScalarToFixed(sdx);
1672 SkFixed fy = SkScalarToFixed(sfy);
1673 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001674 do {
1675 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1676 SkFixedSquare(fy);
1677 if (magnitudeSquared < 0) // Overflow.
1678 magnitudeSquared = SK_FixedMax;
1679 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1680 unsigned fi = repeat_tileproc(dist);
1681 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001682 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1683 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001684 fx += dx;
1685 fy += dy;
1686 } while (--count != 0);
1687}
1688}
1689
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001690void Radial_Gradient::shadeSpan(int x, int y,
1691 SkPMColor* SK_RESTRICT dstC, int count) {
1692 SkASSERT(count > 0);
1693
1694 SkPoint srcPt;
1695 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1696 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001697 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001698#ifdef USE_DITHER_32BIT_GRADIENT
1699 int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32;
1700#else
1701 int toggle = 0;
1702#endif
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001703
1704 if (fDstToIndexClass != kPerspective_MatrixClass) {
1705 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1706 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001707 SkScalar sdx = fDstToIndex.getScaleX();
1708 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001709
1710 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1711 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001712 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1713 &storage[0], &storage[1]);
1714 sdx = SkFixedToScalar(storage[0]);
1715 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001716 } else {
1717 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001718 }
1719
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001720 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001721 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001722 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001723 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001724 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001725 } else {
1726 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001727 }
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001728 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001729 } else { // perspective case
1730 SkScalar dstX = SkIntToScalar(x);
1731 SkScalar dstY = SkIntToScalar(y);
1732 do {
1733 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1734 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1735 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001736 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001737 dstX += SK_Scalar1;
1738 } while (--count != 0);
1739 }
1740}
1741
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001742/* Two-point radial gradients are specified by two circles, each with a center
1743 point and radius. The gradient can be considered to be a series of
1744 concentric circles, with the color interpolated from the start circle
1745 (at t=0) to the end circle (at t=1).
1746
1747 For each point (x, y) in the span, we want to find the
1748 interpolated circle that intersects that point. The center
1749 of the desired circle (Cx, Cy) falls at some distance t
1750 along the line segment between the start point (Sx, Sy) and
1751 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001752
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001753 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1754 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001755
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001756 The radius of the desired circle (r) is also a linear interpolation t
1757 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001758
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001759 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001760
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001761 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001762
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001763 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001764
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001765 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001766
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001767 (x - ((1 - t) * Sx + t * Ex))^2
1768 + (y - ((1 - t) * Sy + t * Ey))^2
1769 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001770
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001771 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001772
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001773 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1774 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1775 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001776
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001777 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1778
1779 [Dx^2 + Dy^2 - Dr^2)] * t^2
1780 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1781 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001782
1783 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001784 possible circles on which the point may fall. Solving for t yields
1785 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001786
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001787 If a<0, the start circle is entirely contained in the
1788 end circle, and one of the roots will be <0 or >1 (off the line
1789 segment). If a>0, the start circle falls at least partially
1790 outside the end circle (or vice versa), and the gradient
1791 defines a "tube" where a point may be on one circle (on the
1792 inside of the tube) or the other (outside of the tube). We choose
1793 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001794
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001795 In order to keep the math to within the limits of fixed point,
1796 we divide the entire quadratic by Dr^2, and replace
1797 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001798
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001799 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1800 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1801 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001802
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001803 (x' and y' are computed by appending the subtract and scale to the
1804 fDstToIndex matrix in the constructor).
1805
1806 Since the 'A' component of the quadratic is independent of x' and y', it
1807 is precomputed in the constructor. Since the 'B' component is linear in
1808 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001809 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001810 a perspective projection), it must be computed in the loop.
1811
1812*/
1813
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001814namespace {
1815
1816inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1817 SkScalar sr2d2, SkScalar foura,
1818 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001819 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001820 if (0 == foura) {
1821 return SkScalarToFixed(SkScalarDiv(-c, b));
1822 }
1823
reed@google.com84e9c082011-04-13 17:44:24 +00001824 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001825 if (discrim < 0) {
1826 discrim = -discrim;
1827 }
reed@google.com84e9c082011-04-13 17:44:24 +00001828 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1829 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001830 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001831 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001832 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001833 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001834 }
reed@google.com84e9c082011-04-13 17:44:24 +00001835 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001836}
1837
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001838typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1839 SkScalar fy, SkScalar dy,
1840 SkScalar b, SkScalar db,
1841 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001842 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001843 int count);
1844
1845void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1846 SkScalar fy, SkScalar dy,
1847 SkScalar b, SkScalar db,
1848 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001849 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001850 int count) {
1851 for (; count > 0; --count) {
1852 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1853 fOneOverTwoA, posRoot);
1854 SkFixed index = SkClampMax(t, 0xFFFF);
1855 SkASSERT(index <= 0xFFFF);
1856 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1857 fx += dx;
1858 fy += dy;
1859 b += db;
1860 }
1861}
1862void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1863 SkScalar fy, SkScalar dy,
1864 SkScalar b, SkScalar db,
1865 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001866 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001867 int count) {
1868 for (; count > 0; --count) {
1869 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1870 fOneOverTwoA, posRoot);
1871 SkFixed index = mirror_tileproc(t);
1872 SkASSERT(index <= 0xFFFF);
1873 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1874 fx += dx;
1875 fy += dy;
1876 b += db;
1877 }
1878}
1879
1880void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1881 SkScalar fy, SkScalar dy,
1882 SkScalar b, SkScalar db,
1883 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001884 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001885 int count) {
1886 for (; count > 0; --count) {
1887 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1888 fOneOverTwoA, posRoot);
1889 SkFixed index = repeat_tileproc(t);
1890 SkASSERT(index <= 0xFFFF);
1891 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1892 fx += dx;
1893 fy += dy;
1894 b += db;
1895 }
1896}
1897
1898
1899
1900}
1901
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001902class Two_Point_Radial_Gradient : public Gradient_Shader {
1903public:
1904 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1905 const SkPoint& end, SkScalar endRadius,
1906 const SkColor colors[], const SkScalar pos[],
1907 int colorCount, SkShader::TileMode mode,
1908 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001909 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1910 fCenter1(start),
1911 fCenter2(end),
1912 fRadius1(startRadius),
1913 fRadius2(endRadius) {
1914 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001915 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001916
1917 virtual BitmapType asABitmap(SkBitmap* bitmap,
1918 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001919 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001920 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001921 if (bitmap) {
1922 this->commonAsABitmap(bitmap);
1923 }
1924 SkScalar diffL = 0; // just to avoid gcc warning
1925 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001926 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001927 SkScalarSquare(fDiff.fY));
1928 }
1929 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001930 if (diffL) {
1931 SkScalar invDiffL = SkScalarInvert(diffL);
1932 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1933 SkScalarMul(invDiffL, fDiff.fX));
1934 } else {
1935 matrix->reset();
1936 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001937 matrix->preConcat(fPtsToUnit);
1938 }
1939 if (xy) {
1940 xy[0] = fTileMode;
1941 xy[1] = kClamp_TileMode;
1942 }
1943 if (NULL != twoPointRadialParams) {
1944 twoPointRadialParams[0] = diffL;
1945 twoPointRadialParams[1] = fStartRadius;
1946 twoPointRadialParams[2] = fDiffRadius;
1947 }
1948 return kTwoPointRadial_BitmapType;
1949 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001950
reed@google.com8e6d9142011-12-07 15:30:34 +00001951 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001952 if (info) {
1953 commonAsAGradient(info);
1954 info->fPoint[0] = fCenter1;
1955 info->fPoint[1] = fCenter2;
1956 info->fRadius[0] = fRadius1;
1957 info->fRadius[1] = fRadius2;
1958 }
1959 return kRadial2_GradientType;
1960 }
1961
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001962 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1963 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001964 SkASSERT(count > 0);
1965
1966 // Zero difference between radii: fill with transparent black.
1967 if (fDiffRadius == 0) {
1968 sk_bzero(dstC, count * sizeof(*dstC));
1969 return;
1970 }
1971 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1972 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001973 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001974
1975 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001976 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001977 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001978 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001979 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1980 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001981 SkScalar dx, fx = srcPt.fX;
1982 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001983
reed@google.com61eb0402011-04-15 12:11:12 +00001984 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001985 SkFixed fixedX, fixedY;
1986 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1987 dx = SkFixedToScalar(fixedX);
1988 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001989 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001990 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001991 dx = fDstToIndex.getScaleX();
1992 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001993 }
reed@google.com84e9c082011-04-13 17:44:24 +00001994 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1995 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1996 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1997 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001998
1999 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00002000 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002001 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00002002 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002003 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00002004 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002005 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002006 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002007 (*shadeProc)(fx, dx, fy, dy, b, db,
2008 fSr2D2, foura, fOneOverTwoA, posRoot,
2009 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00002010 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00002011 SkScalar dstX = SkIntToScalar(x);
2012 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002013 for (; count > 0; --count) {
2014 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00002015 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00002016 SkScalar fx = srcPt.fX;
2017 SkScalar fy = srcPt.fY;
2018 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
2019 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002020 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
2021 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002022 SkFixed index = proc(t);
2023 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002024 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00002025 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002026 }
2027 }
2028 }
2029
reed@android.com6c59a172009-09-22 20:24:05 +00002030 virtual bool setContext(const SkBitmap& device,
2031 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00002032 const SkMatrix& matrix) SK_OVERRIDE {
2033 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00002034 return false;
2035 }
2036
2037 // we don't have a span16 proc
2038 fFlags &= ~kHasSpan16_Flag;
2039 return true;
2040 }
2041
reed@google.com7716afb2011-12-07 15:17:50 +00002042 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00002043 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002044 buffer.writeScalar(fCenter1.fX);
2045 buffer.writeScalar(fCenter1.fY);
2046 buffer.writeScalar(fCenter2.fX);
2047 buffer.writeScalar(fCenter2.fY);
2048 buffer.writeScalar(fRadius1);
2049 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002050 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002051
djsollen@google.comba28d032012-03-26 17:57:35 +00002052 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Two_Point_Radial_Gradient)
2053
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002054protected:
reed@android.combcfc7332009-11-10 16:19:39 +00002055 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002056 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002057 fCenter1(unflatten_point(buffer)),
2058 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002059 fRadius1(buffer.readScalar()),
2060 fRadius2(buffer.readScalar()) {
2061 init();
reed@android.combcfc7332009-11-10 16:19:39 +00002062 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002063
2064private:
2065 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002066 const SkPoint fCenter1;
2067 const SkPoint fCenter2;
2068 const SkScalar fRadius1;
2069 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002070 SkPoint fDiff;
2071 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002072
2073 void init() {
2074 fDiff = fCenter1 - fCenter2;
2075 fDiffRadius = fRadius2 - fRadius1;
2076 SkScalar inv = SkScalarInvert(fDiffRadius);
2077 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2078 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2079 fStartRadius = SkScalarMul(fRadius1, inv);
2080 fSr2D2 = SkScalarSquare(fStartRadius);
2081 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002082 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002083
2084 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2085 fPtsToUnit.postScale(inv, inv);
2086 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002087};
2088
reed@android.com8a1c16f2008-12-17 15:59:43 +00002089///////////////////////////////////////////////////////////////////////////////
2090
2091class Sweep_Gradient : public Gradient_Shader {
2092public:
2093 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2094 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002095 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2096 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002097 {
2098 fPtsToUnit.setTranslate(-cx, -cy);
2099 }
reed@google.com7716afb2011-12-07 15:17:50 +00002100 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2101 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002102
2103 virtual BitmapType asABitmap(SkBitmap* bitmap,
2104 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002105 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002106 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002107 if (bitmap) {
2108 this->commonAsABitmap(bitmap);
2109 }
2110 if (matrix) {
2111 *matrix = fPtsToUnit;
2112 }
2113 if (xy) {
2114 xy[0] = fTileMode;
2115 xy[1] = kClamp_TileMode;
2116 }
2117 return kSweep_BitmapType;
2118 }
2119
reed@google.com7716afb2011-12-07 15:17:50 +00002120 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002121 if (info) {
2122 commonAsAGradient(info);
2123 info->fPoint[0] = fCenter;
2124 }
2125 return kSweep_GradientType;
2126 }
2127
reed@google.com7716afb2011-12-07 15:17:50 +00002128 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002129 this->INHERITED::flatten(buffer);
2130 buffer.writeScalar(fCenter.fX);
2131 buffer.writeScalar(fCenter.fY);
2132 }
2133
djsollen@google.comba28d032012-03-26 17:57:35 +00002134 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Sweep_Gradient)
2135
reed@android.com8a1c16f2008-12-17 15:59:43 +00002136protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002137 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
2138 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002139 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002140 }
2141
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142private:
2143 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002144 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145};
2146
2147#ifdef COMPUTE_SWEEP_TABLE
2148#define PI 3.14159265
2149static bool gSweepTableReady;
2150static uint8_t gSweepTable[65];
2151
2152/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2153 We scale the results to [0..32]
2154*/
reed@google.com61eb0402011-04-15 12:11:12 +00002155static const uint8_t* build_sweep_table() {
2156 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157 const int N = 65;
2158 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002159
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 for (int i = 0; i < N; i++)
2161 {
2162 double arg = i / DENOM;
2163 double v = atan(arg);
2164 int iv = (int)round(v * DENOM * 2 / PI);
2165// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2166 printf("%d, ", iv);
2167 gSweepTable[i] = iv;
2168 }
2169 gSweepTableReady = true;
2170 }
2171 return gSweepTable;
2172}
2173#else
2174static const uint8_t gSweepTable[] = {
2175 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2176 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2177 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2178 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2179 32
2180};
2181static const uint8_t* build_sweep_table() { return gSweepTable; }
2182#endif
2183
2184// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2185// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2186// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2187
2188//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002189static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190 SkASSERT(numer <= denom);
2191 SkASSERT(numer > 0);
2192 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002193
reed@android.com8a1c16f2008-12-17 15:59:43 +00002194 int nbits = SkCLZ(numer);
2195 int dbits = SkCLZ(denom);
2196 int bits = 6 - nbits + dbits;
2197 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002198
reed@google.com61eb0402011-04-15 12:11:12 +00002199 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002200 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002201 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002202
2203 denom <<= dbits - 1;
2204 numer <<= nbits - 1;
2205
2206 unsigned result = 0;
2207
2208 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002209 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002210 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002211 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002212 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002213 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002214
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002216 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002217 // make room for the rest of the answer bits
2218 result <<= bits;
2219 switch (bits) {
2220 case 6:
2221 if ((numer = (numer << 1) - denom) >= 0)
2222 result |= 32;
2223 else
2224 numer += denom;
2225 case 5:
2226 if ((numer = (numer << 1) - denom) >= 0)
2227 result |= 16;
2228 else
2229 numer += denom;
2230 case 4:
2231 if ((numer = (numer << 1) - denom) >= 0)
2232 result |= 8;
2233 else
2234 numer += denom;
2235 case 3:
2236 if ((numer = (numer << 1) - denom) >= 0)
2237 result |= 4;
2238 else
2239 numer += denom;
2240 case 2:
2241 if ((numer = (numer << 1) - denom) >= 0)
2242 result |= 2;
2243 else
2244 numer += denom;
2245 case 1:
2246 default: // not strictly need, but makes GCC make better ARM code
2247 if ((numer = (numer << 1) - denom) >= 0)
2248 result |= 1;
2249 else
2250 numer += denom;
2251 }
2252 }
2253 return result;
2254}
2255
2256// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002257static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002258#ifdef SK_DEBUG
2259 {
2260 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002261 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002262 gOnce = true;
2263 SkASSERT(div_64(55, 55) == 64);
2264 SkASSERT(div_64(128, 256) == 32);
2265 SkASSERT(div_64(2326528, 4685824) == 31);
2266 SkASSERT(div_64(753664, 5210112) == 9);
2267 SkASSERT(div_64(229376, 4882432) == 3);
2268 SkASSERT(div_64(2, 64) == 2);
2269 SkASSERT(div_64(1, 64) == 1);
2270 // test that we handle underflow correctly
2271 SkASSERT(div_64(12345, 0x54321234) == 0);
2272 }
2273 }
2274#endif
2275
2276 SkASSERT(y > 0 && x > 0);
2277 const uint8_t* table = build_sweep_table();
2278
2279 unsigned result;
2280 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002281 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002282 // first part of the atan(v) = PI/2 - atan(1/v) identity
2283 // since our div_64 and table want v <= 1, where v = y/x
2284 SkTSwap<SkFixed>(x, y);
2285 }
2286
2287 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002288
reed@android.com8a1c16f2008-12-17 15:59:43 +00002289#ifdef SK_DEBUG
2290 {
2291 unsigned result2 = SkDivBits(y, x, 6);
2292 SkASSERT(result2 == result ||
2293 (result == 1 && result2 == 0));
2294 }
2295#endif
2296
2297 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2298 result = table[result];
2299
reed@google.com61eb0402011-04-15 12:11:12 +00002300 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002301 // complete the atan(v) = PI/2 - atan(1/v) identity
2302 result = 64 - result;
2303 // pin to 63
2304 result -= result >> 6;
2305 }
2306
2307 SkASSERT(result <= 63);
2308 return result;
2309}
2310
2311// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002312#ifdef SK_SCALAR_IS_FLOAT
2313static unsigned SkATan2_255(float y, float x) {
2314 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2315 static const float g255Over2PI = 40.584510488433314f;
2316
2317 float result = sk_float_atan2(y, x);
2318 if (result < 0) {
2319 result += 2 * SK_ScalarPI;
2320 }
2321 SkASSERT(result >= 0);
2322 // since our value is always >= 0, we can cast to int, which is faster than
2323 // calling floorf()
2324 int ir = (int)(result * g255Over2PI);
2325 SkASSERT(ir >= 0 && ir <= 255);
2326 return ir;
2327}
2328#else
reed@google.com61eb0402011-04-15 12:11:12 +00002329static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2330 if (x == 0) {
2331 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002332 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002333 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002334 return y < 0 ? 192 : 64;
2335 }
reed@google.com61eb0402011-04-15 12:11:12 +00002336 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002337 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002338 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002339
reed@android.com8a1c16f2008-12-17 15:59:43 +00002340 /* Find the right quadrant for x,y
2341 Since atan_0_90 only handles the first quadrant, we rotate x,y
2342 appropriately before calling it, and then add the right amount
2343 to account for the real quadrant.
2344 quadrant 0 : add 0 | x > 0 && y > 0
2345 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2346 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2347 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002348
reed@android.com8a1c16f2008-12-17 15:59:43 +00002349 map x<0 to (1 << 6)
2350 map y<0 to (3 << 6)
2351 add = map_x ^ map_y
2352 */
2353 int xsign = x >> 31;
2354 int ysign = y >> 31;
2355 int add = ((-xsign) ^ (ysign & 3)) << 6;
2356
2357#ifdef SK_DEBUG
2358 if (0 == add)
2359 SkASSERT(x > 0 && y > 0);
2360 else if (64 == add)
2361 SkASSERT(x < 0 && y > 0);
2362 else if (128 == add)
2363 SkASSERT(x < 0 && y < 0);
2364 else if (192 == add)
2365 SkASSERT(x > 0 && y < 0);
2366 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002367 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002368#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002369
reed@android.com8a1c16f2008-12-17 15:59:43 +00002370 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2371 where we need to rotate x,y by 90 or -90
2372 */
2373 x = (x ^ xsign) - xsign;
2374 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002375 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002376 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002377 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002378
2379 unsigned result = add + atan_0_90(y, x);
2380 SkASSERT(result < 256);
2381 return result;
2382}
reed@google.com51baf5a2011-09-21 13:38:36 +00002383#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002384
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002385void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2386 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002387 SkMatrix::MapXYProc proc = fDstToIndexProc;
2388 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002389 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002390 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002391
reed@google.com61eb0402011-04-15 12:11:12 +00002392 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002393 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2394 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002395 SkScalar dx, fx = srcPt.fX;
2396 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002397
reed@google.com61eb0402011-04-15 12:11:12 +00002398 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002399 SkFixed storage[2];
2400 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2401 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002402 dx = SkFixedToScalar(storage[0]);
2403 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002404 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002405 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002406 dx = matrix.getScaleX();
2407 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002408 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002409
reed@google.com61eb0402011-04-15 12:11:12 +00002410 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002411 *dstC++ = cache[SkATan2_255(fy, fx)];
2412 fx += dx;
2413 fy += dy;
2414 }
reed@google.com61eb0402011-04-15 12:11:12 +00002415 } else { // perspective case
2416 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002417 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002418 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2419 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002420 }
2421 }
2422}
2423
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002424void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2425 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002426 SkMatrix::MapXYProc proc = fDstToIndexProc;
2427 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002428 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002429 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002430 SkPoint srcPt;
2431
reed@google.com61eb0402011-04-15 12:11:12 +00002432 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002433 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2434 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002435 SkScalar dx, fx = srcPt.fX;
2436 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002437
reed@google.com61eb0402011-04-15 12:11:12 +00002438 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002439 SkFixed storage[2];
2440 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2441 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002442 dx = SkFixedToScalar(storage[0]);
2443 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002444 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002445 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002446 dx = matrix.getScaleX();
2447 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002448 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002449
reed@google.com61eb0402011-04-15 12:11:12 +00002450 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002451 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2452 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002453 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002454 fx += dx;
2455 fy += dy;
2456 }
reed@google.com61eb0402011-04-15 12:11:12 +00002457 } else { // perspective case
2458 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002459 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2460 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002461
reed@google.com51baf5a2011-09-21 13:38:36 +00002462 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002463 index >>= (8 - kCache16Bits);
2464 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002465 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002466 }
2467 }
2468}
2469
reed@google.com61eb0402011-04-15 12:11:12 +00002470///////////////////////////////////////////////////////////////////////////////
2471///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002472
2473// assumes colors is SkColor* and pos is SkScalar*
2474#define EXPAND_1_COLOR(count) \
2475 SkColor tmp[2]; \
2476 do { \
2477 if (1 == count) { \
2478 tmp[0] = tmp[1] = colors[0]; \
2479 colors = tmp; \
2480 pos = NULL; \
2481 count = 2; \
2482 } \
2483 } while (0)
2484
reed@google.com61eb0402011-04-15 12:11:12 +00002485SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2486 const SkColor colors[],
2487 const SkScalar pos[], int colorCount,
2488 SkShader::TileMode mode,
2489 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002490 if (NULL == pts || NULL == colors || colorCount < 1) {
2491 return NULL;
2492 }
2493 EXPAND_1_COLOR(colorCount);
2494
reed@android.comab840b82009-07-01 17:00:03 +00002495 return SkNEW_ARGS(Linear_Gradient,
2496 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002497}
2498
reed@google.com61eb0402011-04-15 12:11:12 +00002499SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2500 const SkColor colors[],
2501 const SkScalar pos[], int colorCount,
2502 SkShader::TileMode mode,
2503 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002504 if (radius <= 0 || NULL == colors || colorCount < 1) {
2505 return NULL;
2506 }
2507 EXPAND_1_COLOR(colorCount);
2508
reed@android.comab840b82009-07-01 17:00:03 +00002509 return SkNEW_ARGS(Radial_Gradient,
2510 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002511}
2512
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002513SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2514 SkScalar startRadius,
2515 const SkPoint& end,
2516 SkScalar endRadius,
2517 const SkColor colors[],
2518 const SkScalar pos[],
2519 int colorCount,
2520 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002521 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002522 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2523 return NULL;
2524 }
2525 EXPAND_1_COLOR(colorCount);
2526
2527 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002528 (start, startRadius, end, endRadius, colors, pos,
2529 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002530}
2531
reed@android.com8a1c16f2008-12-17 15:59:43 +00002532SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2533 const SkColor colors[],
2534 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002535 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002536 if (NULL == colors || count < 1) {
2537 return NULL;
2538 }
2539 EXPAND_1_COLOR(count);
2540
2541 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2542}
2543
caryclark@google.comd26147a2011-12-15 14:16:43 +00002544SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2545 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2546 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002547 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002548 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2549SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END