blob: a93bd52dbb00e5271dac88c04c3badcd02cb526d [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& );
djsollen@google.com54924242012-03-29 15:18:04 +0000177 virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
178
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179 SkUnitMapper* fMapper;
180 SkMatrix fPtsToUnit; // set by subclass
181 SkMatrix fDstToIndex;
182 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183 TileMode fTileMode;
184 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000185 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186 uint8_t fDstToIndexClass;
187 uint8_t fFlags;
188
189 struct Rec {
190 SkFixed fPos; // 0...1
191 uint32_t fScale; // (1 << 24) / range
192 };
193 Rec* fRecs;
194
reed@google.com7c2f27d2011-03-07 19:29:00 +0000195 const uint16_t* getCache16() const;
196 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000197
reed@google.com7c2f27d2011-03-07 19:29:00 +0000198 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000199 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000200
reed@android.com8a1c16f2008-12-17 15:59:43 +0000201private:
202 enum {
203 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
204
reed@android.com1c12abe2009-07-02 15:01:02 +0000205 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000206 };
207 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000208 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
209 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000210
reed@google.com7c2f27d2011-03-07 19:29:00 +0000211 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
212 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000213
reed@google.com7c2f27d2011-03-07 19:29:00 +0000214 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
215 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000216 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 +0000217
reed@android.com512a8762009-12-14 15:25:36 +0000218 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000219 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
220 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000221 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000222 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000223
reed@android.com8a1c16f2008-12-17 15:59:43 +0000224 typedef SkShader INHERITED;
225};
226
reed@android.com41bccf52009-04-03 13:33:51 +0000227static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000228 SkASSERT(x >= 0 && x <= SK_Scalar1);
229
230#ifdef SK_SCALAR_IS_FLOAT
231 return (unsigned)(x * 0xFFFF);
232#else
233 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
234#endif
235}
236
reed@android.com41bccf52009-04-03 13:33:51 +0000237Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
238 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000239 SkASSERT(colorCount > 1);
240
241 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
242
243 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000244 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
247 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
248 fTileMode = mode;
249 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000250
reed@android.com41bccf52009-04-03 13:33:51 +0000251 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000252 fCache32 = NULL;
253 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000254
reed@android.com41bccf52009-04-03 13:33:51 +0000255 /* Note: we let the caller skip the first and/or last position.
256 i.e. pos[0] = 0.3, pos[1] = 0.7
257 In these cases, we insert dummy entries to ensure that the final data
258 will be bracketed by [0, 1].
259 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
260
261 Thus colorCount (the caller's value, and fColorCount (our value) may
262 differ by up to 2. In the above example:
263 colorCount = 2
264 fColorCount = 4
265 */
266 fColorCount = colorCount;
267 // check if we need to add in dummy start and/or end position/colors
268 bool dummyFirst = false;
269 bool dummyLast = false;
270 if (pos) {
271 dummyFirst = pos[0] != 0;
272 dummyLast = pos[colorCount - 1] != SK_Scalar1;
273 fColorCount += dummyFirst + dummyLast;
274 }
275
276 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000277 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000278 fOrigColors = reinterpret_cast<SkColor*>(
279 sk_malloc_throw(size * fColorCount));
280 }
281 else {
282 fOrigColors = fStorage;
283 }
284
285 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 {
reed@android.com41bccf52009-04-03 13:33:51 +0000287 SkColor* origColors = fOrigColors;
288 if (dummyFirst) {
289 *origColors++ = colors[0];
290 }
291 memcpy(origColors, colors, colorCount * sizeof(SkColor));
292 if (dummyLast) {
293 origColors += colorCount;
294 *origColors = colors[colorCount - 1];
295 }
296 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297
reed@android.com1c12abe2009-07-02 15:01:02 +0000298 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000299 if (fColorCount > 2) {
300 Rec* recs = fRecs;
301 recs->fPos = 0;
302 // recs->fScale = 0; // unused;
303 recs += 1;
304 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305 /* We need to convert the user's array of relative positions into
306 fixed-point positions and scale factors. We need these results
307 to be strictly monotonic (no two values equal or out of order).
308 Hence this complex loop that just jams a zero for the scale
309 value if it sees a segment out of order, and it assures that
310 we start at 0 and end at 1.0
311 */
312 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000313 int startIndex = dummyFirst ? 0 : 1;
314 int count = colorCount + dummyLast;
315 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000316 // force the last value to be 1.0
317 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000318 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000320 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 }
reed@android.com41bccf52009-04-03 13:33:51 +0000323 // pin curr withing range
324 if (curr < 0) {
325 curr = 0;
326 } else if (curr > SK_Fixed1) {
327 curr = SK_Fixed1;
328 }
329 recs->fPos = curr;
330 if (curr > prev) {
331 recs->fScale = (1 << 24) / (curr - prev);
332 } else {
333 recs->fScale = 0; // ignore this segment
334 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335 // get ready for the next value
336 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000337 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338 }
reed@android.com41bccf52009-04-03 13:33:51 +0000339 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340 SkFixed dp = SK_Fixed1 / (colorCount - 1);
341 SkFixed p = dp;
342 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000343 for (int i = 1; i < colorCount; i++) {
344 recs->fPos = p;
345 recs->fScale = scale;
346 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 p += dp;
348 }
349 }
350 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000351 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352}
353
354Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000355 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356 fCacheAlpha = 256;
357
358 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
359
360 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000361 fCache32 = NULL;
362 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363
reed@android.com41bccf52009-04-03 13:33:51 +0000364 int colorCount = fColorCount = buffer.readU32();
365 if (colorCount > kColorStorageCount) {
366 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
367 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
368 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000370 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372
373 fTileMode = (TileMode)buffer.readU8();
374 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000375 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000376 if (colorCount > 2) {
377 Rec* recs = fRecs;
378 recs[0].fPos = 0;
379 for (int i = 1; i < colorCount; i++) {
380 recs[i].fPos = buffer.readS32();
381 recs[i].fScale = buffer.readU32();
382 }
383 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000384 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000385 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000386}
387
reed@android.com41bccf52009-04-03 13:33:51 +0000388Gradient_Shader::~Gradient_Shader() {
389 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000390 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000391 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000392 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000393 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000394 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000395 }
reed@google.com82065d62011-02-07 15:30:46 +0000396 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397}
398
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000399void Gradient_Shader::initCommon() {
400 fFlags = 0;
401 unsigned colorAlpha = 0xFF;
402 for (int i = 0; i < fColorCount; i++) {
403 colorAlpha &= SkColorGetA(fOrigColors[i]);
404 }
405 fColorsAreOpaque = colorAlpha == 0xFF;
406}
407
djsollen@google.com54924242012-03-29 15:18:04 +0000408void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000409 this->INHERITED::flatten(buffer);
410 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000411 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
413 buffer.write8(fTileMode);
414 if (fColorCount > 2) {
415 Rec* recs = fRecs;
416 for (int i = 1; i < fColorCount; i++) {
417 buffer.write32(recs[i].fPos);
418 buffer.write32(recs[i].fScale);
419 }
420 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000421 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000422}
423
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000424bool Gradient_Shader::isOpaque() const {
425 return fColorsAreOpaque;
426}
427
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428bool Gradient_Shader::setContext(const SkBitmap& device,
429 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000430 const SkMatrix& matrix) {
431 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000433 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434
435 const SkMatrix& inverse = this->getTotalInverse();
436
437 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
438 return false;
439 }
440
441 fDstToIndexProc = fDstToIndex.getMapXYProc();
442 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
443
444 // now convert our colors in to PMColors
445 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000446
447 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000448 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449 fFlags |= kOpaqueAlpha_Flag;
450 }
451 // we can do span16 as long as our individual colors are opaque,
452 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000453 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000454 fFlags |= kHasSpan16_Flag;
455 }
456
reed@google.com95eed982011-07-05 17:01:56 +0000457 this->setCacheAlpha(paintAlpha);
458 return true;
459}
460
461void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000462 // if the new alpha differs from the previous time we were called, inval our cache
463 // this will trigger the cache to be rebuilt.
464 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000465 if (fCacheAlpha != alpha) {
466 fCache16 = NULL; // inval the cache
467 fCache32 = NULL; // inval the cache
468 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000469 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000470 if (fCache32PixelRef) {
471 fCache32PixelRef->notifyPixelsChanged();
472 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000473 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000474}
475
reed@android.com41bccf52009-04-03 13:33:51 +0000476static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000477 SkASSERT(a == SkToU8(a));
478 SkASSERT(b == SkToU8(b));
479 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000480 return a + ((b - a) * scale >> 8);
481}
482
reed@android.com41bccf52009-04-03 13:33:51 +0000483static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
484 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000485#if 0
486 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
487 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
488 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
489 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
490
491 return SkPackARGB32(a, r, g, b);
492#else
493 int otherBlend = 256 - blend;
494
495#if 0
496 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
497 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
498 SkASSERT((t0 & t1) == 0);
499 return t0 | t1;
500#else
501 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
502 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
503#endif
504
505#endif
506}
507
508#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
509
reed@android.com41bccf52009-04-03 13:33:51 +0000510/** We take the original colors, not our premultiplied PMColors, since we can
511 build a 16bit table as long as the original colors are opaque, even if the
512 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000513*/
reed@android.com512a8762009-12-14 15:25:36 +0000514void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
515 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516 SkASSERT(count > 1);
517 SkASSERT(SkColorGetA(c0) == 0xFF);
518 SkASSERT(SkColorGetA(c1) == 0xFF);
519
520 SkFixed r = SkColorGetR(c0);
521 SkFixed g = SkColorGetG(c0);
522 SkFixed b = SkColorGetB(c0);
523
524 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
525 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
526 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
527
528 r = SkIntToFixed(r) + 0x8000;
529 g = SkIntToFixed(g) + 0x8000;
530 b = SkIntToFixed(b) + 0x8000;
531
532 do {
533 unsigned rr = r >> 16;
534 unsigned gg = g >> 16;
535 unsigned bb = b >> 16;
536 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000537 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000538 cache += 1;
539 r += dr;
540 g += dg;
541 b += db;
542 } while (--count != 0);
543}
544
reed@google.com55b8e8c2011-01-13 16:22:35 +0000545/*
546 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
547 * semantics of how we 2x2 dither 32->16
548 */
549static inline U8CPU dither_fixed_to_8(SkFixed n) {
550 n >>= 8;
551 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
552}
553
554/*
555 * For dithering with premultiply, we want to ceiling the alpha component,
556 * to ensure that it is always >= any color component.
557 */
558static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
559 n >>= 8;
560 return ((n << 1) - (n | (n >> 8))) >> 8;
561}
562
563void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
564 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000565 SkASSERT(count > 1);
566
reed@android.com1c12abe2009-07-02 15:01:02 +0000567 // need to apply paintAlpha to our two endpoints
568 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
569 SkFixed da;
570 {
571 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
572 da = SkIntToFixed(tmp - a) / (count - 1);
573 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000574
reed@android.com1c12abe2009-07-02 15:01:02 +0000575 SkFixed r = SkColorGetR(c0);
576 SkFixed g = SkColorGetG(c0);
577 SkFixed b = SkColorGetB(c0);
578 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
579 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
580 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581
582 a = SkIntToFixed(a) + 0x8000;
583 r = SkIntToFixed(r) + 0x8000;
584 g = SkIntToFixed(g) + 0x8000;
585 b = SkIntToFixed(b) + 0x8000;
586
587 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000588 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000589 cache[kCache32Count] =
590 SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
591 dither_fixed_to_8(r),
592 dither_fixed_to_8(g),
593 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000594 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000595 a += da;
596 r += dr;
597 g += dg;
598 b += db;
599 } while (--count != 0);
600}
601
reed@android.com41bccf52009-04-03 13:33:51 +0000602static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603 SkASSERT((unsigned)x <= SK_Fixed1);
604 return x - (x >> 16);
605}
606
reed@android.com200645d2009-12-14 16:41:57 +0000607static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000608 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000609 if (6 == bits) {
610 return (x << 10) | (x << 4) | (x >> 2);
611 }
612 if (8 == bits) {
613 return (x << 8) | x;
614 }
615 sk_throw();
616 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617}
618
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000619/** We duplicate the last value in each half of the cache so that
620 interpolation doesn't have to special-case being at the last point.
621*/
622static void complete_16bit_cache(uint16_t* cache, int stride) {
623 cache[stride - 1] = cache[stride - 2];
624 cache[2 * stride - 1] = cache[2 * stride - 2];
625}
626
reed@google.com7c2f27d2011-03-07 19:29:00 +0000627const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000628 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000629 // double the count for dither entries
630 const int entryCount = kCache16Count * 2;
631 const size_t allocSize = sizeof(uint16_t) * entryCount;
632
reed@android.com3c9b2a42009-08-27 19:28:37 +0000633 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000634 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000635 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000637 if (fColorCount == 2) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000638 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
639 kGradient16Length);
reed@android.com41bccf52009-04-03 13:33:51 +0000640 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641 Rec* rec = fRecs;
642 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000643 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000644 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000645 SkASSERT(nextIndex < kCache16Count);
646
647 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000648 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000649 prevIndex = nextIndex;
650 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000651 // one extra space left over at the end for complete_16bit_cache()
652 SkASSERT(prevIndex == kGradient16Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 }
654
reed@android.com41bccf52009-04-03 13:33:51 +0000655 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000656 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657 uint16_t* linear = fCache16; // just computed linear data
658 uint16_t* mapped = fCache16Storage; // storage for mapped data
659 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000660 for (int i = 0; i < kGradient16Length; i++) {
reed@android.com200645d2009-12-14 16:41:57 +0000661 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000663 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 }
665 sk_free(fCache16);
666 fCache16 = fCache16Storage;
667 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000668 complete_16bit_cache(fCache16, kCache16Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000669 }
670 return fCache16;
671}
672
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000673/** We duplicate the last value in each half of the cache so that
674 interpolation doesn't have to special-case being at the last point.
675*/
676static void complete_32bit_cache(SkPMColor* cache, int stride) {
677 cache[stride - 1] = cache[stride - 2];
678 cache[2 * stride - 1] = cache[2 * stride - 2];
679}
680
reed@google.com7c2f27d2011-03-07 19:29:00 +0000681const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000682 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000683 // double the count for dither entries
684 const int entryCount = kCache32Count * 2;
685 const size_t allocSize = sizeof(SkPMColor) * entryCount;
686
reed@google.comdc731fd2010-12-23 15:19:47 +0000687 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000688 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
689 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000690 }
691 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000692 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000693 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000694 kGradient32Length, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000695 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 Rec* rec = fRecs;
697 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000698 for (int i = 1; i < fColorCount; i++) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000699 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000700 SkASSERT(nextIndex < kGradient32Length);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701
702 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000703 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
704 fOrigColors[i],
705 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706 prevIndex = nextIndex;
707 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000708 SkASSERT(prevIndex == kGradient32Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709 }
710
reed@android.com41bccf52009-04-03 13:33:51 +0000711 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000712 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000713 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000715 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000717 for (int i = 0; i < kGradient32Length; i++) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000718 int index = map->mapUnit16((i << 8) | i) >> 8;
719 mapped[i] = linear[index];
720 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000721 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000722 fCache32PixelRef->unref();
723 fCache32PixelRef = newPR;
724 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000726 complete_32bit_cache(fCache32, kCache32Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727 }
728 return fCache32;
729}
730
reed@google.comdc731fd2010-12-23 15:19:47 +0000731/*
732 * Because our caller might rebuild the same (logically the same) gradient
733 * over and over, we'd like to return exactly the same "bitmap" if possible,
734 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
735 * To do that, we maintain a private cache of built-bitmaps, based on our
736 * colors and positions. Note: we don't try to flatten the fMapper, so if one
737 * is present, we skip the cache for now.
738 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000739void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000740 // our caller assumes no external alpha, so we ensure that our cache is
741 // built with 0xFF
742 this->setCacheAlpha(0xFF);
743
reed@google.comdc731fd2010-12-23 15:19:47 +0000744 // don't have a way to put the mapper into our cache-key yet
745 if (fMapper) {
746 // force our cahce32pixelref to be built
747 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000748 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000749 bitmap->setPixelRef(fCache32PixelRef);
750 return;
751 }
752
753 // build our key: [numColors + colors[] + {positions[]} ]
754 int count = 1 + fColorCount;
755 if (fColorCount > 2) {
756 count += fColorCount - 1; // fRecs[].fPos
757 }
758
759 SkAutoSTMalloc<16, int32_t> storage(count);
760 int32_t* buffer = storage.get();
761
762 *buffer++ = fColorCount;
763 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
764 buffer += fColorCount;
765 if (fColorCount > 2) {
766 for (int i = 1; i < fColorCount; i++) {
767 *buffer++ = fRecs[i].fPos;
768 }
769 }
770 SkASSERT(buffer - storage.get() == count);
771
772 ///////////////////////////////////
773
digit@google.com1771cbf2012-01-26 21:26:40 +0000774 SK_DECLARE_STATIC_MUTEX(gMutex);
reed@google.comdc731fd2010-12-23 15:19:47 +0000775 static SkBitmapCache* gCache;
776 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
777 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
778 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000779
reed@google.comdc731fd2010-12-23 15:19:47 +0000780 if (NULL == gCache) {
781 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
782 }
783 size_t size = count * sizeof(int32_t);
784
785 if (!gCache->find(storage.get(), size, bitmap)) {
786 // force our cahce32pixelref to be built
787 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000788 // Only expose the linear section of the cache; don't let the caller
789 // know about the padding at the end to make interpolation faster.
790 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000791 bitmap->setPixelRef(fCache32PixelRef);
792
793 gCache->add(storage.get(), size, *bitmap);
794 }
795}
796
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000797void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
798 if (info) {
799 if (info->fColorCount >= fColorCount) {
800 if (info->fColors) {
801 memcpy(info->fColors, fOrigColors,
802 fColorCount * sizeof(SkColor));
803 }
804 if (info->fColorOffsets) {
805 if (fColorCount == 2) {
806 info->fColorOffsets[0] = 0;
807 info->fColorOffsets[1] = SK_Scalar1;
808 } else if (fColorCount > 2) {
809 for (int i = 0; i < fColorCount; i++)
810 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
811 }
812 }
813 }
814 info->fColorCount = fColorCount;
815 info->fTileMode = fTileMode;
816 }
817}
818
reed@google.com61eb0402011-04-15 12:11:12 +0000819///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820
reed@android.com41bccf52009-04-03 13:33:51 +0000821static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 SkVector vec = pts[1] - pts[0];
823 SkScalar mag = vec.length();
824 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
825
826 vec.scale(inv);
827 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
828 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
829 matrix->postScale(inv, inv);
830}
831
832///////////////////////////////////////////////////////////////////////////////
833
834class Linear_Gradient : public Gradient_Shader {
835public:
836 Linear_Gradient(const SkPoint pts[2],
837 const SkColor colors[], const SkScalar pos[], int colorCount,
838 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000839 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
840 fStart(pts[0]),
841 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842 {
843 pts_to_unit_matrix(pts, &fPtsToUnit);
844 }
reed@android.com9b46e772009-06-05 12:24:41 +0000845
reed@google.com7716afb2011-12-07 15:17:50 +0000846 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
847 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
848 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
849 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
850 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
851 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852
djsollen@google.com54924242012-03-29 15:18:04 +0000853 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Linear_Gradient)
854
855protected:
856 Linear_Gradient(SkFlattenableReadBuffer& buffer)
857 : INHERITED(buffer),
858 fStart(unflatten_point(buffer)),
859 fEnd(unflatten_point(buffer)) {
860 }
861 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000862 this->INHERITED::flatten(buffer);
863 buffer.writeScalar(fStart.fX);
864 buffer.writeScalar(fStart.fY);
865 buffer.writeScalar(fEnd.fX);
866 buffer.writeScalar(fEnd.fY);
867 }
868
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869private:
870 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000871 const SkPoint fStart;
872 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873};
874
reed@android.com5119bdb2009-06-12 21:27:03 +0000875bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
876 const SkMatrix& matrix) {
877 if (!this->INHERITED::setContext(device, paint, matrix)) {
878 return false;
879 }
880
881 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
882 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000883 fFlags |= SkShader::kConstInY32_Flag;
884 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
885 // only claim this if we do have a 16bit mode (i.e. none of our
886 // colors have alpha), and if we are not dithering (which obviously
887 // is not const in Y).
888 fFlags |= SkShader::kConstInY16_Flag;
889 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000890 }
891 return true;
892}
893
reed@google.com5eb158d2011-04-15 15:50:34 +0000894#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000895 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000896 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000897 SkASSERT(fi <= 0xFF); \
898 fx += dx; \
899 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000900 toggle ^= Gradient_Shader::kDitherStride32; \
reed@google.com13659f12011-04-18 19:59:38 +0000901 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000902
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000903namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000904
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000905typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000906 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000907 int toggle, int count);
908
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000909// This function is deprecated, and will be replaced by
910// shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it.
911void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
912 SkPMColor* SK_RESTRICT dstC,
913 const SkPMColor* SK_RESTRICT cache,
914 int toggle, int count) {
915 // We're a vertical gradient, so no change in a span.
916 // If colors change sharply across the gradient, dithering is
917 // insufficient (it subsamples the color space) and we need to lerp.
918 unsigned fullIndex = proc(fx);
919 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
920 sk_memset32_dither(dstC,
921 cache[toggle + fi],
922 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
923 count);
924}
925
926// Linear interpolation (lerp) is unnecessary if there are no sharp
927// discontinuities in the gradient - which must be true if there are
928// only 2 colors - but it's cheap.
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000929void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
930 SkPMColor* SK_RESTRICT dstC,
931 const SkPMColor* SK_RESTRICT cache,
932 int toggle, int count) {
933 // We're a vertical gradient, so no change in a span.
934 // If colors change sharply across the gradient, dithering is
935 // insufficient (it subsamples the color space) and we need to lerp.
936 unsigned fullIndex = proc(fx);
937 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
938 unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32;
939 SkPMColor lerp =
940 SkFastFourByteInterp(
941 cache[toggle + fi + 1],
942 cache[toggle + fi], remainder);
943 SkPMColor dlerp =
944 SkFastFourByteInterp(
945 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1],
946 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], remainder);
947 sk_memset32_dither(dstC, lerp, dlerp, count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000948}
949
950void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
951 SkPMColor* SK_RESTRICT dstC,
952 const SkPMColor* SK_RESTRICT cache,
953 int toggle, int count) {
954 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000955 range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000956
957 if ((count = range.fCount0) > 0) {
958 sk_memset32_dither(dstC,
959 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000960 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000961 count);
962 dstC += count;
963 }
964 if ((count = range.fCount1) > 0) {
965 int unroll = count >> 3;
966 fx = range.fFx1;
967 for (int i = 0; i < unroll; i++) {
968 NO_CHECK_ITER; NO_CHECK_ITER;
969 NO_CHECK_ITER; NO_CHECK_ITER;
970 NO_CHECK_ITER; NO_CHECK_ITER;
971 NO_CHECK_ITER; NO_CHECK_ITER;
972 }
973 if ((count &= 7) > 0) {
974 do {
975 NO_CHECK_ITER;
976 } while (--count != 0);
977 }
978 }
979 if ((count = range.fCount2) > 0) {
980 sk_memset32_dither(dstC,
981 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000982 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000983 count);
984 }
985}
986
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000987void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
988 SkPMColor* SK_RESTRICT dstC,
989 const SkPMColor* SK_RESTRICT cache,
990 int toggle, int count) {
991 do {
992 unsigned fi = mirror_8bits(fx >> 8);
993 SkASSERT(fi <= 0xFF);
994 fx += dx;
995 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000996 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000997 } while (--count != 0);
998}
999
1000void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1001 SkPMColor* SK_RESTRICT dstC,
1002 const SkPMColor* SK_RESTRICT cache,
1003 int toggle, int count) {
1004 do {
1005 unsigned fi = repeat_8bits(fx >> 8);
1006 SkASSERT(fi <= 0xFF);
1007 fx += dx;
1008 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001009 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001010 } while (--count != 0);
1011}
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001012
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001013}
1014
1015void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1016 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017 SkASSERT(count > 0);
1018
1019 SkPoint srcPt;
1020 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1021 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001022 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +00001023#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001024 int toggle = ((x ^ y) & 1) * kDitherStride32;
reed@google.com0e734bd2011-12-08 17:24:44 +00001025#else
1026 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +00001027#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001028
reed@android.comc552a432009-06-12 20:02:50 +00001029 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001030 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1031 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001032 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1033
reed@android.comc552a432009-06-12 20:02:50 +00001034 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001035 SkFixed dxStorage[1];
1036 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1037 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +00001038 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001039 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1040 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1041 }
1042
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001043 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +00001044 if (SkFixedNearlyZero(dx)) {
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001045#ifdef SK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS
1046 if (fColorCount > 2) {
1047 shadeProc = shadeSpan_linear_vertical_lerp;
1048 } else {
1049 shadeProc = shadeSpan_linear_vertical;
1050 }
1051#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001052 shadeProc = shadeSpan_linear_vertical_lerp;
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001053#endif
reed@android.comc552a432009-06-12 20:02:50 +00001054 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001055 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +00001056 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001057 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +00001058 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001059 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001061 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +00001062 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 SkScalar dstX = SkIntToScalar(x);
1064 SkScalar dstY = SkIntToScalar(y);
1065 do {
1066 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1067 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1068 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001069 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001070 toggle ^= Gradient_Shader::kDitherStride32;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001071 dstX += SK_Scalar1;
1072 } while (--count != 0);
1073 }
1074}
1075
reed@google.com55b8e8c2011-01-13 16:22:35 +00001076SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +00001077 SkMatrix* matrix,
1078 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +00001079 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001080 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001081 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 }
1083 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001084 matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085 matrix->preConcat(fPtsToUnit);
1086 }
1087 if (xy) {
1088 xy[0] = fTileMode;
1089 xy[1] = kClamp_TileMode;
1090 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001091 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001092}
1093
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001094SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1095 if (info) {
1096 commonAsAGradient(info);
1097 info->fPoint[0] = fStart;
1098 info->fPoint[1] = fEnd;
1099 }
1100 return kLinear_GradientType;
1101}
1102
reed@android.com3c9b2a42009-08-27 19:28:37 +00001103static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1104 int count) {
1105 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001106 *dst++ = value;
1107 count -= 1;
1108 SkTSwap(value, other);
1109 }
1110
1111 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001112
reed@android.com3c9b2a42009-08-27 19:28:37 +00001113 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001114 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001115 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001116}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001117
reed@google.com5eb158d2011-04-15 15:50:34 +00001118#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001119 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001120 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001121 SkASSERT(fi < Gradient_Shader::kCache16Count); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001122 fx += dx; \
1123 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001124 toggle ^= Gradient_Shader::kDitherStride16; \
reed@google.com13659f12011-04-18 19:59:38 +00001125 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001126
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001127namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001128
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001129typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001130 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001131 int toggle, int count);
1132
1133void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1134 uint16_t* SK_RESTRICT dstC,
1135 const uint16_t* SK_RESTRICT cache,
1136 int toggle, int count) {
1137 // we're a vertical gradient, so no change in a span
1138 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001139 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001140 dither_memset16(dstC, cache[toggle + fi],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001141 cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001142
1143}
1144
1145void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1146 uint16_t* SK_RESTRICT dstC,
1147 const uint16_t* SK_RESTRICT cache,
1148 int toggle, int count) {
1149 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001150 range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001151
1152 if ((count = range.fCount0) > 0) {
1153 dither_memset16(dstC,
1154 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001155 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001156 count);
1157 dstC += count;
1158 }
1159 if ((count = range.fCount1) > 0) {
1160 int unroll = count >> 3;
1161 fx = range.fFx1;
1162 for (int i = 0; i < unroll; i++) {
1163 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1164 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1165 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1166 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1167 }
1168 if ((count &= 7) > 0) {
1169 do {
1170 NO_CHECK_ITER_16;
1171 } while (--count != 0);
1172 }
1173 }
1174 if ((count = range.fCount2) > 0) {
1175 dither_memset16(dstC,
1176 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001177 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001178 count);
1179 }
1180}
1181
1182void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1183 uint16_t* SK_RESTRICT dstC,
1184 const uint16_t* SK_RESTRICT cache,
1185 int toggle, int count) {
1186 do {
1187 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1188 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001189 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001190 fx += dx;
1191 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001192 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001193 } while (--count != 0);
1194}
1195
1196void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1197 uint16_t* SK_RESTRICT dstC,
1198 const uint16_t* SK_RESTRICT cache,
1199 int toggle, int count) {
1200 SkASSERT(proc == repeat_tileproc);
1201 do {
1202 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1203 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001204 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001205 fx += dx;
1206 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001207 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001208 } while (--count != 0);
1209}
1210}
1211
1212void Linear_Gradient::shadeSpan16(int x, int y,
1213 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 SkASSERT(count > 0);
1215
1216 SkPoint srcPt;
1217 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1218 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001219 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001220 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001222 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001223 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1224 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1226
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001227 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228 SkFixed dxStorage[1];
1229 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1230 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001231 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1233 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1234 }
1235
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001236 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001237 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001238 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001239 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001240 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001241 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001242 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001243 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001246 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001247 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 SkScalar dstX = SkIntToScalar(x);
1249 SkScalar dstY = SkIntToScalar(y);
1250 do {
1251 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1252 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1253 SkASSERT(fi <= 0xFFFF);
1254
reed@android.com512a8762009-12-14 15:25:36 +00001255 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001257 toggle ^= Gradient_Shader::kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258
1259 dstX += SK_Scalar1;
1260 } while (--count != 0);
1261 }
1262}
1263
1264///////////////////////////////////////////////////////////////////////////////
1265
1266#define kSQRT_TABLE_BITS 11
1267#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1268
1269#include "SkRadialGradient_Table.h"
1270
1271#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1272
1273#include <stdio.h>
1274
reed@google.com61eb0402011-04-15 12:11:12 +00001275void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1277
1278 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1279 SkASSERT(file);
1280 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1281
reed@google.com61eb0402011-04-15 12:11:12 +00001282 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1283 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001285 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286
1287 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1288
1289 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001290 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001292 }
1293 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001294 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001295 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 }
1297 ::fprintf(file, "};\n");
1298 ::fclose(file);
1299}
1300
1301#endif
1302
1303
reed@google.com61eb0402011-04-15 12:11:12 +00001304static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1305 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 SkScalar inv = SkScalarInvert(radius);
1307
1308 matrix->setTranslate(-center.fX, -center.fY);
1309 matrix->postScale(inv, inv);
1310}
1311
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001312
1313namespace {
1314
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001315typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
1316 SkScalar sfy, SkScalar sdy,
1317 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001318 int toggle, int count);
1319
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001320void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
1321 SkScalar sfy, SkScalar sdy,
1322 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001323 int toggle, int count) {
1324 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1325
1326 /* knock these down so we can pin against +- 0x7FFF, which is an
1327 immediate load, rather than 0xFFFF which is slower. This is a
1328 compromise, since it reduces our precision, but that appears
1329 to be visually OK. If we decide this is OK for all of our cases,
1330 we could (it seems) put this scale-down into fDstToIndex,
1331 to avoid having to do these extra shifts each time.
1332 */
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001333 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1334 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1335 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1336 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001337 // might perform this check for the other modes,
1338 // but the win will be a smaller % of the total
1339 if (dy == 0) {
1340 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1341 fy *= fy;
1342 do {
1343 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1344 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1345 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1346 fx += dx;
1347 *dstC++ = cache[toggle +
1348 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001349 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001350 } while (--count != 0);
1351 } else {
1352 do {
1353 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1354 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1355 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1356 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1357 fx += dx;
1358 fy += dy;
1359 *dstC++ = cache[toggle +
1360 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001361 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001362 } while (--count != 0);
1363 }
1364}
1365
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001366void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
1367 SkScalar sfy, SkScalar sdy,
1368 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001369 int toggle, int count) {
1370 do {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001371#ifdef SK_SCALAR_IS_FLOAT
1372 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1373 SkFixed dist = SkFloatToFixed(fdist);
1374#else
1375 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1376 SkFixedSquare(sfy);
1377 if (magnitudeSquared < 0) // Overflow.
1378 magnitudeSquared = SK_FixedMax;
1379 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1380#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001381 unsigned fi = mirror_tileproc(dist);
1382 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001383 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001384 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001385 sfx += sdx;
1386 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001387 } while (--count != 0);
1388}
1389
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001390void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
1391 SkScalar sfy, SkScalar sdy,
1392 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001393 int toggle, int count) {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001394 SkFixed fx = SkScalarToFixed(sfx);
1395 SkFixed dx = SkScalarToFixed(sdx);
1396 SkFixed fy = SkScalarToFixed(sfy);
1397 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001398 do {
1399 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1400 unsigned fi = repeat_tileproc(dist);
1401 SkASSERT(fi <= 0xFFFF);
1402 fx += dx;
1403 fy += dy;
1404 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001405 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001406 } while (--count != 0);
1407}
1408
1409}
1410
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411class Radial_Gradient : public Gradient_Shader {
1412public:
1413 Radial_Gradient(const SkPoint& center, SkScalar radius,
1414 const SkColor colors[], const SkScalar pos[], int colorCount,
1415 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001416 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1417 fCenter(center),
1418 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001419 {
1420 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1421 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1422
1423 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1424 }
reed@google.com61eb0402011-04-15 12:11:12 +00001425
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001426 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
1427 SK_OVERRIDE;
1428 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
1429 int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 SkASSERT(count > 0);
1431
1432 SkPoint srcPt;
1433 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1434 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001435 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001436 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437
reed@android.com3c9b2a42009-08-27 19:28:37 +00001438 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001439 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1440 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001441
1442 SkScalar sdx = fDstToIndex.getScaleX();
1443 SkScalar sdy = fDstToIndex.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001444
reed@android.com3c9b2a42009-08-27 19:28:37 +00001445 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446 SkFixed storage[2];
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001447 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1448 &storage[0], &storage[1]);
1449 sdx = SkFixedToScalar(storage[0]);
1450 sdy = SkFixedToScalar(storage[1]);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001451 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001453 }
1454
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001455 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001456 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001457 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001458 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001459 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001460 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 }
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001463 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
1464 cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001465 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466 SkScalar dstX = SkIntToScalar(x);
1467 SkScalar dstY = SkIntToScalar(y);
1468 do {
1469 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1470 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1471 SkASSERT(fi <= 0xFFFF);
1472
1473 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001475 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476
1477 dstX += SK_Scalar1;
1478 } while (--count != 0);
1479 }
1480 }
1481
reed@google.com55b8e8c2011-01-13 16:22:35 +00001482 virtual BitmapType asABitmap(SkBitmap* bitmap,
1483 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001484 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001485 SkScalar* twoPointRadialParams)
1486 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001487 if (bitmap) {
1488 this->commonAsABitmap(bitmap);
1489 }
1490 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001491 matrix->setScale(SkIntToScalar(kGradient32Length),
1492 SkIntToScalar(kGradient32Length));
reed@google.comdc731fd2010-12-23 15:19:47 +00001493 matrix->preConcat(fPtsToUnit);
1494 }
1495 if (xy) {
1496 xy[0] = fTileMode;
1497 xy[1] = kClamp_TileMode;
1498 }
1499 return kRadial_BitmapType;
1500 }
reed@google.com7716afb2011-12-07 15:17:50 +00001501 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001502 if (info) {
1503 commonAsAGradient(info);
1504 info->fPoint[0] = fCenter;
1505 info->fRadius[0] = fRadius;
1506 }
1507 return kRadial_GradientType;
1508 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001509
djsollen@google.comba28d032012-03-26 17:57:35 +00001510 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Radial_Gradient)
1511
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001513 Radial_Gradient(SkFlattenableReadBuffer& buffer)
djsollen@google.com54924242012-03-29 15:18:04 +00001514 : INHERITED(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001515 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001516 fRadius(buffer.readScalar()) {
1517 }
djsollen@google.com54924242012-03-29 15:18:04 +00001518 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
1519 this->INHERITED::flatten(buffer);
1520 buffer.writeScalar(fCenter.fX);
1521 buffer.writeScalar(fCenter.fY);
1522 buffer.writeScalar(fRadius);
1523 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524
1525private:
1526 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001527 const SkPoint fCenter;
1528 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529};
1530
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001531namespace {
1532
1533inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001534 // fast, overly-conservative test: checks unit square instead
1535 // of unit circle
1536 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1537 (fx <= -SK_FixedHalf && dx <= 0);
1538 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1539 (fy <= -SK_FixedHalf && dy <= 0);
1540
1541 return xClamped || yClamped;
1542}
1543
1544// Return true if (fx * fy) is always inside the unit circle
1545// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1546// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001547inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001548 int fy, int dy, int count) {
1549 SkASSERT(count > 0);
1550 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1551 return false;
1552 }
1553 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1554 return false;
1555 }
1556 fx += (count - 1) * dx;
1557 fy += (count - 1) * dy;
1558 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1559 return false;
1560 }
1561 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1562}
1563
1564#define UNPINNED_RADIAL_STEP \
1565 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001566 *dstC++ = cache[toggle + \
1567 (sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift)]; \
1568 toggle ^= Gradient_Shader::kDitherStride32; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001569 fx += dx; \
1570 fy += dy;
1571
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001572typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1573 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001574 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001575 int count, int toggle);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001576
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001577// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001578void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
1579 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001580 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001581 int count, int toggle) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001582 // Floating point seems to be slower than fixed point,
1583 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001584 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001585 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1586 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1587 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1588 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001589 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001590 unsigned fi = Gradient_Shader::kGradient32Length;
1591 sk_memset32_dither(dstC,
1592 cache[toggle + fi],
1593 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
1594 count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001595 } else if ((count > 4) &&
1596 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1597 unsigned fi;
1598 // 4x unroll appears to be no faster than 2x unroll on Linux
1599 while (count > 1) {
1600 UNPINNED_RADIAL_STEP;
1601 UNPINNED_RADIAL_STEP;
1602 count -= 2;
1603 }
1604 if (count) {
1605 UNPINNED_RADIAL_STEP;
1606 }
1607 }
1608 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001609 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1610 if (dy == 0) {
1611 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1612 yy *= yy;
1613 do {
1614 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1615 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1616 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001617 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1618 Gradient_Shader::kSqrt32Shift)];
1619 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001620 fx += dx;
1621 } while (--count != 0);
1622 } else {
1623 do {
1624 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1625 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1626 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1627 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001628 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1629 Gradient_Shader::kSqrt32Shift)];
1630 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001631 fx += dx;
1632 fy += dy;
1633 } while (--count != 0);
1634 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001635 }
1636}
1637
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001638// Unrolling this loop doesn't seem to help (when float); we're stalling to
1639// get the results of the sqrt (?), and don't have enough extra registers to
1640// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001641void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1642 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001643 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001644 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001645 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001646#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001647 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1648 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001649#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001650 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1651 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001652 if (magnitudeSquared < 0) // Overflow.
1653 magnitudeSquared = SK_FixedMax;
1654 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001655#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001656 unsigned fi = mirror_tileproc(dist);
1657 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001658 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1659 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001660 sfx += sdx;
1661 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001662 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001663}
1664
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001665void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1666 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001667 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001668 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001669 SkFixed fx = SkScalarToFixed(sfx);
1670 SkFixed dx = SkScalarToFixed(sdx);
1671 SkFixed fy = SkScalarToFixed(sfy);
1672 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001673 do {
1674 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1675 SkFixedSquare(fy);
1676 if (magnitudeSquared < 0) // Overflow.
1677 magnitudeSquared = SK_FixedMax;
1678 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1679 unsigned fi = repeat_tileproc(dist);
1680 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001681 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1682 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001683 fx += dx;
1684 fy += dy;
1685 } while (--count != 0);
1686}
1687}
1688
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001689void Radial_Gradient::shadeSpan(int x, int y,
1690 SkPMColor* SK_RESTRICT dstC, int count) {
1691 SkASSERT(count > 0);
1692
1693 SkPoint srcPt;
1694 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1695 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001696 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001697#ifdef USE_DITHER_32BIT_GRADIENT
1698 int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32;
1699#else
1700 int toggle = 0;
1701#endif
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001702
1703 if (fDstToIndexClass != kPerspective_MatrixClass) {
1704 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1705 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001706 SkScalar sdx = fDstToIndex.getScaleX();
1707 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001708
1709 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1710 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001711 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1712 &storage[0], &storage[1]);
1713 sdx = SkFixedToScalar(storage[0]);
1714 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001715 } else {
1716 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001717 }
1718
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001719 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001720 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001721 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001722 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001723 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001724 } else {
1725 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001726 }
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001727 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001728 } else { // perspective case
1729 SkScalar dstX = SkIntToScalar(x);
1730 SkScalar dstY = SkIntToScalar(y);
1731 do {
1732 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1733 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1734 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001735 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001736 dstX += SK_Scalar1;
1737 } while (--count != 0);
1738 }
1739}
1740
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001741/* Two-point radial gradients are specified by two circles, each with a center
1742 point and radius. The gradient can be considered to be a series of
1743 concentric circles, with the color interpolated from the start circle
1744 (at t=0) to the end circle (at t=1).
1745
1746 For each point (x, y) in the span, we want to find the
1747 interpolated circle that intersects that point. The center
1748 of the desired circle (Cx, Cy) falls at some distance t
1749 along the line segment between the start point (Sx, Sy) and
1750 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001751
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001752 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1753 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001754
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001755 The radius of the desired circle (r) is also a linear interpolation t
1756 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001757
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001758 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001759
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001760 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001761
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001762 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001763
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001764 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001765
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001766 (x - ((1 - t) * Sx + t * Ex))^2
1767 + (y - ((1 - t) * Sy + t * Ey))^2
1768 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001769
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001770 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001771
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001772 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1773 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1774 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001775
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001776 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1777
1778 [Dx^2 + Dy^2 - Dr^2)] * t^2
1779 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1780 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001781
1782 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001783 possible circles on which the point may fall. Solving for t yields
1784 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001785
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001786 If a<0, the start circle is entirely contained in the
1787 end circle, and one of the roots will be <0 or >1 (off the line
1788 segment). If a>0, the start circle falls at least partially
1789 outside the end circle (or vice versa), and the gradient
1790 defines a "tube" where a point may be on one circle (on the
1791 inside of the tube) or the other (outside of the tube). We choose
1792 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001793
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001794 In order to keep the math to within the limits of fixed point,
1795 we divide the entire quadratic by Dr^2, and replace
1796 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001797
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001798 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1799 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1800 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001801
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001802 (x' and y' are computed by appending the subtract and scale to the
1803 fDstToIndex matrix in the constructor).
1804
1805 Since the 'A' component of the quadratic is independent of x' and y', it
1806 is precomputed in the constructor. Since the 'B' component is linear in
1807 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001808 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001809 a perspective projection), it must be computed in the loop.
1810
1811*/
1812
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001813namespace {
1814
1815inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1816 SkScalar sr2d2, SkScalar foura,
1817 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001818 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001819 if (0 == foura) {
1820 return SkScalarToFixed(SkScalarDiv(-c, b));
1821 }
1822
reed@google.com84e9c082011-04-13 17:44:24 +00001823 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001824 if (discrim < 0) {
1825 discrim = -discrim;
1826 }
reed@google.com84e9c082011-04-13 17:44:24 +00001827 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1828 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001829 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001830 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001831 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001832 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001833 }
reed@google.com84e9c082011-04-13 17:44:24 +00001834 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001835}
1836
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001837typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1838 SkScalar fy, SkScalar dy,
1839 SkScalar b, SkScalar db,
1840 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001841 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001842 int count);
1843
1844void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1845 SkScalar fy, SkScalar dy,
1846 SkScalar b, SkScalar db,
1847 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001848 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001849 int count) {
1850 for (; count > 0; --count) {
1851 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1852 fOneOverTwoA, posRoot);
1853 SkFixed index = SkClampMax(t, 0xFFFF);
1854 SkASSERT(index <= 0xFFFF);
1855 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1856 fx += dx;
1857 fy += dy;
1858 b += db;
1859 }
1860}
1861void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1862 SkScalar fy, SkScalar dy,
1863 SkScalar b, SkScalar db,
1864 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001865 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001866 int count) {
1867 for (; count > 0; --count) {
1868 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1869 fOneOverTwoA, posRoot);
1870 SkFixed index = mirror_tileproc(t);
1871 SkASSERT(index <= 0xFFFF);
1872 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1873 fx += dx;
1874 fy += dy;
1875 b += db;
1876 }
1877}
1878
1879void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1880 SkScalar fy, SkScalar dy,
1881 SkScalar b, SkScalar db,
1882 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001883 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001884 int count) {
1885 for (; count > 0; --count) {
1886 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1887 fOneOverTwoA, posRoot);
1888 SkFixed index = repeat_tileproc(t);
1889 SkASSERT(index <= 0xFFFF);
1890 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1891 fx += dx;
1892 fy += dy;
1893 b += db;
1894 }
1895}
1896
1897
1898
1899}
1900
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001901class Two_Point_Radial_Gradient : public Gradient_Shader {
1902public:
1903 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1904 const SkPoint& end, SkScalar endRadius,
1905 const SkColor colors[], const SkScalar pos[],
1906 int colorCount, SkShader::TileMode mode,
1907 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001908 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1909 fCenter1(start),
1910 fCenter2(end),
1911 fRadius1(startRadius),
1912 fRadius2(endRadius) {
1913 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001914 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001915
1916 virtual BitmapType asABitmap(SkBitmap* bitmap,
1917 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001918 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001919 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001920 if (bitmap) {
1921 this->commonAsABitmap(bitmap);
1922 }
1923 SkScalar diffL = 0; // just to avoid gcc warning
1924 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001925 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001926 SkScalarSquare(fDiff.fY));
1927 }
1928 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001929 if (diffL) {
1930 SkScalar invDiffL = SkScalarInvert(diffL);
1931 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1932 SkScalarMul(invDiffL, fDiff.fX));
1933 } else {
1934 matrix->reset();
1935 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001936 matrix->preConcat(fPtsToUnit);
1937 }
1938 if (xy) {
1939 xy[0] = fTileMode;
1940 xy[1] = kClamp_TileMode;
1941 }
1942 if (NULL != twoPointRadialParams) {
1943 twoPointRadialParams[0] = diffL;
1944 twoPointRadialParams[1] = fStartRadius;
1945 twoPointRadialParams[2] = fDiffRadius;
1946 }
1947 return kTwoPointRadial_BitmapType;
1948 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001949
reed@google.com8e6d9142011-12-07 15:30:34 +00001950 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001951 if (info) {
1952 commonAsAGradient(info);
1953 info->fPoint[0] = fCenter1;
1954 info->fPoint[1] = fCenter2;
1955 info->fRadius[0] = fRadius1;
1956 info->fRadius[1] = fRadius2;
1957 }
1958 return kRadial2_GradientType;
1959 }
1960
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001961 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1962 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001963 SkASSERT(count > 0);
1964
1965 // Zero difference between radii: fill with transparent black.
1966 if (fDiffRadius == 0) {
1967 sk_bzero(dstC, count * sizeof(*dstC));
1968 return;
1969 }
1970 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1971 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001972 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001973
1974 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001975 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001976 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001977 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001978 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1979 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001980 SkScalar dx, fx = srcPt.fX;
1981 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001982
reed@google.com61eb0402011-04-15 12:11:12 +00001983 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001984 SkFixed fixedX, fixedY;
1985 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1986 dx = SkFixedToScalar(fixedX);
1987 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001988 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001989 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001990 dx = fDstToIndex.getScaleX();
1991 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001992 }
reed@google.com84e9c082011-04-13 17:44:24 +00001993 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1994 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1995 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1996 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001997
1998 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00001999 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002000 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00002001 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002002 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00002003 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002004 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002005 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002006 (*shadeProc)(fx, dx, fy, dy, b, db,
2007 fSr2D2, foura, fOneOverTwoA, posRoot,
2008 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00002009 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00002010 SkScalar dstX = SkIntToScalar(x);
2011 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002012 for (; count > 0; --count) {
2013 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00002014 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00002015 SkScalar fx = srcPt.fX;
2016 SkScalar fy = srcPt.fY;
2017 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
2018 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002019 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
2020 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002021 SkFixed index = proc(t);
2022 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002023 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00002024 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002025 }
2026 }
2027 }
2028
reed@android.com6c59a172009-09-22 20:24:05 +00002029 virtual bool setContext(const SkBitmap& device,
2030 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00002031 const SkMatrix& matrix) SK_OVERRIDE {
2032 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00002033 return false;
2034 }
2035
2036 // we don't have a span16 proc
2037 fFlags &= ~kHasSpan16_Flag;
2038 return true;
2039 }
2040
djsollen@google.com54924242012-03-29 15:18:04 +00002041 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Two_Point_Radial_Gradient)
2042
2043protected:
2044 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
2045 : INHERITED(buffer),
2046 fCenter1(unflatten_point(buffer)),
2047 fCenter2(unflatten_point(buffer)),
2048 fRadius1(buffer.readScalar()),
2049 fRadius2(buffer.readScalar()) {
2050 init();
2051 };
2052
2053 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00002054 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002055 buffer.writeScalar(fCenter1.fX);
2056 buffer.writeScalar(fCenter1.fY);
2057 buffer.writeScalar(fCenter2.fX);
2058 buffer.writeScalar(fCenter2.fY);
2059 buffer.writeScalar(fRadius1);
2060 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002061 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002062
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002063private:
2064 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002065 const SkPoint fCenter1;
2066 const SkPoint fCenter2;
2067 const SkScalar fRadius1;
2068 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002069 SkPoint fDiff;
2070 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002071
2072 void init() {
2073 fDiff = fCenter1 - fCenter2;
2074 fDiffRadius = fRadius2 - fRadius1;
2075 SkScalar inv = SkScalarInvert(fDiffRadius);
2076 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2077 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2078 fStartRadius = SkScalarMul(fRadius1, inv);
2079 fSr2D2 = SkScalarSquare(fStartRadius);
2080 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002081 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002082
2083 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2084 fPtsToUnit.postScale(inv, inv);
2085 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002086};
2087
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088///////////////////////////////////////////////////////////////////////////////
2089
2090class Sweep_Gradient : public Gradient_Shader {
2091public:
2092 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2093 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002094 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2095 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096 {
2097 fPtsToUnit.setTranslate(-cx, -cy);
2098 }
reed@google.com7716afb2011-12-07 15:17:50 +00002099 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2100 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002101
2102 virtual BitmapType asABitmap(SkBitmap* bitmap,
2103 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002104 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002105 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002106 if (bitmap) {
2107 this->commonAsABitmap(bitmap);
2108 }
2109 if (matrix) {
2110 *matrix = fPtsToUnit;
2111 }
2112 if (xy) {
2113 xy[0] = fTileMode;
2114 xy[1] = kClamp_TileMode;
2115 }
2116 return kSweep_BitmapType;
2117 }
2118
reed@google.com7716afb2011-12-07 15:17:50 +00002119 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002120 if (info) {
2121 commonAsAGradient(info);
2122 info->fPoint[0] = fCenter;
2123 }
2124 return kSweep_GradientType;
2125 }
2126
djsollen@google.comba28d032012-03-26 17:57:35 +00002127 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Sweep_Gradient)
2128
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002130 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
djsollen@google.com54924242012-03-29 15:18:04 +00002131 : INHERITED(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002132 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002133 }
djsollen@google.com54924242012-03-29 15:18:04 +00002134 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
2135 this->INHERITED::flatten(buffer);
2136 buffer.writeScalar(fCenter.fX);
2137 buffer.writeScalar(fCenter.fY);
2138 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002139
reed@android.com8a1c16f2008-12-17 15:59:43 +00002140private:
2141 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002142 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143};
2144
2145#ifdef COMPUTE_SWEEP_TABLE
2146#define PI 3.14159265
2147static bool gSweepTableReady;
2148static uint8_t gSweepTable[65];
2149
2150/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2151 We scale the results to [0..32]
2152*/
reed@google.com61eb0402011-04-15 12:11:12 +00002153static const uint8_t* build_sweep_table() {
2154 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002155 const int N = 65;
2156 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002157
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158 for (int i = 0; i < N; i++)
2159 {
2160 double arg = i / DENOM;
2161 double v = atan(arg);
2162 int iv = (int)round(v * DENOM * 2 / PI);
2163// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2164 printf("%d, ", iv);
2165 gSweepTable[i] = iv;
2166 }
2167 gSweepTableReady = true;
2168 }
2169 return gSweepTable;
2170}
2171#else
2172static const uint8_t gSweepTable[] = {
2173 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2174 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2175 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2176 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2177 32
2178};
2179static const uint8_t* build_sweep_table() { return gSweepTable; }
2180#endif
2181
2182// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2183// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2184// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2185
2186//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002187static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002188 SkASSERT(numer <= denom);
2189 SkASSERT(numer > 0);
2190 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002191
reed@android.com8a1c16f2008-12-17 15:59:43 +00002192 int nbits = SkCLZ(numer);
2193 int dbits = SkCLZ(denom);
2194 int bits = 6 - nbits + dbits;
2195 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002196
reed@google.com61eb0402011-04-15 12:11:12 +00002197 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002198 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002199 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002200
2201 denom <<= dbits - 1;
2202 numer <<= nbits - 1;
2203
2204 unsigned result = 0;
2205
2206 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002207 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002208 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002209 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002210 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002211 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002212
reed@android.com8a1c16f2008-12-17 15:59:43 +00002213 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002214 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215 // make room for the rest of the answer bits
2216 result <<= bits;
2217 switch (bits) {
2218 case 6:
2219 if ((numer = (numer << 1) - denom) >= 0)
2220 result |= 32;
2221 else
2222 numer += denom;
2223 case 5:
2224 if ((numer = (numer << 1) - denom) >= 0)
2225 result |= 16;
2226 else
2227 numer += denom;
2228 case 4:
2229 if ((numer = (numer << 1) - denom) >= 0)
2230 result |= 8;
2231 else
2232 numer += denom;
2233 case 3:
2234 if ((numer = (numer << 1) - denom) >= 0)
2235 result |= 4;
2236 else
2237 numer += denom;
2238 case 2:
2239 if ((numer = (numer << 1) - denom) >= 0)
2240 result |= 2;
2241 else
2242 numer += denom;
2243 case 1:
2244 default: // not strictly need, but makes GCC make better ARM code
2245 if ((numer = (numer << 1) - denom) >= 0)
2246 result |= 1;
2247 else
2248 numer += denom;
2249 }
2250 }
2251 return result;
2252}
2253
2254// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002255static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002256#ifdef SK_DEBUG
2257 {
2258 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002259 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002260 gOnce = true;
2261 SkASSERT(div_64(55, 55) == 64);
2262 SkASSERT(div_64(128, 256) == 32);
2263 SkASSERT(div_64(2326528, 4685824) == 31);
2264 SkASSERT(div_64(753664, 5210112) == 9);
2265 SkASSERT(div_64(229376, 4882432) == 3);
2266 SkASSERT(div_64(2, 64) == 2);
2267 SkASSERT(div_64(1, 64) == 1);
2268 // test that we handle underflow correctly
2269 SkASSERT(div_64(12345, 0x54321234) == 0);
2270 }
2271 }
2272#endif
2273
2274 SkASSERT(y > 0 && x > 0);
2275 const uint8_t* table = build_sweep_table();
2276
2277 unsigned result;
2278 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002279 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002280 // first part of the atan(v) = PI/2 - atan(1/v) identity
2281 // since our div_64 and table want v <= 1, where v = y/x
2282 SkTSwap<SkFixed>(x, y);
2283 }
2284
2285 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002286
reed@android.com8a1c16f2008-12-17 15:59:43 +00002287#ifdef SK_DEBUG
2288 {
2289 unsigned result2 = SkDivBits(y, x, 6);
2290 SkASSERT(result2 == result ||
2291 (result == 1 && result2 == 0));
2292 }
2293#endif
2294
2295 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2296 result = table[result];
2297
reed@google.com61eb0402011-04-15 12:11:12 +00002298 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002299 // complete the atan(v) = PI/2 - atan(1/v) identity
2300 result = 64 - result;
2301 // pin to 63
2302 result -= result >> 6;
2303 }
2304
2305 SkASSERT(result <= 63);
2306 return result;
2307}
2308
2309// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002310#ifdef SK_SCALAR_IS_FLOAT
2311static unsigned SkATan2_255(float y, float x) {
2312 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2313 static const float g255Over2PI = 40.584510488433314f;
2314
2315 float result = sk_float_atan2(y, x);
2316 if (result < 0) {
2317 result += 2 * SK_ScalarPI;
2318 }
2319 SkASSERT(result >= 0);
2320 // since our value is always >= 0, we can cast to int, which is faster than
2321 // calling floorf()
2322 int ir = (int)(result * g255Over2PI);
2323 SkASSERT(ir >= 0 && ir <= 255);
2324 return ir;
2325}
2326#else
reed@google.com61eb0402011-04-15 12:11:12 +00002327static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2328 if (x == 0) {
2329 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002330 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002331 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002332 return y < 0 ? 192 : 64;
2333 }
reed@google.com61eb0402011-04-15 12:11:12 +00002334 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002335 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002336 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002337
reed@android.com8a1c16f2008-12-17 15:59:43 +00002338 /* Find the right quadrant for x,y
2339 Since atan_0_90 only handles the first quadrant, we rotate x,y
2340 appropriately before calling it, and then add the right amount
2341 to account for the real quadrant.
2342 quadrant 0 : add 0 | x > 0 && y > 0
2343 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2344 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2345 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002346
reed@android.com8a1c16f2008-12-17 15:59:43 +00002347 map x<0 to (1 << 6)
2348 map y<0 to (3 << 6)
2349 add = map_x ^ map_y
2350 */
2351 int xsign = x >> 31;
2352 int ysign = y >> 31;
2353 int add = ((-xsign) ^ (ysign & 3)) << 6;
2354
2355#ifdef SK_DEBUG
2356 if (0 == add)
2357 SkASSERT(x > 0 && y > 0);
2358 else if (64 == add)
2359 SkASSERT(x < 0 && y > 0);
2360 else if (128 == add)
2361 SkASSERT(x < 0 && y < 0);
2362 else if (192 == add)
2363 SkASSERT(x > 0 && y < 0);
2364 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002365 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002366#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002367
reed@android.com8a1c16f2008-12-17 15:59:43 +00002368 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2369 where we need to rotate x,y by 90 or -90
2370 */
2371 x = (x ^ xsign) - xsign;
2372 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002373 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002374 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002375 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002376
2377 unsigned result = add + atan_0_90(y, x);
2378 SkASSERT(result < 256);
2379 return result;
2380}
reed@google.com51baf5a2011-09-21 13:38:36 +00002381#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002382
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002383void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2384 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002385 SkMatrix::MapXYProc proc = fDstToIndexProc;
2386 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002387 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002388 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002389
reed@google.com61eb0402011-04-15 12:11:12 +00002390 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002391 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2392 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002393 SkScalar dx, fx = srcPt.fX;
2394 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002395
reed@google.com61eb0402011-04-15 12:11:12 +00002396 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002397 SkFixed storage[2];
2398 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2399 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002400 dx = SkFixedToScalar(storage[0]);
2401 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002402 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002403 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002404 dx = matrix.getScaleX();
2405 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002406 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002407
reed@google.com61eb0402011-04-15 12:11:12 +00002408 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002409 *dstC++ = cache[SkATan2_255(fy, fx)];
2410 fx += dx;
2411 fy += dy;
2412 }
reed@google.com61eb0402011-04-15 12:11:12 +00002413 } else { // perspective case
2414 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002415 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002416 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2417 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002418 }
2419 }
2420}
2421
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002422void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2423 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002424 SkMatrix::MapXYProc proc = fDstToIndexProc;
2425 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002426 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002427 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002428 SkPoint srcPt;
2429
reed@google.com61eb0402011-04-15 12:11:12 +00002430 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002431 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2432 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002433 SkScalar dx, fx = srcPt.fX;
2434 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002435
reed@google.com61eb0402011-04-15 12:11:12 +00002436 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002437 SkFixed storage[2];
2438 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2439 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002440 dx = SkFixedToScalar(storage[0]);
2441 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002442 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002443 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002444 dx = matrix.getScaleX();
2445 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002446 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002447
reed@google.com61eb0402011-04-15 12:11:12 +00002448 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002449 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2450 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002451 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002452 fx += dx;
2453 fy += dy;
2454 }
reed@google.com61eb0402011-04-15 12:11:12 +00002455 } else { // perspective case
2456 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002457 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2458 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002459
reed@google.com51baf5a2011-09-21 13:38:36 +00002460 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002461 index >>= (8 - kCache16Bits);
2462 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002463 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002464 }
2465 }
2466}
2467
reed@google.com61eb0402011-04-15 12:11:12 +00002468///////////////////////////////////////////////////////////////////////////////
2469///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002470
2471// assumes colors is SkColor* and pos is SkScalar*
2472#define EXPAND_1_COLOR(count) \
2473 SkColor tmp[2]; \
2474 do { \
2475 if (1 == count) { \
2476 tmp[0] = tmp[1] = colors[0]; \
2477 colors = tmp; \
2478 pos = NULL; \
2479 count = 2; \
2480 } \
2481 } while (0)
2482
reed@google.com61eb0402011-04-15 12:11:12 +00002483SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2484 const SkColor colors[],
2485 const SkScalar pos[], int colorCount,
2486 SkShader::TileMode mode,
2487 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002488 if (NULL == pts || NULL == colors || colorCount < 1) {
2489 return NULL;
2490 }
2491 EXPAND_1_COLOR(colorCount);
2492
reed@android.comab840b82009-07-01 17:00:03 +00002493 return SkNEW_ARGS(Linear_Gradient,
2494 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002495}
2496
reed@google.com61eb0402011-04-15 12:11:12 +00002497SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2498 const SkColor colors[],
2499 const SkScalar pos[], int colorCount,
2500 SkShader::TileMode mode,
2501 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002502 if (radius <= 0 || NULL == colors || colorCount < 1) {
2503 return NULL;
2504 }
2505 EXPAND_1_COLOR(colorCount);
2506
reed@android.comab840b82009-07-01 17:00:03 +00002507 return SkNEW_ARGS(Radial_Gradient,
2508 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002509}
2510
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002511SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2512 SkScalar startRadius,
2513 const SkPoint& end,
2514 SkScalar endRadius,
2515 const SkColor colors[],
2516 const SkScalar pos[],
2517 int colorCount,
2518 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002519 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002520 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2521 return NULL;
2522 }
2523 EXPAND_1_COLOR(colorCount);
2524
2525 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002526 (start, startRadius, end, endRadius, colors, pos,
2527 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002528}
2529
reed@android.com8a1c16f2008-12-17 15:59:43 +00002530SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2531 const SkColor colors[],
2532 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002533 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002534 if (NULL == colors || count < 1) {
2535 return NULL;
2536 }
2537 EXPAND_1_COLOR(count);
2538
2539 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2540}
2541
caryclark@google.comd26147a2011-12-15 14:16:43 +00002542SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2543 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2544 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002545 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002546 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2547SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END