blob: 4bbc96c42f43b7aa8e2a7c532ecda71d538c0a34 [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.com55b8e8c2011-01-13 16:22:35 +0000852 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 return SkNEW_ARGS(Linear_Gradient, (buffer));
854 }
855
reed@google.com7716afb2011-12-07 15:17:50 +0000856 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000857 this->INHERITED::flatten(buffer);
858 buffer.writeScalar(fStart.fX);
859 buffer.writeScalar(fStart.fY);
860 buffer.writeScalar(fEnd.fX);
861 buffer.writeScalar(fEnd.fY);
862 }
863
caryclark@google.comd26147a2011-12-15 14:16:43 +0000864 SK_DECLARE_FLATTENABLE_REGISTRAR()
865
reed@android.com8a1c16f2008-12-17 15:59:43 +0000866protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000867 Linear_Gradient(SkFlattenableReadBuffer& buffer)
868 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000869 fStart(unflatten_point(buffer)),
870 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000871 }
reed@google.com7716afb2011-12-07 15:17:50 +0000872 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873
874private:
875 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000876 const SkPoint fStart;
877 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878};
879
reed@android.com5119bdb2009-06-12 21:27:03 +0000880bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
881 const SkMatrix& matrix) {
882 if (!this->INHERITED::setContext(device, paint, matrix)) {
883 return false;
884 }
885
886 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
887 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000888 fFlags |= SkShader::kConstInY32_Flag;
889 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
890 // only claim this if we do have a 16bit mode (i.e. none of our
891 // colors have alpha), and if we are not dithering (which obviously
892 // is not const in Y).
893 fFlags |= SkShader::kConstInY16_Flag;
894 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000895 }
896 return true;
897}
898
reed@google.com5eb158d2011-04-15 15:50:34 +0000899#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000900 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000901 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000902 SkASSERT(fi <= 0xFF); \
903 fx += dx; \
904 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000905 toggle ^= Gradient_Shader::kDitherStride32; \
reed@google.com13659f12011-04-18 19:59:38 +0000906 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000907
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000908namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000909
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000910typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000911 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000912 int toggle, int count);
913
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000914// This function is deprecated, and will be replaced by
915// shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it.
916void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
917 SkPMColor* SK_RESTRICT dstC,
918 const SkPMColor* SK_RESTRICT cache,
919 int toggle, int count) {
920 // We're a vertical gradient, so no change in a span.
921 // If colors change sharply across the gradient, dithering is
922 // insufficient (it subsamples the color space) and we need to lerp.
923 unsigned fullIndex = proc(fx);
924 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
925 sk_memset32_dither(dstC,
926 cache[toggle + fi],
927 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
928 count);
929}
930
931// Linear interpolation (lerp) is unnecessary if there are no sharp
932// discontinuities in the gradient - which must be true if there are
933// only 2 colors - but it's cheap.
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000934void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
935 SkPMColor* SK_RESTRICT dstC,
936 const SkPMColor* SK_RESTRICT cache,
937 int toggle, int count) {
938 // We're a vertical gradient, so no change in a span.
939 // If colors change sharply across the gradient, dithering is
940 // insufficient (it subsamples the color space) and we need to lerp.
941 unsigned fullIndex = proc(fx);
942 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
943 unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32;
944 SkPMColor lerp =
945 SkFastFourByteInterp(
946 cache[toggle + fi + 1],
947 cache[toggle + fi], remainder);
948 SkPMColor dlerp =
949 SkFastFourByteInterp(
950 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1],
951 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], remainder);
952 sk_memset32_dither(dstC, lerp, dlerp, count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000953}
954
955void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
956 SkPMColor* SK_RESTRICT dstC,
957 const SkPMColor* SK_RESTRICT cache,
958 int toggle, int count) {
959 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000960 range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000961
962 if ((count = range.fCount0) > 0) {
963 sk_memset32_dither(dstC,
964 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000965 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000966 count);
967 dstC += count;
968 }
969 if ((count = range.fCount1) > 0) {
970 int unroll = count >> 3;
971 fx = range.fFx1;
972 for (int i = 0; i < unroll; i++) {
973 NO_CHECK_ITER; NO_CHECK_ITER;
974 NO_CHECK_ITER; NO_CHECK_ITER;
975 NO_CHECK_ITER; NO_CHECK_ITER;
976 NO_CHECK_ITER; NO_CHECK_ITER;
977 }
978 if ((count &= 7) > 0) {
979 do {
980 NO_CHECK_ITER;
981 } while (--count != 0);
982 }
983 }
984 if ((count = range.fCount2) > 0) {
985 sk_memset32_dither(dstC,
986 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000987 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000988 count);
989 }
990}
991
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000992void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
993 SkPMColor* SK_RESTRICT dstC,
994 const SkPMColor* SK_RESTRICT cache,
995 int toggle, int count) {
996 do {
997 unsigned fi = mirror_8bits(fx >> 8);
998 SkASSERT(fi <= 0xFF);
999 fx += dx;
1000 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001001 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001002 } while (--count != 0);
1003}
1004
1005void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1006 SkPMColor* SK_RESTRICT dstC,
1007 const SkPMColor* SK_RESTRICT cache,
1008 int toggle, int count) {
1009 do {
1010 unsigned fi = repeat_8bits(fx >> 8);
1011 SkASSERT(fi <= 0xFF);
1012 fx += dx;
1013 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001014 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001015 } while (--count != 0);
1016}
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001017
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001018}
1019
1020void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1021 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022 SkASSERT(count > 0);
1023
1024 SkPoint srcPt;
1025 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1026 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001027 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +00001028#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001029 int toggle = ((x ^ y) & 1) * kDitherStride32;
reed@google.com0e734bd2011-12-08 17:24:44 +00001030#else
1031 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +00001032#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001033
reed@android.comc552a432009-06-12 20:02:50 +00001034 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001035 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1036 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001037 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1038
reed@android.comc552a432009-06-12 20:02:50 +00001039 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001040 SkFixed dxStorage[1];
1041 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1042 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +00001043 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001044 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1045 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1046 }
1047
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001048 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +00001049 if (SkFixedNearlyZero(dx)) {
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001050#ifdef SK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS
1051 if (fColorCount > 2) {
1052 shadeProc = shadeSpan_linear_vertical_lerp;
1053 } else {
1054 shadeProc = shadeSpan_linear_vertical;
1055 }
1056#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001057 shadeProc = shadeSpan_linear_vertical_lerp;
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001058#endif
reed@android.comc552a432009-06-12 20:02:50 +00001059 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001060 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +00001061 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001062 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +00001063 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001064 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001066 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +00001067 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001068 SkScalar dstX = SkIntToScalar(x);
1069 SkScalar dstY = SkIntToScalar(y);
1070 do {
1071 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1072 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1073 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001074 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001075 toggle ^= Gradient_Shader::kDitherStride32;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001076 dstX += SK_Scalar1;
1077 } while (--count != 0);
1078 }
1079}
1080
reed@google.com55b8e8c2011-01-13 16:22:35 +00001081SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +00001082 SkMatrix* matrix,
1083 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +00001084 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001086 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001087 }
1088 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001089 matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001090 matrix->preConcat(fPtsToUnit);
1091 }
1092 if (xy) {
1093 xy[0] = fTileMode;
1094 xy[1] = kClamp_TileMode;
1095 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001096 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001097}
1098
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001099SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1100 if (info) {
1101 commonAsAGradient(info);
1102 info->fPoint[0] = fStart;
1103 info->fPoint[1] = fEnd;
1104 }
1105 return kLinear_GradientType;
1106}
1107
reed@android.com3c9b2a42009-08-27 19:28:37 +00001108static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1109 int count) {
1110 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 *dst++ = value;
1112 count -= 1;
1113 SkTSwap(value, other);
1114 }
1115
1116 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001117
reed@android.com3c9b2a42009-08-27 19:28:37 +00001118 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001120 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001121}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122
reed@google.com5eb158d2011-04-15 15:50:34 +00001123#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001124 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001125 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001126 SkASSERT(fi < Gradient_Shader::kCache16Count); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001127 fx += dx; \
1128 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001129 toggle ^= Gradient_Shader::kDitherStride16; \
reed@google.com13659f12011-04-18 19:59:38 +00001130 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001131
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001132namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001133
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001134typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001135 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001136 int toggle, int count);
1137
1138void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1139 uint16_t* SK_RESTRICT dstC,
1140 const uint16_t* SK_RESTRICT cache,
1141 int toggle, int count) {
1142 // we're a vertical gradient, so no change in a span
1143 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001144 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001145 dither_memset16(dstC, cache[toggle + fi],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001146 cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001147
1148}
1149
1150void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1151 uint16_t* SK_RESTRICT dstC,
1152 const uint16_t* SK_RESTRICT cache,
1153 int toggle, int count) {
1154 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001155 range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001156
1157 if ((count = range.fCount0) > 0) {
1158 dither_memset16(dstC,
1159 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001160 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001161 count);
1162 dstC += count;
1163 }
1164 if ((count = range.fCount1) > 0) {
1165 int unroll = count >> 3;
1166 fx = range.fFx1;
1167 for (int i = 0; i < unroll; i++) {
1168 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1169 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1170 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1171 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1172 }
1173 if ((count &= 7) > 0) {
1174 do {
1175 NO_CHECK_ITER_16;
1176 } while (--count != 0);
1177 }
1178 }
1179 if ((count = range.fCount2) > 0) {
1180 dither_memset16(dstC,
1181 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001182 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001183 count);
1184 }
1185}
1186
1187void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1188 uint16_t* SK_RESTRICT dstC,
1189 const uint16_t* SK_RESTRICT cache,
1190 int toggle, int count) {
1191 do {
1192 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1193 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001194 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001195 fx += dx;
1196 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001197 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001198 } while (--count != 0);
1199}
1200
1201void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1202 uint16_t* SK_RESTRICT dstC,
1203 const uint16_t* SK_RESTRICT cache,
1204 int toggle, int count) {
1205 SkASSERT(proc == repeat_tileproc);
1206 do {
1207 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1208 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001209 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001210 fx += dx;
1211 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001212 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001213 } while (--count != 0);
1214}
1215}
1216
1217void Linear_Gradient::shadeSpan16(int x, int y,
1218 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001219 SkASSERT(count > 0);
1220
1221 SkPoint srcPt;
1222 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1223 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001224 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001225 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001226
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001227 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001228 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1229 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001230 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1231
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001232 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233 SkFixed dxStorage[1];
1234 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1235 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001236 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1238 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1239 }
1240
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001241 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001242 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001243 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001244 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001245 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001246 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001247 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001248 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001251 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001252 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 SkScalar dstX = SkIntToScalar(x);
1254 SkScalar dstY = SkIntToScalar(y);
1255 do {
1256 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1257 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1258 SkASSERT(fi <= 0xFFFF);
1259
reed@android.com512a8762009-12-14 15:25:36 +00001260 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001262 toggle ^= Gradient_Shader::kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001263
1264 dstX += SK_Scalar1;
1265 } while (--count != 0);
1266 }
1267}
1268
1269///////////////////////////////////////////////////////////////////////////////
1270
1271#define kSQRT_TABLE_BITS 11
1272#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1273
1274#include "SkRadialGradient_Table.h"
1275
1276#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1277
1278#include <stdio.h>
1279
reed@google.com61eb0402011-04-15 12:11:12 +00001280void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001281 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1282
1283 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1284 SkASSERT(file);
1285 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1286
reed@google.com61eb0402011-04-15 12:11:12 +00001287 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1288 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001290 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291
1292 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1293
1294 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001295 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001297 }
1298 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001300 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301 }
1302 ::fprintf(file, "};\n");
1303 ::fclose(file);
1304}
1305
1306#endif
1307
1308
reed@google.com61eb0402011-04-15 12:11:12 +00001309static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1310 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001311 SkScalar inv = SkScalarInvert(radius);
1312
1313 matrix->setTranslate(-center.fX, -center.fY);
1314 matrix->postScale(inv, inv);
1315}
1316
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001317
1318namespace {
1319
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001320typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
1321 SkScalar sfy, SkScalar sdy,
1322 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001323 int toggle, int count);
1324
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001325void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
1326 SkScalar sfy, SkScalar sdy,
1327 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001328 int toggle, int count) {
1329 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1330
1331 /* knock these down so we can pin against +- 0x7FFF, which is an
1332 immediate load, rather than 0xFFFF which is slower. This is a
1333 compromise, since it reduces our precision, but that appears
1334 to be visually OK. If we decide this is OK for all of our cases,
1335 we could (it seems) put this scale-down into fDstToIndex,
1336 to avoid having to do these extra shifts each time.
1337 */
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001338 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1339 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1340 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1341 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001342 // might perform this check for the other modes,
1343 // but the win will be a smaller % of the total
1344 if (dy == 0) {
1345 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1346 fy *= fy;
1347 do {
1348 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1349 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1350 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1351 fx += dx;
1352 *dstC++ = cache[toggle +
1353 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001354 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001355 } while (--count != 0);
1356 } else {
1357 do {
1358 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1359 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1360 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1361 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1362 fx += dx;
1363 fy += dy;
1364 *dstC++ = cache[toggle +
1365 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001366 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001367 } while (--count != 0);
1368 }
1369}
1370
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001371void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
1372 SkScalar sfy, SkScalar sdy,
1373 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001374 int toggle, int count) {
1375 do {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001376#ifdef SK_SCALAR_IS_FLOAT
1377 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1378 SkFixed dist = SkFloatToFixed(fdist);
1379#else
1380 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1381 SkFixedSquare(sfy);
1382 if (magnitudeSquared < 0) // Overflow.
1383 magnitudeSquared = SK_FixedMax;
1384 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1385#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001386 unsigned fi = mirror_tileproc(dist);
1387 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001388 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001389 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001390 sfx += sdx;
1391 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001392 } while (--count != 0);
1393}
1394
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001395void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
1396 SkScalar sfy, SkScalar sdy,
1397 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001398 int toggle, int count) {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001399 SkFixed fx = SkScalarToFixed(sfx);
1400 SkFixed dx = SkScalarToFixed(sdx);
1401 SkFixed fy = SkScalarToFixed(sfy);
1402 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001403 do {
1404 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1405 unsigned fi = repeat_tileproc(dist);
1406 SkASSERT(fi <= 0xFFFF);
1407 fx += dx;
1408 fy += dy;
1409 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001410 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001411 } while (--count != 0);
1412}
1413
1414}
1415
reed@android.com8a1c16f2008-12-17 15:59:43 +00001416class Radial_Gradient : public Gradient_Shader {
1417public:
1418 Radial_Gradient(const SkPoint& center, SkScalar radius,
1419 const SkColor colors[], const SkScalar pos[], int colorCount,
1420 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001421 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1422 fCenter(center),
1423 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424 {
1425 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1426 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1427
1428 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1429 }
reed@google.com61eb0402011-04-15 12:11:12 +00001430
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001431 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
1432 SK_OVERRIDE;
1433 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
1434 int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001435 SkASSERT(count > 0);
1436
1437 SkPoint srcPt;
1438 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1439 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001440 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001441 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442
reed@android.com3c9b2a42009-08-27 19:28:37 +00001443 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001444 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1445 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001446
1447 SkScalar sdx = fDstToIndex.getScaleX();
1448 SkScalar sdy = fDstToIndex.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449
reed@android.com3c9b2a42009-08-27 19:28:37 +00001450 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451 SkFixed storage[2];
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001452 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1453 &storage[0], &storage[1]);
1454 sdx = SkFixedToScalar(storage[0]);
1455 sdy = SkFixedToScalar(storage[1]);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001456 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458 }
1459
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001460 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001461 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001462 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001463 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001464 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001465 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 }
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001468 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
1469 cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001470 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471 SkScalar dstX = SkIntToScalar(x);
1472 SkScalar dstY = SkIntToScalar(y);
1473 do {
1474 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1475 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1476 SkASSERT(fi <= 0xFFFF);
1477
1478 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001479 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001480 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481
1482 dstX += SK_Scalar1;
1483 } while (--count != 0);
1484 }
1485 }
1486
reed@google.com55b8e8c2011-01-13 16:22:35 +00001487 virtual BitmapType asABitmap(SkBitmap* bitmap,
1488 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001489 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001490 SkScalar* twoPointRadialParams)
1491 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001492 if (bitmap) {
1493 this->commonAsABitmap(bitmap);
1494 }
1495 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001496 matrix->setScale(SkIntToScalar(kGradient32Length),
1497 SkIntToScalar(kGradient32Length));
reed@google.comdc731fd2010-12-23 15:19:47 +00001498 matrix->preConcat(fPtsToUnit);
1499 }
1500 if (xy) {
1501 xy[0] = fTileMode;
1502 xy[1] = kClamp_TileMode;
1503 }
1504 return kRadial_BitmapType;
1505 }
reed@google.com7716afb2011-12-07 15:17:50 +00001506 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001507 if (info) {
1508 commonAsAGradient(info);
1509 info->fPoint[0] = fCenter;
1510 info->fRadius[0] = fRadius;
1511 }
1512 return kRadial_GradientType;
1513 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001514
reed@google.com8e6d9142011-12-07 15:30:34 +00001515 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516 return SkNEW_ARGS(Radial_Gradient, (buffer));
1517 }
1518
reed@google.com7716afb2011-12-07 15:17:50 +00001519 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001520 this->INHERITED::flatten(buffer);
1521 buffer.writeScalar(fCenter.fX);
1522 buffer.writeScalar(fCenter.fY);
1523 buffer.writeScalar(fRadius);
1524 }
1525
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001527 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1528 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001529 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001530 fRadius(buffer.readScalar()) {
1531 }
reed@google.com7716afb2011-12-07 15:17:50 +00001532 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533
1534private:
1535 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001536 const SkPoint fCenter;
1537 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538};
1539
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001540namespace {
1541
1542inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001543 // fast, overly-conservative test: checks unit square instead
1544 // of unit circle
1545 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1546 (fx <= -SK_FixedHalf && dx <= 0);
1547 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1548 (fy <= -SK_FixedHalf && dy <= 0);
1549
1550 return xClamped || yClamped;
1551}
1552
1553// Return true if (fx * fy) is always inside the unit circle
1554// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1555// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001556inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001557 int fy, int dy, int count) {
1558 SkASSERT(count > 0);
1559 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1560 return false;
1561 }
1562 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1563 return false;
1564 }
1565 fx += (count - 1) * dx;
1566 fy += (count - 1) * dy;
1567 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1568 return false;
1569 }
1570 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1571}
1572
1573#define UNPINNED_RADIAL_STEP \
1574 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001575 *dstC++ = cache[toggle + \
1576 (sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift)]; \
1577 toggle ^= Gradient_Shader::kDitherStride32; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001578 fx += dx; \
1579 fy += dy;
1580
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001581typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1582 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001583 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001584 int count, int toggle);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001585
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001586// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001587void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
1588 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001589 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001590 int count, int toggle) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001591 // Floating point seems to be slower than fixed point,
1592 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001593 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001594 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1595 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1596 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1597 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001598 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001599 unsigned fi = Gradient_Shader::kGradient32Length;
1600 sk_memset32_dither(dstC,
1601 cache[toggle + fi],
1602 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
1603 count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001604 } else if ((count > 4) &&
1605 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1606 unsigned fi;
1607 // 4x unroll appears to be no faster than 2x unroll on Linux
1608 while (count > 1) {
1609 UNPINNED_RADIAL_STEP;
1610 UNPINNED_RADIAL_STEP;
1611 count -= 2;
1612 }
1613 if (count) {
1614 UNPINNED_RADIAL_STEP;
1615 }
1616 }
1617 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001618 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1619 if (dy == 0) {
1620 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1621 yy *= yy;
1622 do {
1623 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1624 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1625 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001626 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1627 Gradient_Shader::kSqrt32Shift)];
1628 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001629 fx += dx;
1630 } while (--count != 0);
1631 } else {
1632 do {
1633 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1634 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1635 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1636 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001637 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1638 Gradient_Shader::kSqrt32Shift)];
1639 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001640 fx += dx;
1641 fy += dy;
1642 } while (--count != 0);
1643 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001644 }
1645}
1646
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001647// Unrolling this loop doesn't seem to help (when float); we're stalling to
1648// get the results of the sqrt (?), and don't have enough extra registers to
1649// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001650void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1651 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001652 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001653 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001654 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001655#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001656 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1657 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001658#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001659 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1660 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001661 if (magnitudeSquared < 0) // Overflow.
1662 magnitudeSquared = SK_FixedMax;
1663 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001664#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001665 unsigned fi = mirror_tileproc(dist);
1666 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001667 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1668 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001669 sfx += sdx;
1670 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001671 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001672}
1673
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001674void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1675 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001676 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001677 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001678 SkFixed fx = SkScalarToFixed(sfx);
1679 SkFixed dx = SkScalarToFixed(sdx);
1680 SkFixed fy = SkScalarToFixed(sfy);
1681 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001682 do {
1683 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1684 SkFixedSquare(fy);
1685 if (magnitudeSquared < 0) // Overflow.
1686 magnitudeSquared = SK_FixedMax;
1687 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1688 unsigned fi = repeat_tileproc(dist);
1689 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001690 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1691 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001692 fx += dx;
1693 fy += dy;
1694 } while (--count != 0);
1695}
1696}
1697
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001698void Radial_Gradient::shadeSpan(int x, int y,
1699 SkPMColor* SK_RESTRICT dstC, int count) {
1700 SkASSERT(count > 0);
1701
1702 SkPoint srcPt;
1703 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1704 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001705 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001706#ifdef USE_DITHER_32BIT_GRADIENT
1707 int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32;
1708#else
1709 int toggle = 0;
1710#endif
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001711
1712 if (fDstToIndexClass != kPerspective_MatrixClass) {
1713 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1714 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001715 SkScalar sdx = fDstToIndex.getScaleX();
1716 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001717
1718 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1719 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001720 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1721 &storage[0], &storage[1]);
1722 sdx = SkFixedToScalar(storage[0]);
1723 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001724 } else {
1725 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001726 }
1727
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001728 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001729 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001730 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001731 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001732 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001733 } else {
1734 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001735 }
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001736 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001737 } else { // perspective case
1738 SkScalar dstX = SkIntToScalar(x);
1739 SkScalar dstY = SkIntToScalar(y);
1740 do {
1741 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1742 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1743 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001744 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001745 dstX += SK_Scalar1;
1746 } while (--count != 0);
1747 }
1748}
1749
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001750/* Two-point radial gradients are specified by two circles, each with a center
1751 point and radius. The gradient can be considered to be a series of
1752 concentric circles, with the color interpolated from the start circle
1753 (at t=0) to the end circle (at t=1).
1754
1755 For each point (x, y) in the span, we want to find the
1756 interpolated circle that intersects that point. The center
1757 of the desired circle (Cx, Cy) falls at some distance t
1758 along the line segment between the start point (Sx, Sy) and
1759 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001760
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001761 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1762 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001763
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001764 The radius of the desired circle (r) is also a linear interpolation t
1765 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001766
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001767 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001768
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001769 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001770
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001771 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001772
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001773 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001774
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001775 (x - ((1 - t) * Sx + t * Ex))^2
1776 + (y - ((1 - t) * Sy + t * Ey))^2
1777 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001778
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001779 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001780
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001781 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1782 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1783 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001784
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001785 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1786
1787 [Dx^2 + Dy^2 - Dr^2)] * t^2
1788 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1789 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001790
1791 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001792 possible circles on which the point may fall. Solving for t yields
1793 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001794
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001795 If a<0, the start circle is entirely contained in the
1796 end circle, and one of the roots will be <0 or >1 (off the line
1797 segment). If a>0, the start circle falls at least partially
1798 outside the end circle (or vice versa), and the gradient
1799 defines a "tube" where a point may be on one circle (on the
1800 inside of the tube) or the other (outside of the tube). We choose
1801 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001802
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001803 In order to keep the math to within the limits of fixed point,
1804 we divide the entire quadratic by Dr^2, and replace
1805 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001806
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001807 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1808 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1809 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001810
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001811 (x' and y' are computed by appending the subtract and scale to the
1812 fDstToIndex matrix in the constructor).
1813
1814 Since the 'A' component of the quadratic is independent of x' and y', it
1815 is precomputed in the constructor. Since the 'B' component is linear in
1816 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001817 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001818 a perspective projection), it must be computed in the loop.
1819
1820*/
1821
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001822namespace {
1823
1824inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1825 SkScalar sr2d2, SkScalar foura,
1826 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001827 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001828 if (0 == foura) {
1829 return SkScalarToFixed(SkScalarDiv(-c, b));
1830 }
1831
reed@google.com84e9c082011-04-13 17:44:24 +00001832 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001833 if (discrim < 0) {
1834 discrim = -discrim;
1835 }
reed@google.com84e9c082011-04-13 17:44:24 +00001836 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1837 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001838 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001839 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001840 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001841 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001842 }
reed@google.com84e9c082011-04-13 17:44:24 +00001843 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001844}
1845
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001846typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1847 SkScalar fy, SkScalar dy,
1848 SkScalar b, SkScalar db,
1849 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001850 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001851 int count);
1852
1853void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1854 SkScalar fy, SkScalar dy,
1855 SkScalar b, SkScalar db,
1856 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001857 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001858 int count) {
1859 for (; count > 0; --count) {
1860 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1861 fOneOverTwoA, posRoot);
1862 SkFixed index = SkClampMax(t, 0xFFFF);
1863 SkASSERT(index <= 0xFFFF);
1864 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1865 fx += dx;
1866 fy += dy;
1867 b += db;
1868 }
1869}
1870void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1871 SkScalar fy, SkScalar dy,
1872 SkScalar b, SkScalar db,
1873 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001874 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001875 int count) {
1876 for (; count > 0; --count) {
1877 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1878 fOneOverTwoA, posRoot);
1879 SkFixed index = mirror_tileproc(t);
1880 SkASSERT(index <= 0xFFFF);
1881 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1882 fx += dx;
1883 fy += dy;
1884 b += db;
1885 }
1886}
1887
1888void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1889 SkScalar fy, SkScalar dy,
1890 SkScalar b, SkScalar db,
1891 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001892 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001893 int count) {
1894 for (; count > 0; --count) {
1895 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1896 fOneOverTwoA, posRoot);
1897 SkFixed index = repeat_tileproc(t);
1898 SkASSERT(index <= 0xFFFF);
1899 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1900 fx += dx;
1901 fy += dy;
1902 b += db;
1903 }
1904}
1905
1906
1907
1908}
1909
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001910class Two_Point_Radial_Gradient : public Gradient_Shader {
1911public:
1912 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1913 const SkPoint& end, SkScalar endRadius,
1914 const SkColor colors[], const SkScalar pos[],
1915 int colorCount, SkShader::TileMode mode,
1916 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001917 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1918 fCenter1(start),
1919 fCenter2(end),
1920 fRadius1(startRadius),
1921 fRadius2(endRadius) {
1922 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001923 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001924
1925 virtual BitmapType asABitmap(SkBitmap* bitmap,
1926 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001927 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001928 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001929 if (bitmap) {
1930 this->commonAsABitmap(bitmap);
1931 }
1932 SkScalar diffL = 0; // just to avoid gcc warning
1933 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001934 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001935 SkScalarSquare(fDiff.fY));
1936 }
1937 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001938 if (diffL) {
1939 SkScalar invDiffL = SkScalarInvert(diffL);
1940 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1941 SkScalarMul(invDiffL, fDiff.fX));
1942 } else {
1943 matrix->reset();
1944 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001945 matrix->preConcat(fPtsToUnit);
1946 }
1947 if (xy) {
1948 xy[0] = fTileMode;
1949 xy[1] = kClamp_TileMode;
1950 }
1951 if (NULL != twoPointRadialParams) {
1952 twoPointRadialParams[0] = diffL;
1953 twoPointRadialParams[1] = fStartRadius;
1954 twoPointRadialParams[2] = fDiffRadius;
1955 }
1956 return kTwoPointRadial_BitmapType;
1957 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001958
reed@google.com8e6d9142011-12-07 15:30:34 +00001959 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001960 if (info) {
1961 commonAsAGradient(info);
1962 info->fPoint[0] = fCenter1;
1963 info->fPoint[1] = fCenter2;
1964 info->fRadius[0] = fRadius1;
1965 info->fRadius[1] = fRadius2;
1966 }
1967 return kRadial2_GradientType;
1968 }
1969
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001970 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1971 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001972 SkASSERT(count > 0);
1973
1974 // Zero difference between radii: fill with transparent black.
1975 if (fDiffRadius == 0) {
1976 sk_bzero(dstC, count * sizeof(*dstC));
1977 return;
1978 }
1979 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1980 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001981 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001982
1983 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001984 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001985 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001986 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001987 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1988 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001989 SkScalar dx, fx = srcPt.fX;
1990 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001991
reed@google.com61eb0402011-04-15 12:11:12 +00001992 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001993 SkFixed fixedX, fixedY;
1994 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1995 dx = SkFixedToScalar(fixedX);
1996 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001997 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001998 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001999 dx = fDstToIndex.getScaleX();
2000 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002001 }
reed@google.com84e9c082011-04-13 17:44:24 +00002002 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
2003 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
2004 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
2005 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002006
2007 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00002008 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002009 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00002010 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002011 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00002012 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002013 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002014 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002015 (*shadeProc)(fx, dx, fy, dy, b, db,
2016 fSr2D2, foura, fOneOverTwoA, posRoot,
2017 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00002018 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00002019 SkScalar dstX = SkIntToScalar(x);
2020 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002021 for (; count > 0; --count) {
2022 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00002023 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00002024 SkScalar fx = srcPt.fX;
2025 SkScalar fy = srcPt.fY;
2026 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
2027 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002028 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
2029 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002030 SkFixed index = proc(t);
2031 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002032 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00002033 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002034 }
2035 }
2036 }
2037
reed@android.com6c59a172009-09-22 20:24:05 +00002038 virtual bool setContext(const SkBitmap& device,
2039 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00002040 const SkMatrix& matrix) SK_OVERRIDE {
2041 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00002042 return false;
2043 }
2044
2045 // we don't have a span16 proc
2046 fFlags &= ~kHasSpan16_Flag;
2047 return true;
2048 }
2049
reed@google.com8e6d9142011-12-07 15:30:34 +00002050 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002051 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
2052 }
2053
reed@google.com7716afb2011-12-07 15:17:50 +00002054 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00002055 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002056 buffer.writeScalar(fCenter1.fX);
2057 buffer.writeScalar(fCenter1.fY);
2058 buffer.writeScalar(fCenter2.fX);
2059 buffer.writeScalar(fCenter2.fY);
2060 buffer.writeScalar(fRadius1);
2061 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002062 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002063
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002064protected:
reed@android.combcfc7332009-11-10 16:19:39 +00002065 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002066 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002067 fCenter1(unflatten_point(buffer)),
2068 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002069 fRadius1(buffer.readScalar()),
2070 fRadius2(buffer.readScalar()) {
2071 init();
reed@android.combcfc7332009-11-10 16:19:39 +00002072 };
reed@google.com7716afb2011-12-07 15:17:50 +00002073 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002074
2075private:
2076 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002077 const SkPoint fCenter1;
2078 const SkPoint fCenter2;
2079 const SkScalar fRadius1;
2080 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002081 SkPoint fDiff;
2082 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002083
2084 void init() {
2085 fDiff = fCenter1 - fCenter2;
2086 fDiffRadius = fRadius2 - fRadius1;
2087 SkScalar inv = SkScalarInvert(fDiffRadius);
2088 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2089 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2090 fStartRadius = SkScalarMul(fRadius1, inv);
2091 fSr2D2 = SkScalarSquare(fStartRadius);
2092 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002093 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002094
2095 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2096 fPtsToUnit.postScale(inv, inv);
2097 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002098};
2099
reed@android.com8a1c16f2008-12-17 15:59:43 +00002100///////////////////////////////////////////////////////////////////////////////
2101
2102class Sweep_Gradient : public Gradient_Shader {
2103public:
2104 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2105 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002106 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2107 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002108 {
2109 fPtsToUnit.setTranslate(-cx, -cy);
2110 }
reed@google.com7716afb2011-12-07 15:17:50 +00002111 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2112 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002113
2114 virtual BitmapType asABitmap(SkBitmap* bitmap,
2115 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002116 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002117 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002118 if (bitmap) {
2119 this->commonAsABitmap(bitmap);
2120 }
2121 if (matrix) {
2122 *matrix = fPtsToUnit;
2123 }
2124 if (xy) {
2125 xy[0] = fTileMode;
2126 xy[1] = kClamp_TileMode;
2127 }
2128 return kSweep_BitmapType;
2129 }
2130
reed@google.com7716afb2011-12-07 15:17:50 +00002131 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002132 if (info) {
2133 commonAsAGradient(info);
2134 info->fPoint[0] = fCenter;
2135 }
2136 return kSweep_GradientType;
2137 }
2138
reed@google.com8e6d9142011-12-07 15:30:34 +00002139 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002140 return SkNEW_ARGS(Sweep_Gradient, (buffer));
2141 }
2142
reed@google.com7716afb2011-12-07 15:17:50 +00002143 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002144 this->INHERITED::flatten(buffer);
2145 buffer.writeScalar(fCenter.fX);
2146 buffer.writeScalar(fCenter.fY);
2147 }
2148
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002150 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
2151 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002152 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002153 }
2154
reed@google.com7716afb2011-12-07 15:17:50 +00002155 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156
2157private:
2158 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002159 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160};
2161
2162#ifdef COMPUTE_SWEEP_TABLE
2163#define PI 3.14159265
2164static bool gSweepTableReady;
2165static uint8_t gSweepTable[65];
2166
2167/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2168 We scale the results to [0..32]
2169*/
reed@google.com61eb0402011-04-15 12:11:12 +00002170static const uint8_t* build_sweep_table() {
2171 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002172 const int N = 65;
2173 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002174
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175 for (int i = 0; i < N; i++)
2176 {
2177 double arg = i / DENOM;
2178 double v = atan(arg);
2179 int iv = (int)round(v * DENOM * 2 / PI);
2180// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2181 printf("%d, ", iv);
2182 gSweepTable[i] = iv;
2183 }
2184 gSweepTableReady = true;
2185 }
2186 return gSweepTable;
2187}
2188#else
2189static const uint8_t gSweepTable[] = {
2190 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2191 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2192 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2193 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2194 32
2195};
2196static const uint8_t* build_sweep_table() { return gSweepTable; }
2197#endif
2198
2199// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2200// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2201// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2202
2203//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002204static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002205 SkASSERT(numer <= denom);
2206 SkASSERT(numer > 0);
2207 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002208
reed@android.com8a1c16f2008-12-17 15:59:43 +00002209 int nbits = SkCLZ(numer);
2210 int dbits = SkCLZ(denom);
2211 int bits = 6 - nbits + dbits;
2212 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002213
reed@google.com61eb0402011-04-15 12:11:12 +00002214 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002216 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002217
2218 denom <<= dbits - 1;
2219 numer <<= nbits - 1;
2220
2221 unsigned result = 0;
2222
2223 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002224 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002225 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002226 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002227 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002228 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002229
reed@android.com8a1c16f2008-12-17 15:59:43 +00002230 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002231 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002232 // make room for the rest of the answer bits
2233 result <<= bits;
2234 switch (bits) {
2235 case 6:
2236 if ((numer = (numer << 1) - denom) >= 0)
2237 result |= 32;
2238 else
2239 numer += denom;
2240 case 5:
2241 if ((numer = (numer << 1) - denom) >= 0)
2242 result |= 16;
2243 else
2244 numer += denom;
2245 case 4:
2246 if ((numer = (numer << 1) - denom) >= 0)
2247 result |= 8;
2248 else
2249 numer += denom;
2250 case 3:
2251 if ((numer = (numer << 1) - denom) >= 0)
2252 result |= 4;
2253 else
2254 numer += denom;
2255 case 2:
2256 if ((numer = (numer << 1) - denom) >= 0)
2257 result |= 2;
2258 else
2259 numer += denom;
2260 case 1:
2261 default: // not strictly need, but makes GCC make better ARM code
2262 if ((numer = (numer << 1) - denom) >= 0)
2263 result |= 1;
2264 else
2265 numer += denom;
2266 }
2267 }
2268 return result;
2269}
2270
2271// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002272static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002273#ifdef SK_DEBUG
2274 {
2275 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002276 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002277 gOnce = true;
2278 SkASSERT(div_64(55, 55) == 64);
2279 SkASSERT(div_64(128, 256) == 32);
2280 SkASSERT(div_64(2326528, 4685824) == 31);
2281 SkASSERT(div_64(753664, 5210112) == 9);
2282 SkASSERT(div_64(229376, 4882432) == 3);
2283 SkASSERT(div_64(2, 64) == 2);
2284 SkASSERT(div_64(1, 64) == 1);
2285 // test that we handle underflow correctly
2286 SkASSERT(div_64(12345, 0x54321234) == 0);
2287 }
2288 }
2289#endif
2290
2291 SkASSERT(y > 0 && x > 0);
2292 const uint8_t* table = build_sweep_table();
2293
2294 unsigned result;
2295 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002296 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002297 // first part of the atan(v) = PI/2 - atan(1/v) identity
2298 // since our div_64 and table want v <= 1, where v = y/x
2299 SkTSwap<SkFixed>(x, y);
2300 }
2301
2302 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002303
reed@android.com8a1c16f2008-12-17 15:59:43 +00002304#ifdef SK_DEBUG
2305 {
2306 unsigned result2 = SkDivBits(y, x, 6);
2307 SkASSERT(result2 == result ||
2308 (result == 1 && result2 == 0));
2309 }
2310#endif
2311
2312 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2313 result = table[result];
2314
reed@google.com61eb0402011-04-15 12:11:12 +00002315 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002316 // complete the atan(v) = PI/2 - atan(1/v) identity
2317 result = 64 - result;
2318 // pin to 63
2319 result -= result >> 6;
2320 }
2321
2322 SkASSERT(result <= 63);
2323 return result;
2324}
2325
2326// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002327#ifdef SK_SCALAR_IS_FLOAT
2328static unsigned SkATan2_255(float y, float x) {
2329 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2330 static const float g255Over2PI = 40.584510488433314f;
2331
2332 float result = sk_float_atan2(y, x);
2333 if (result < 0) {
2334 result += 2 * SK_ScalarPI;
2335 }
2336 SkASSERT(result >= 0);
2337 // since our value is always >= 0, we can cast to int, which is faster than
2338 // calling floorf()
2339 int ir = (int)(result * g255Over2PI);
2340 SkASSERT(ir >= 0 && ir <= 255);
2341 return ir;
2342}
2343#else
reed@google.com61eb0402011-04-15 12:11:12 +00002344static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2345 if (x == 0) {
2346 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002347 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002348 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002349 return y < 0 ? 192 : 64;
2350 }
reed@google.com61eb0402011-04-15 12:11:12 +00002351 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002352 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002353 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002354
reed@android.com8a1c16f2008-12-17 15:59:43 +00002355 /* Find the right quadrant for x,y
2356 Since atan_0_90 only handles the first quadrant, we rotate x,y
2357 appropriately before calling it, and then add the right amount
2358 to account for the real quadrant.
2359 quadrant 0 : add 0 | x > 0 && y > 0
2360 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2361 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2362 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002363
reed@android.com8a1c16f2008-12-17 15:59:43 +00002364 map x<0 to (1 << 6)
2365 map y<0 to (3 << 6)
2366 add = map_x ^ map_y
2367 */
2368 int xsign = x >> 31;
2369 int ysign = y >> 31;
2370 int add = ((-xsign) ^ (ysign & 3)) << 6;
2371
2372#ifdef SK_DEBUG
2373 if (0 == add)
2374 SkASSERT(x > 0 && y > 0);
2375 else if (64 == add)
2376 SkASSERT(x < 0 && y > 0);
2377 else if (128 == add)
2378 SkASSERT(x < 0 && y < 0);
2379 else if (192 == add)
2380 SkASSERT(x > 0 && y < 0);
2381 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002382 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002383#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002384
reed@android.com8a1c16f2008-12-17 15:59:43 +00002385 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2386 where we need to rotate x,y by 90 or -90
2387 */
2388 x = (x ^ xsign) - xsign;
2389 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002390 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002391 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002392 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002393
2394 unsigned result = add + atan_0_90(y, x);
2395 SkASSERT(result < 256);
2396 return result;
2397}
reed@google.com51baf5a2011-09-21 13:38:36 +00002398#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002399
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002400void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2401 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002402 SkMatrix::MapXYProc proc = fDstToIndexProc;
2403 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002404 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002405 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002406
reed@google.com61eb0402011-04-15 12:11:12 +00002407 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002408 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2409 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002410 SkScalar dx, fx = srcPt.fX;
2411 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002412
reed@google.com61eb0402011-04-15 12:11:12 +00002413 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002414 SkFixed storage[2];
2415 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2416 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002417 dx = SkFixedToScalar(storage[0]);
2418 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002419 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002420 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002421 dx = matrix.getScaleX();
2422 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002423 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002424
reed@google.com61eb0402011-04-15 12:11:12 +00002425 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002426 *dstC++ = cache[SkATan2_255(fy, fx)];
2427 fx += dx;
2428 fy += dy;
2429 }
reed@google.com61eb0402011-04-15 12:11:12 +00002430 } else { // perspective case
2431 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002432 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002433 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2434 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002435 }
2436 }
2437}
2438
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002439void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2440 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002441 SkMatrix::MapXYProc proc = fDstToIndexProc;
2442 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002443 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002444 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002445 SkPoint srcPt;
2446
reed@google.com61eb0402011-04-15 12:11:12 +00002447 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002448 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2449 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002450 SkScalar dx, fx = srcPt.fX;
2451 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002452
reed@google.com61eb0402011-04-15 12:11:12 +00002453 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002454 SkFixed storage[2];
2455 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2456 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002457 dx = SkFixedToScalar(storage[0]);
2458 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002459 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002460 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002461 dx = matrix.getScaleX();
2462 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002463 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002464
reed@google.com61eb0402011-04-15 12:11:12 +00002465 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002466 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2467 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002468 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002469 fx += dx;
2470 fy += dy;
2471 }
reed@google.com61eb0402011-04-15 12:11:12 +00002472 } else { // perspective case
2473 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002474 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2475 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002476
reed@google.com51baf5a2011-09-21 13:38:36 +00002477 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002478 index >>= (8 - kCache16Bits);
2479 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002480 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002481 }
2482 }
2483}
2484
reed@google.com61eb0402011-04-15 12:11:12 +00002485///////////////////////////////////////////////////////////////////////////////
2486///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002487
2488// assumes colors is SkColor* and pos is SkScalar*
2489#define EXPAND_1_COLOR(count) \
2490 SkColor tmp[2]; \
2491 do { \
2492 if (1 == count) { \
2493 tmp[0] = tmp[1] = colors[0]; \
2494 colors = tmp; \
2495 pos = NULL; \
2496 count = 2; \
2497 } \
2498 } while (0)
2499
reed@google.com61eb0402011-04-15 12:11:12 +00002500SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2501 const SkColor colors[],
2502 const SkScalar pos[], int colorCount,
2503 SkShader::TileMode mode,
2504 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002505 if (NULL == pts || NULL == colors || colorCount < 1) {
2506 return NULL;
2507 }
2508 EXPAND_1_COLOR(colorCount);
2509
reed@android.comab840b82009-07-01 17:00:03 +00002510 return SkNEW_ARGS(Linear_Gradient,
2511 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002512}
2513
reed@google.com61eb0402011-04-15 12:11:12 +00002514SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2515 const SkColor colors[],
2516 const SkScalar pos[], int colorCount,
2517 SkShader::TileMode mode,
2518 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002519 if (radius <= 0 || NULL == colors || colorCount < 1) {
2520 return NULL;
2521 }
2522 EXPAND_1_COLOR(colorCount);
2523
reed@android.comab840b82009-07-01 17:00:03 +00002524 return SkNEW_ARGS(Radial_Gradient,
2525 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002526}
2527
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002528SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2529 SkScalar startRadius,
2530 const SkPoint& end,
2531 SkScalar endRadius,
2532 const SkColor colors[],
2533 const SkScalar pos[],
2534 int colorCount,
2535 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002536 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002537 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2538 return NULL;
2539 }
2540 EXPAND_1_COLOR(colorCount);
2541
2542 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002543 (start, startRadius, end, endRadius, colors, pos,
2544 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002545}
2546
reed@android.com8a1c16f2008-12-17 15:59:43 +00002547SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2548 const SkColor colors[],
2549 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002550 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002551 if (NULL == colors || count < 1) {
2552 return NULL;
2553 }
2554 EXPAND_1_COLOR(count);
2555
2556 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2557}
2558
caryclark@google.comd26147a2011-12-15 14:16:43 +00002559SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2560 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2561 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002562
caryclark@google.comd26147a2011-12-15 14:16:43 +00002563 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002564
caryclark@google.comd26147a2011-12-15 14:16:43 +00002565 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2566SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END