blob: 6e8d2fe0f1773a9b736c4731e5a681b24cf14d64 [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 +0000227Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
228 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000229 SkASSERT(colorCount > 1);
230
231 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
232
233 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000234 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000235
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
237 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
238 fTileMode = mode;
239 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000240
reed@android.com41bccf52009-04-03 13:33:51 +0000241 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000242 fCache32 = NULL;
243 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244
reed@android.com41bccf52009-04-03 13:33:51 +0000245 /* Note: we let the caller skip the first and/or last position.
246 i.e. pos[0] = 0.3, pos[1] = 0.7
247 In these cases, we insert dummy entries to ensure that the final data
248 will be bracketed by [0, 1].
249 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
250
251 Thus colorCount (the caller's value, and fColorCount (our value) may
252 differ by up to 2. In the above example:
253 colorCount = 2
254 fColorCount = 4
255 */
256 fColorCount = colorCount;
257 // check if we need to add in dummy start and/or end position/colors
258 bool dummyFirst = false;
259 bool dummyLast = false;
260 if (pos) {
261 dummyFirst = pos[0] != 0;
262 dummyLast = pos[colorCount - 1] != SK_Scalar1;
263 fColorCount += dummyFirst + dummyLast;
264 }
265
266 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000267 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000268 fOrigColors = reinterpret_cast<SkColor*>(
269 sk_malloc_throw(size * fColorCount));
270 }
271 else {
272 fOrigColors = fStorage;
273 }
274
275 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000276 {
reed@android.com41bccf52009-04-03 13:33:51 +0000277 SkColor* origColors = fOrigColors;
278 if (dummyFirst) {
279 *origColors++ = colors[0];
280 }
281 memcpy(origColors, colors, colorCount * sizeof(SkColor));
282 if (dummyLast) {
283 origColors += colorCount;
284 *origColors = colors[colorCount - 1];
285 }
286 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000287
reed@android.com1c12abe2009-07-02 15:01:02 +0000288 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000289 if (fColorCount > 2) {
290 Rec* recs = fRecs;
291 recs->fPos = 0;
292 // recs->fScale = 0; // unused;
293 recs += 1;
294 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 /* We need to convert the user's array of relative positions into
296 fixed-point positions and scale factors. We need these results
297 to be strictly monotonic (no two values equal or out of order).
298 Hence this complex loop that just jams a zero for the scale
299 value if it sees a segment out of order, and it assures that
300 we start at 0 and end at 1.0
301 */
302 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000303 int startIndex = dummyFirst ? 0 : 1;
304 int count = colorCount + dummyLast;
305 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306 // force the last value to be 1.0
307 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000308 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000309 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000310 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312 }
reed@android.com41bccf52009-04-03 13:33:51 +0000313 // pin curr withing range
314 if (curr < 0) {
315 curr = 0;
316 } else if (curr > SK_Fixed1) {
317 curr = SK_Fixed1;
318 }
319 recs->fPos = curr;
320 if (curr > prev) {
321 recs->fScale = (1 << 24) / (curr - prev);
322 } else {
323 recs->fScale = 0; // ignore this segment
324 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325 // get ready for the next value
326 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000327 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328 }
reed@android.com41bccf52009-04-03 13:33:51 +0000329 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330 SkFixed dp = SK_Fixed1 / (colorCount - 1);
331 SkFixed p = dp;
332 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000333 for (int i = 1; i < colorCount; i++) {
334 recs->fPos = p;
335 recs->fScale = scale;
336 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 p += dp;
338 }
339 }
340 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000341 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342}
343
344Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000345 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346 fCacheAlpha = 256;
347
348 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
349
350 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000351 fCache32 = NULL;
352 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000353
reed@android.com41bccf52009-04-03 13:33:51 +0000354 int colorCount = fColorCount = buffer.readU32();
355 if (colorCount > kColorStorageCount) {
356 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
357 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
358 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000360 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362
363 fTileMode = (TileMode)buffer.readU8();
364 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000365 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366 if (colorCount > 2) {
367 Rec* recs = fRecs;
368 recs[0].fPos = 0;
369 for (int i = 1; i < colorCount; i++) {
370 recs[i].fPos = buffer.readS32();
371 recs[i].fScale = buffer.readU32();
372 }
373 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000374 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000375 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000376}
377
reed@android.com41bccf52009-04-03 13:33:51 +0000378Gradient_Shader::~Gradient_Shader() {
379 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000381 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000382 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000383 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000384 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000385 }
reed@google.com82065d62011-02-07 15:30:46 +0000386 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000387}
388
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000389void Gradient_Shader::initCommon() {
390 fFlags = 0;
391 unsigned colorAlpha = 0xFF;
392 for (int i = 0; i < fColorCount; i++) {
393 colorAlpha &= SkColorGetA(fOrigColors[i]);
394 }
395 fColorsAreOpaque = colorAlpha == 0xFF;
396}
397
djsollen@google.com54924242012-03-29 15:18:04 +0000398void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000399 this->INHERITED::flatten(buffer);
400 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000401 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000402 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
403 buffer.write8(fTileMode);
404 if (fColorCount > 2) {
405 Rec* recs = fRecs;
406 for (int i = 1; i < fColorCount; i++) {
407 buffer.write32(recs[i].fPos);
408 buffer.write32(recs[i].fScale);
409 }
410 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000411 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412}
413
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000414bool Gradient_Shader::isOpaque() const {
415 return fColorsAreOpaque;
416}
417
reed@android.com8a1c16f2008-12-17 15:59:43 +0000418bool Gradient_Shader::setContext(const SkBitmap& device,
419 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000420 const SkMatrix& matrix) {
421 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000422 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000423 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424
425 const SkMatrix& inverse = this->getTotalInverse();
426
427 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
428 return false;
429 }
430
431 fDstToIndexProc = fDstToIndex.getMapXYProc();
432 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
433
434 // now convert our colors in to PMColors
435 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436
437 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000438 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439 fFlags |= kOpaqueAlpha_Flag;
440 }
441 // we can do span16 as long as our individual colors are opaque,
442 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000443 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000444 fFlags |= kHasSpan16_Flag;
445 }
446
reed@google.com95eed982011-07-05 17:01:56 +0000447 this->setCacheAlpha(paintAlpha);
448 return true;
449}
450
451void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000452 // if the new alpha differs from the previous time we were called, inval our cache
453 // this will trigger the cache to be rebuilt.
454 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000455 if (fCacheAlpha != alpha) {
456 fCache16 = NULL; // inval the cache
457 fCache32 = NULL; // inval the cache
458 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000459 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000460 if (fCache32PixelRef) {
461 fCache32PixelRef->notifyPixelsChanged();
462 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000463 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000464}
465
reed@android.com8a1c16f2008-12-17 15:59:43 +0000466#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
467
reed@android.com41bccf52009-04-03 13:33:51 +0000468/** We take the original colors, not our premultiplied PMColors, since we can
469 build a 16bit table as long as the original colors are opaque, even if the
470 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000471*/
reed@android.com512a8762009-12-14 15:25:36 +0000472void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
473 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000474 SkASSERT(count > 1);
475 SkASSERT(SkColorGetA(c0) == 0xFF);
476 SkASSERT(SkColorGetA(c1) == 0xFF);
477
478 SkFixed r = SkColorGetR(c0);
479 SkFixed g = SkColorGetG(c0);
480 SkFixed b = SkColorGetB(c0);
481
482 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
483 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
484 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
485
486 r = SkIntToFixed(r) + 0x8000;
487 g = SkIntToFixed(g) + 0x8000;
488 b = SkIntToFixed(b) + 0x8000;
489
490 do {
491 unsigned rr = r >> 16;
492 unsigned gg = g >> 16;
493 unsigned bb = b >> 16;
494 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000495 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000496 cache += 1;
497 r += dr;
498 g += dg;
499 b += db;
500 } while (--count != 0);
501}
502
reed@google.com55b8e8c2011-01-13 16:22:35 +0000503/*
504 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
505 * semantics of how we 2x2 dither 32->16
506 */
507static inline U8CPU dither_fixed_to_8(SkFixed n) {
508 n >>= 8;
509 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
510}
511
512/*
513 * For dithering with premultiply, we want to ceiling the alpha component,
514 * to ensure that it is always >= any color component.
515 */
516static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
517 n >>= 8;
518 return ((n << 1) - (n | (n >> 8))) >> 8;
519}
520
521void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
522 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000523 SkASSERT(count > 1);
524
reed@android.com1c12abe2009-07-02 15:01:02 +0000525 // need to apply paintAlpha to our two endpoints
526 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
527 SkFixed da;
528 {
529 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
530 da = SkIntToFixed(tmp - a) / (count - 1);
531 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000532
reed@android.com1c12abe2009-07-02 15:01:02 +0000533 SkFixed r = SkColorGetR(c0);
534 SkFixed g = SkColorGetG(c0);
535 SkFixed b = SkColorGetB(c0);
536 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
537 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
538 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000539
540 a = SkIntToFixed(a) + 0x8000;
541 r = SkIntToFixed(r) + 0x8000;
542 g = SkIntToFixed(g) + 0x8000;
543 b = SkIntToFixed(b) + 0x8000;
544
545 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000546 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000547 cache[kCache32Count] =
548 SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
549 dither_fixed_to_8(r),
550 dither_fixed_to_8(g),
551 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000552 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000553 a += da;
554 r += dr;
555 g += dg;
556 b += db;
557 } while (--count != 0);
558}
559
reed@android.com41bccf52009-04-03 13:33:51 +0000560static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000561 SkASSERT((unsigned)x <= SK_Fixed1);
562 return x - (x >> 16);
563}
564
reed@android.com200645d2009-12-14 16:41:57 +0000565static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000566 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000567 if (6 == bits) {
568 return (x << 10) | (x << 4) | (x >> 2);
569 }
570 if (8 == bits) {
571 return (x << 8) | x;
572 }
573 sk_throw();
574 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000575}
576
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000577/** We duplicate the last value in each half of the cache so that
578 interpolation doesn't have to special-case being at the last point.
579*/
580static void complete_16bit_cache(uint16_t* cache, int stride) {
581 cache[stride - 1] = cache[stride - 2];
582 cache[2 * stride - 1] = cache[2 * stride - 2];
583}
584
reed@google.com7c2f27d2011-03-07 19:29:00 +0000585const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000586 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000587 // double the count for dither entries
588 const int entryCount = kCache16Count * 2;
589 const size_t allocSize = sizeof(uint16_t) * entryCount;
590
reed@android.com3c9b2a42009-08-27 19:28:37 +0000591 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000592 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000593 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000594 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000595 if (fColorCount == 2) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000596 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
597 kGradient16Length);
reed@android.com41bccf52009-04-03 13:33:51 +0000598 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 Rec* rec = fRecs;
600 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000601 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000602 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603 SkASSERT(nextIndex < kCache16Count);
604
605 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000606 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000607 prevIndex = nextIndex;
608 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000609 // one extra space left over at the end for complete_16bit_cache()
610 SkASSERT(prevIndex == kGradient16Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000611 }
612
reed@android.com41bccf52009-04-03 13:33:51 +0000613 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000614 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615 uint16_t* linear = fCache16; // just computed linear data
616 uint16_t* mapped = fCache16Storage; // storage for mapped data
617 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000618 for (int i = 0; i < kGradient16Length; i++) {
reed@android.com200645d2009-12-14 16:41:57 +0000619 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000620 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000621 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000622 }
623 sk_free(fCache16);
624 fCache16 = fCache16Storage;
625 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000626 complete_16bit_cache(fCache16, kCache16Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000627 }
628 return fCache16;
629}
630
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000631/** We duplicate the last value in each half of the cache so that
632 interpolation doesn't have to special-case being at the last point.
633*/
634static void complete_32bit_cache(SkPMColor* cache, int stride) {
635 cache[stride - 1] = cache[stride - 2];
636 cache[2 * stride - 1] = cache[2 * stride - 2];
637}
638
reed@google.com7c2f27d2011-03-07 19:29:00 +0000639const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000640 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000641 // double the count for dither entries
642 const int entryCount = kCache32Count * 2;
643 const size_t allocSize = sizeof(SkPMColor) * entryCount;
644
reed@google.comdc731fd2010-12-23 15:19:47 +0000645 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000646 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
647 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000648 }
649 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000650 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000651 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000652 kGradient32Length, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000653 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000654 Rec* rec = fRecs;
655 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000656 for (int i = 1; i < fColorCount; i++) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000657 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000658 SkASSERT(nextIndex < kGradient32Length);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659
660 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000661 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
662 fOrigColors[i],
663 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 prevIndex = nextIndex;
665 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000666 SkASSERT(prevIndex == kGradient32Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667 }
668
reed@android.com41bccf52009-04-03 13:33:51 +0000669 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000670 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000671 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000673 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000675 for (int i = 0; i < kGradient32Length; i++) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000676 int index = map->mapUnit16((i << 8) | i) >> 8;
677 mapped[i] = linear[index];
678 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000679 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000680 fCache32PixelRef->unref();
681 fCache32PixelRef = newPR;
682 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000684 complete_32bit_cache(fCache32, kCache32Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685 }
686 return fCache32;
687}
688
reed@google.comdc731fd2010-12-23 15:19:47 +0000689/*
690 * Because our caller might rebuild the same (logically the same) gradient
691 * over and over, we'd like to return exactly the same "bitmap" if possible,
692 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
693 * To do that, we maintain a private cache of built-bitmaps, based on our
694 * colors and positions. Note: we don't try to flatten the fMapper, so if one
695 * is present, we skip the cache for now.
696 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000697void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000698 // our caller assumes no external alpha, so we ensure that our cache is
699 // built with 0xFF
700 this->setCacheAlpha(0xFF);
701
reed@google.comdc731fd2010-12-23 15:19:47 +0000702 // don't have a way to put the mapper into our cache-key yet
703 if (fMapper) {
704 // force our cahce32pixelref to be built
705 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000706 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000707 bitmap->setPixelRef(fCache32PixelRef);
708 return;
709 }
710
711 // build our key: [numColors + colors[] + {positions[]} ]
712 int count = 1 + fColorCount;
713 if (fColorCount > 2) {
714 count += fColorCount - 1; // fRecs[].fPos
715 }
716
717 SkAutoSTMalloc<16, int32_t> storage(count);
718 int32_t* buffer = storage.get();
719
720 *buffer++ = fColorCount;
721 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
722 buffer += fColorCount;
723 if (fColorCount > 2) {
724 for (int i = 1; i < fColorCount; i++) {
725 *buffer++ = fRecs[i].fPos;
726 }
727 }
728 SkASSERT(buffer - storage.get() == count);
729
730 ///////////////////////////////////
731
digit@google.com1771cbf2012-01-26 21:26:40 +0000732 SK_DECLARE_STATIC_MUTEX(gMutex);
reed@google.comdc731fd2010-12-23 15:19:47 +0000733 static SkBitmapCache* gCache;
734 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
735 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
736 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000737
reed@google.comdc731fd2010-12-23 15:19:47 +0000738 if (NULL == gCache) {
739 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
740 }
741 size_t size = count * sizeof(int32_t);
742
743 if (!gCache->find(storage.get(), size, bitmap)) {
744 // force our cahce32pixelref to be built
745 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000746 // Only expose the linear section of the cache; don't let the caller
747 // know about the padding at the end to make interpolation faster.
748 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000749 bitmap->setPixelRef(fCache32PixelRef);
750
751 gCache->add(storage.get(), size, *bitmap);
752 }
753}
754
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000755void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
756 if (info) {
757 if (info->fColorCount >= fColorCount) {
758 if (info->fColors) {
759 memcpy(info->fColors, fOrigColors,
760 fColorCount * sizeof(SkColor));
761 }
762 if (info->fColorOffsets) {
763 if (fColorCount == 2) {
764 info->fColorOffsets[0] = 0;
765 info->fColorOffsets[1] = SK_Scalar1;
766 } else if (fColorCount > 2) {
767 for (int i = 0; i < fColorCount; i++)
768 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
769 }
770 }
771 }
772 info->fColorCount = fColorCount;
773 info->fTileMode = fTileMode;
774 }
775}
776
reed@google.com61eb0402011-04-15 12:11:12 +0000777///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778
reed@android.com41bccf52009-04-03 13:33:51 +0000779static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780 SkVector vec = pts[1] - pts[0];
781 SkScalar mag = vec.length();
782 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
783
784 vec.scale(inv);
785 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
786 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
787 matrix->postScale(inv, inv);
788}
789
790///////////////////////////////////////////////////////////////////////////////
791
792class Linear_Gradient : public Gradient_Shader {
793public:
794 Linear_Gradient(const SkPoint pts[2],
795 const SkColor colors[], const SkScalar pos[], int colorCount,
796 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000797 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
798 fStart(pts[0]),
799 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 {
801 pts_to_unit_matrix(pts, &fPtsToUnit);
802 }
reed@android.com9b46e772009-06-05 12:24:41 +0000803
reed@google.com7716afb2011-12-07 15:17:50 +0000804 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
805 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
806 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
807 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
808 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
809 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810
djsollen@google.com54924242012-03-29 15:18:04 +0000811 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Linear_Gradient)
812
813protected:
814 Linear_Gradient(SkFlattenableReadBuffer& buffer)
815 : INHERITED(buffer),
816 fStart(unflatten_point(buffer)),
817 fEnd(unflatten_point(buffer)) {
818 }
819 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000820 this->INHERITED::flatten(buffer);
821 buffer.writeScalar(fStart.fX);
822 buffer.writeScalar(fStart.fY);
823 buffer.writeScalar(fEnd.fX);
824 buffer.writeScalar(fEnd.fY);
825 }
826
reed@android.com8a1c16f2008-12-17 15:59:43 +0000827private:
828 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000829 const SkPoint fStart;
830 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831};
832
reed@android.com5119bdb2009-06-12 21:27:03 +0000833bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
834 const SkMatrix& matrix) {
835 if (!this->INHERITED::setContext(device, paint, matrix)) {
836 return false;
837 }
838
839 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
840 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000841 fFlags |= SkShader::kConstInY32_Flag;
842 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
843 // only claim this if we do have a 16bit mode (i.e. none of our
844 // colors have alpha), and if we are not dithering (which obviously
845 // is not const in Y).
846 fFlags |= SkShader::kConstInY16_Flag;
847 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000848 }
849 return true;
850}
851
reed@google.com5eb158d2011-04-15 15:50:34 +0000852#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000853 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000854 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000855 SkASSERT(fi <= 0xFF); \
856 fx += dx; \
857 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000858 toggle ^= Gradient_Shader::kDitherStride32; \
reed@google.com13659f12011-04-18 19:59:38 +0000859 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000860
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000861namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000862
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000863typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000864 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000865 int toggle, int count);
866
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000867// This function is deprecated, and will be replaced by
868// shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it.
869void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
870 SkPMColor* SK_RESTRICT dstC,
871 const SkPMColor* SK_RESTRICT cache,
872 int toggle, int count) {
873 // We're a vertical gradient, so no change in a span.
874 // If colors change sharply across the gradient, dithering is
875 // insufficient (it subsamples the color space) and we need to lerp.
876 unsigned fullIndex = proc(fx);
877 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
878 sk_memset32_dither(dstC,
879 cache[toggle + fi],
880 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
881 count);
882}
883
884// Linear interpolation (lerp) is unnecessary if there are no sharp
885// discontinuities in the gradient - which must be true if there are
886// only 2 colors - but it's cheap.
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000887void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
888 SkPMColor* SK_RESTRICT dstC,
889 const SkPMColor* SK_RESTRICT cache,
890 int toggle, int count) {
891 // We're a vertical gradient, so no change in a span.
892 // If colors change sharply across the gradient, dithering is
893 // insufficient (it subsamples the color space) and we need to lerp.
894 unsigned fullIndex = proc(fx);
895 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
896 unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32;
897 SkPMColor lerp =
898 SkFastFourByteInterp(
899 cache[toggle + fi + 1],
900 cache[toggle + fi], remainder);
901 SkPMColor dlerp =
902 SkFastFourByteInterp(
903 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1],
904 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], remainder);
905 sk_memset32_dither(dstC, lerp, dlerp, count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000906}
907
908void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
909 SkPMColor* SK_RESTRICT dstC,
910 const SkPMColor* SK_RESTRICT cache,
911 int toggle, int count) {
912 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000913 range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000914
915 if ((count = range.fCount0) > 0) {
916 sk_memset32_dither(dstC,
917 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000918 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000919 count);
920 dstC += count;
921 }
922 if ((count = range.fCount1) > 0) {
923 int unroll = count >> 3;
924 fx = range.fFx1;
925 for (int i = 0; i < unroll; i++) {
926 NO_CHECK_ITER; NO_CHECK_ITER;
927 NO_CHECK_ITER; NO_CHECK_ITER;
928 NO_CHECK_ITER; NO_CHECK_ITER;
929 NO_CHECK_ITER; NO_CHECK_ITER;
930 }
931 if ((count &= 7) > 0) {
932 do {
933 NO_CHECK_ITER;
934 } while (--count != 0);
935 }
936 }
937 if ((count = range.fCount2) > 0) {
938 sk_memset32_dither(dstC,
939 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000940 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000941 count);
942 }
943}
944
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000945void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
946 SkPMColor* SK_RESTRICT dstC,
947 const SkPMColor* SK_RESTRICT cache,
948 int toggle, int count) {
949 do {
950 unsigned fi = mirror_8bits(fx >> 8);
951 SkASSERT(fi <= 0xFF);
952 fx += dx;
953 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000954 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000955 } while (--count != 0);
956}
957
958void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
959 SkPMColor* SK_RESTRICT dstC,
960 const SkPMColor* SK_RESTRICT cache,
961 int toggle, int count) {
962 do {
963 unsigned fi = repeat_8bits(fx >> 8);
964 SkASSERT(fi <= 0xFF);
965 fx += dx;
966 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000967 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000968 } while (--count != 0);
969}
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000970
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000971}
972
973void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
974 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 SkASSERT(count > 0);
976
977 SkPoint srcPt;
978 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
979 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +0000980 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +0000981#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000982 int toggle = ((x ^ y) & 1) * kDitherStride32;
reed@google.com0e734bd2011-12-08 17:24:44 +0000983#else
984 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +0000985#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986
reed@android.comc552a432009-06-12 20:02:50 +0000987 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000988 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
989 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000990 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
991
reed@android.comc552a432009-06-12 20:02:50 +0000992 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993 SkFixed dxStorage[1];
994 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
995 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000996 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
998 dx = SkScalarToFixed(fDstToIndex.getScaleX());
999 }
1000
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001001 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +00001002 if (SkFixedNearlyZero(dx)) {
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001003#ifdef SK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS
1004 if (fColorCount > 2) {
1005 shadeProc = shadeSpan_linear_vertical_lerp;
1006 } else {
1007 shadeProc = shadeSpan_linear_vertical;
1008 }
1009#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001010 shadeProc = shadeSpan_linear_vertical_lerp;
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001011#endif
reed@android.comc552a432009-06-12 20:02:50 +00001012 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001013 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +00001014 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001015 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +00001016 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001018 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001019 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +00001020 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001021 SkScalar dstX = SkIntToScalar(x);
1022 SkScalar dstY = SkIntToScalar(y);
1023 do {
1024 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1025 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1026 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001027 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001028 toggle ^= Gradient_Shader::kDitherStride32;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001029 dstX += SK_Scalar1;
1030 } while (--count != 0);
1031 }
1032}
1033
reed@google.com55b8e8c2011-01-13 16:22:35 +00001034SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +00001035 SkMatrix* matrix,
1036 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +00001037 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001038 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001039 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001040 }
1041 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001042 matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001043 matrix->preConcat(fPtsToUnit);
1044 }
1045 if (xy) {
1046 xy[0] = fTileMode;
1047 xy[1] = kClamp_TileMode;
1048 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001049 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001050}
1051
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001052SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1053 if (info) {
1054 commonAsAGradient(info);
1055 info->fPoint[0] = fStart;
1056 info->fPoint[1] = fEnd;
1057 }
1058 return kLinear_GradientType;
1059}
1060
reed@android.com3c9b2a42009-08-27 19:28:37 +00001061static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1062 int count) {
1063 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001064 *dst++ = value;
1065 count -= 1;
1066 SkTSwap(value, other);
1067 }
1068
1069 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001070
reed@android.com3c9b2a42009-08-27 19:28:37 +00001071 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001073 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075
reed@google.com5eb158d2011-04-15 15:50:34 +00001076#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001077 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001078 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001079 SkASSERT(fi < Gradient_Shader::kCache16Count); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001080 fx += dx; \
1081 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001082 toggle ^= Gradient_Shader::kDitherStride16; \
reed@google.com13659f12011-04-18 19:59:38 +00001083 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001084
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001085namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001086
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001087typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001088 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001089 int toggle, int count);
1090
1091void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1092 uint16_t* SK_RESTRICT dstC,
1093 const uint16_t* SK_RESTRICT cache,
1094 int toggle, int count) {
1095 // we're a vertical gradient, so no change in a span
1096 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001097 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001098 dither_memset16(dstC, cache[toggle + fi],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001099 cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001100
1101}
1102
1103void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1104 uint16_t* SK_RESTRICT dstC,
1105 const uint16_t* SK_RESTRICT cache,
1106 int toggle, int count) {
1107 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001108 range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001109
1110 if ((count = range.fCount0) > 0) {
1111 dither_memset16(dstC,
1112 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001113 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001114 count);
1115 dstC += count;
1116 }
1117 if ((count = range.fCount1) > 0) {
1118 int unroll = count >> 3;
1119 fx = range.fFx1;
1120 for (int i = 0; i < unroll; i++) {
1121 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1122 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1123 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1124 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1125 }
1126 if ((count &= 7) > 0) {
1127 do {
1128 NO_CHECK_ITER_16;
1129 } while (--count != 0);
1130 }
1131 }
1132 if ((count = range.fCount2) > 0) {
1133 dither_memset16(dstC,
1134 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001135 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001136 count);
1137 }
1138}
1139
1140void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1141 uint16_t* SK_RESTRICT dstC,
1142 const uint16_t* SK_RESTRICT cache,
1143 int toggle, int count) {
1144 do {
1145 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1146 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001147 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001148 fx += dx;
1149 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001150 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001151 } while (--count != 0);
1152}
1153
1154void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1155 uint16_t* SK_RESTRICT dstC,
1156 const uint16_t* SK_RESTRICT cache,
1157 int toggle, int count) {
1158 SkASSERT(proc == repeat_tileproc);
1159 do {
1160 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1161 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001162 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001163 fx += dx;
1164 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001165 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001166 } while (--count != 0);
1167}
1168}
1169
1170void Linear_Gradient::shadeSpan16(int x, int y,
1171 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172 SkASSERT(count > 0);
1173
1174 SkPoint srcPt;
1175 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1176 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001177 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001178 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001179
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001180 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001181 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1182 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001183 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1184
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001185 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001186 SkFixed dxStorage[1];
1187 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1188 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001189 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001190 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1191 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1192 }
1193
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001194 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001195 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001196 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001197 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001198 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001199 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001200 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001201 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001204 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001205 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206 SkScalar dstX = SkIntToScalar(x);
1207 SkScalar dstY = SkIntToScalar(y);
1208 do {
1209 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1210 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1211 SkASSERT(fi <= 0xFFFF);
1212
reed@android.com512a8762009-12-14 15:25:36 +00001213 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001215 toggle ^= Gradient_Shader::kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001216
1217 dstX += SK_Scalar1;
1218 } while (--count != 0);
1219 }
1220}
1221
1222///////////////////////////////////////////////////////////////////////////////
1223
1224#define kSQRT_TABLE_BITS 11
1225#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1226
1227#include "SkRadialGradient_Table.h"
1228
1229#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1230
1231#include <stdio.h>
1232
reed@google.com61eb0402011-04-15 12:11:12 +00001233void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1235
1236 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1237 SkASSERT(file);
1238 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1239
reed@google.com61eb0402011-04-15 12:11:12 +00001240 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1241 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001243 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244
1245 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1246
1247 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001248 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001250 }
1251 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001253 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001254 }
1255 ::fprintf(file, "};\n");
1256 ::fclose(file);
1257}
1258
1259#endif
1260
1261
reed@google.com61eb0402011-04-15 12:11:12 +00001262static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1263 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 SkScalar inv = SkScalarInvert(radius);
1265
1266 matrix->setTranslate(-center.fX, -center.fY);
1267 matrix->postScale(inv, inv);
1268}
1269
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001270
1271namespace {
1272
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001273typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
1274 SkScalar sfy, SkScalar sdy,
1275 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001276 int toggle, int count);
1277
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001278void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
1279 SkScalar sfy, SkScalar sdy,
1280 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001281 int toggle, int count) {
1282 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1283
1284 /* knock these down so we can pin against +- 0x7FFF, which is an
1285 immediate load, rather than 0xFFFF which is slower. This is a
1286 compromise, since it reduces our precision, but that appears
1287 to be visually OK. If we decide this is OK for all of our cases,
1288 we could (it seems) put this scale-down into fDstToIndex,
1289 to avoid having to do these extra shifts each time.
1290 */
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001291 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1292 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1293 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1294 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001295 // might perform this check for the other modes,
1296 // but the win will be a smaller % of the total
1297 if (dy == 0) {
1298 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1299 fy *= fy;
1300 do {
1301 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1302 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1303 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1304 fx += dx;
1305 *dstC++ = cache[toggle +
1306 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001307 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001308 } while (--count != 0);
1309 } else {
1310 do {
1311 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1312 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1313 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1314 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1315 fx += dx;
1316 fy += dy;
1317 *dstC++ = cache[toggle +
1318 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001319 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001320 } while (--count != 0);
1321 }
1322}
1323
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001324void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
1325 SkScalar sfy, SkScalar sdy,
1326 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001327 int toggle, int count) {
1328 do {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001329#ifdef SK_SCALAR_IS_FLOAT
1330 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1331 SkFixed dist = SkFloatToFixed(fdist);
1332#else
1333 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1334 SkFixedSquare(sfy);
1335 if (magnitudeSquared < 0) // Overflow.
1336 magnitudeSquared = SK_FixedMax;
1337 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1338#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001339 unsigned fi = mirror_tileproc(dist);
1340 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001341 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001342 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001343 sfx += sdx;
1344 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001345 } while (--count != 0);
1346}
1347
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001348void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
1349 SkScalar sfy, SkScalar sdy,
1350 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001351 int toggle, int count) {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001352 SkFixed fx = SkScalarToFixed(sfx);
1353 SkFixed dx = SkScalarToFixed(sdx);
1354 SkFixed fy = SkScalarToFixed(sfy);
1355 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001356 do {
1357 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1358 unsigned fi = repeat_tileproc(dist);
1359 SkASSERT(fi <= 0xFFFF);
1360 fx += dx;
1361 fy += dy;
1362 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001363 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001364 } while (--count != 0);
1365}
1366
1367}
1368
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369class Radial_Gradient : public Gradient_Shader {
1370public:
1371 Radial_Gradient(const SkPoint& center, SkScalar radius,
1372 const SkColor colors[], const SkScalar pos[], int colorCount,
1373 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001374 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1375 fCenter(center),
1376 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001377 {
1378 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1379 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1380
1381 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1382 }
reed@google.com61eb0402011-04-15 12:11:12 +00001383
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001384 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
1385 SK_OVERRIDE;
1386 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
1387 int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001388 SkASSERT(count > 0);
1389
1390 SkPoint srcPt;
1391 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1392 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001393 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001394 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395
reed@android.com3c9b2a42009-08-27 19:28:37 +00001396 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001397 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1398 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001399
1400 SkScalar sdx = fDstToIndex.getScaleX();
1401 SkScalar sdy = fDstToIndex.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402
reed@android.com3c9b2a42009-08-27 19:28:37 +00001403 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404 SkFixed storage[2];
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001405 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1406 &storage[0], &storage[1]);
1407 sdx = SkFixedToScalar(storage[0]);
1408 sdy = SkFixedToScalar(storage[1]);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001409 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 }
1412
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001413 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001414 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001415 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001416 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001417 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001418 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001419 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001420 }
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001421 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
1422 cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001423 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424 SkScalar dstX = SkIntToScalar(x);
1425 SkScalar dstY = SkIntToScalar(y);
1426 do {
1427 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1428 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1429 SkASSERT(fi <= 0xFFFF);
1430
1431 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001433 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434
1435 dstX += SK_Scalar1;
1436 } while (--count != 0);
1437 }
1438 }
1439
reed@google.com55b8e8c2011-01-13 16:22:35 +00001440 virtual BitmapType asABitmap(SkBitmap* bitmap,
1441 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001442 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001443 SkScalar* twoPointRadialParams)
1444 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001445 if (bitmap) {
1446 this->commonAsABitmap(bitmap);
1447 }
1448 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001449 matrix->setScale(SkIntToScalar(kGradient32Length),
1450 SkIntToScalar(kGradient32Length));
reed@google.comdc731fd2010-12-23 15:19:47 +00001451 matrix->preConcat(fPtsToUnit);
1452 }
1453 if (xy) {
1454 xy[0] = fTileMode;
1455 xy[1] = kClamp_TileMode;
1456 }
1457 return kRadial_BitmapType;
1458 }
reed@google.com7716afb2011-12-07 15:17:50 +00001459 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001460 if (info) {
1461 commonAsAGradient(info);
1462 info->fPoint[0] = fCenter;
1463 info->fRadius[0] = fRadius;
1464 }
1465 return kRadial_GradientType;
1466 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001467
djsollen@google.comba28d032012-03-26 17:57:35 +00001468 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Radial_Gradient)
1469
reed@android.com8a1c16f2008-12-17 15:59:43 +00001470protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001471 Radial_Gradient(SkFlattenableReadBuffer& buffer)
djsollen@google.com54924242012-03-29 15:18:04 +00001472 : INHERITED(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001473 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001474 fRadius(buffer.readScalar()) {
1475 }
djsollen@google.com54924242012-03-29 15:18:04 +00001476 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
1477 this->INHERITED::flatten(buffer);
1478 buffer.writeScalar(fCenter.fX);
1479 buffer.writeScalar(fCenter.fY);
1480 buffer.writeScalar(fRadius);
1481 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482
1483private:
1484 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001485 const SkPoint fCenter;
1486 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487};
1488
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001489namespace {
1490
1491inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001492 // fast, overly-conservative test: checks unit square instead
1493 // of unit circle
1494 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1495 (fx <= -SK_FixedHalf && dx <= 0);
1496 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1497 (fy <= -SK_FixedHalf && dy <= 0);
1498
1499 return xClamped || yClamped;
1500}
1501
1502// Return true if (fx * fy) is always inside the unit circle
1503// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1504// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001505inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001506 int fy, int dy, int count) {
1507 SkASSERT(count > 0);
1508 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1509 return false;
1510 }
1511 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1512 return false;
1513 }
1514 fx += (count - 1) * dx;
1515 fy += (count - 1) * dy;
1516 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1517 return false;
1518 }
1519 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1520}
1521
1522#define UNPINNED_RADIAL_STEP \
1523 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001524 *dstC++ = cache[toggle + \
1525 (sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift)]; \
1526 toggle ^= Gradient_Shader::kDitherStride32; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001527 fx += dx; \
1528 fy += dy;
1529
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001530typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1531 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001532 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001533 int count, int toggle);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001534
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001535// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001536void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
1537 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001538 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001539 int count, int toggle) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001540 // Floating point seems to be slower than fixed point,
1541 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001542 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001543 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1544 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1545 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1546 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001547 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001548 unsigned fi = Gradient_Shader::kGradient32Length;
1549 sk_memset32_dither(dstC,
1550 cache[toggle + fi],
1551 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
1552 count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001553 } else if ((count > 4) &&
1554 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1555 unsigned fi;
1556 // 4x unroll appears to be no faster than 2x unroll on Linux
1557 while (count > 1) {
1558 UNPINNED_RADIAL_STEP;
1559 UNPINNED_RADIAL_STEP;
1560 count -= 2;
1561 }
1562 if (count) {
1563 UNPINNED_RADIAL_STEP;
1564 }
1565 }
1566 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001567 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1568 if (dy == 0) {
1569 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1570 yy *= yy;
1571 do {
1572 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1573 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1574 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001575 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1576 Gradient_Shader::kSqrt32Shift)];
1577 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001578 fx += dx;
1579 } while (--count != 0);
1580 } else {
1581 do {
1582 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1583 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1584 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1585 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001586 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1587 Gradient_Shader::kSqrt32Shift)];
1588 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001589 fx += dx;
1590 fy += dy;
1591 } while (--count != 0);
1592 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001593 }
1594}
1595
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001596// Unrolling this loop doesn't seem to help (when float); we're stalling to
1597// get the results of the sqrt (?), and don't have enough extra registers to
1598// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001599void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1600 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001601 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001602 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001603 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001604#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001605 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1606 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001607#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001608 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1609 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001610 if (magnitudeSquared < 0) // Overflow.
1611 magnitudeSquared = SK_FixedMax;
1612 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001613#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001614 unsigned fi = mirror_tileproc(dist);
1615 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001616 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1617 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001618 sfx += sdx;
1619 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001620 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001621}
1622
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001623void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1624 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001625 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001626 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001627 SkFixed fx = SkScalarToFixed(sfx);
1628 SkFixed dx = SkScalarToFixed(sdx);
1629 SkFixed fy = SkScalarToFixed(sfy);
1630 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001631 do {
1632 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1633 SkFixedSquare(fy);
1634 if (magnitudeSquared < 0) // Overflow.
1635 magnitudeSquared = SK_FixedMax;
1636 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1637 unsigned fi = repeat_tileproc(dist);
1638 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001639 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1640 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001641 fx += dx;
1642 fy += dy;
1643 } while (--count != 0);
1644}
1645}
1646
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001647void Radial_Gradient::shadeSpan(int x, int y,
1648 SkPMColor* SK_RESTRICT dstC, int count) {
1649 SkASSERT(count > 0);
1650
1651 SkPoint srcPt;
1652 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1653 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001654 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001655#ifdef USE_DITHER_32BIT_GRADIENT
1656 int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32;
1657#else
1658 int toggle = 0;
1659#endif
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001660
1661 if (fDstToIndexClass != kPerspective_MatrixClass) {
1662 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1663 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001664 SkScalar sdx = fDstToIndex.getScaleX();
1665 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001666
1667 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1668 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001669 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1670 &storage[0], &storage[1]);
1671 sdx = SkFixedToScalar(storage[0]);
1672 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001673 } else {
1674 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001675 }
1676
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001677 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001678 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001679 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001680 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001681 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001682 } else {
1683 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001684 }
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001685 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001686 } else { // perspective case
1687 SkScalar dstX = SkIntToScalar(x);
1688 SkScalar dstY = SkIntToScalar(y);
1689 do {
1690 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1691 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1692 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001693 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001694 dstX += SK_Scalar1;
1695 } while (--count != 0);
1696 }
1697}
1698
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001699/* Two-point radial gradients are specified by two circles, each with a center
1700 point and radius. The gradient can be considered to be a series of
1701 concentric circles, with the color interpolated from the start circle
1702 (at t=0) to the end circle (at t=1).
1703
1704 For each point (x, y) in the span, we want to find the
1705 interpolated circle that intersects that point. The center
1706 of the desired circle (Cx, Cy) falls at some distance t
1707 along the line segment between the start point (Sx, Sy) and
1708 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001709
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001710 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1711 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001712
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001713 The radius of the desired circle (r) is also a linear interpolation t
1714 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001715
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001716 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001717
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001718 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001719
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001720 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001721
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001722 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001723
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001724 (x - ((1 - t) * Sx + t * Ex))^2
1725 + (y - ((1 - t) * Sy + t * Ey))^2
1726 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001727
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001728 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001729
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001730 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1731 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1732 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001733
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001734 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1735
1736 [Dx^2 + Dy^2 - Dr^2)] * t^2
1737 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1738 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001739
1740 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001741 possible circles on which the point may fall. Solving for t yields
1742 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001743
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001744 If a<0, the start circle is entirely contained in the
1745 end circle, and one of the roots will be <0 or >1 (off the line
1746 segment). If a>0, the start circle falls at least partially
1747 outside the end circle (or vice versa), and the gradient
1748 defines a "tube" where a point may be on one circle (on the
1749 inside of the tube) or the other (outside of the tube). We choose
1750 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001751
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001752 In order to keep the math to within the limits of fixed point,
1753 we divide the entire quadratic by Dr^2, and replace
1754 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001755
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001756 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1757 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1758 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001759
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001760 (x' and y' are computed by appending the subtract and scale to the
1761 fDstToIndex matrix in the constructor).
1762
1763 Since the 'A' component of the quadratic is independent of x' and y', it
1764 is precomputed in the constructor. Since the 'B' component is linear in
1765 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001766 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001767 a perspective projection), it must be computed in the loop.
1768
1769*/
1770
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001771namespace {
1772
1773inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1774 SkScalar sr2d2, SkScalar foura,
1775 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001776 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001777 if (0 == foura) {
1778 return SkScalarToFixed(SkScalarDiv(-c, b));
1779 }
1780
reed@google.com84e9c082011-04-13 17:44:24 +00001781 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001782 if (discrim < 0) {
1783 discrim = -discrim;
1784 }
reed@google.com84e9c082011-04-13 17:44:24 +00001785 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1786 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001787 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001788 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001789 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001790 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001791 }
reed@google.com84e9c082011-04-13 17:44:24 +00001792 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001793}
1794
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001795typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1796 SkScalar fy, SkScalar dy,
1797 SkScalar b, SkScalar db,
1798 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001799 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001800 int count);
1801
1802void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1803 SkScalar fy, SkScalar dy,
1804 SkScalar b, SkScalar db,
1805 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001806 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001807 int count) {
1808 for (; count > 0; --count) {
1809 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1810 fOneOverTwoA, posRoot);
1811 SkFixed index = SkClampMax(t, 0xFFFF);
1812 SkASSERT(index <= 0xFFFF);
1813 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1814 fx += dx;
1815 fy += dy;
1816 b += db;
1817 }
1818}
1819void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1820 SkScalar fy, SkScalar dy,
1821 SkScalar b, SkScalar db,
1822 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001823 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001824 int count) {
1825 for (; count > 0; --count) {
1826 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1827 fOneOverTwoA, posRoot);
1828 SkFixed index = mirror_tileproc(t);
1829 SkASSERT(index <= 0xFFFF);
1830 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1831 fx += dx;
1832 fy += dy;
1833 b += db;
1834 }
1835}
1836
1837void shadeSpan_twopoint_repeat(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 for (; count > 0; --count) {
1844 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1845 fOneOverTwoA, posRoot);
1846 SkFixed index = repeat_tileproc(t);
1847 SkASSERT(index <= 0xFFFF);
1848 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1849 fx += dx;
1850 fy += dy;
1851 b += db;
1852 }
1853}
1854
1855
1856
1857}
1858
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001859class Two_Point_Radial_Gradient : public Gradient_Shader {
1860public:
1861 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1862 const SkPoint& end, SkScalar endRadius,
1863 const SkColor colors[], const SkScalar pos[],
1864 int colorCount, SkShader::TileMode mode,
1865 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001866 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1867 fCenter1(start),
1868 fCenter2(end),
1869 fRadius1(startRadius),
1870 fRadius2(endRadius) {
1871 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001872 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001873
1874 virtual BitmapType asABitmap(SkBitmap* bitmap,
1875 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001876 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001877 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001878 if (bitmap) {
1879 this->commonAsABitmap(bitmap);
1880 }
1881 SkScalar diffL = 0; // just to avoid gcc warning
1882 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001883 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001884 SkScalarSquare(fDiff.fY));
1885 }
1886 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001887 if (diffL) {
1888 SkScalar invDiffL = SkScalarInvert(diffL);
1889 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1890 SkScalarMul(invDiffL, fDiff.fX));
1891 } else {
1892 matrix->reset();
1893 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001894 matrix->preConcat(fPtsToUnit);
1895 }
1896 if (xy) {
1897 xy[0] = fTileMode;
1898 xy[1] = kClamp_TileMode;
1899 }
1900 if (NULL != twoPointRadialParams) {
1901 twoPointRadialParams[0] = diffL;
1902 twoPointRadialParams[1] = fStartRadius;
1903 twoPointRadialParams[2] = fDiffRadius;
1904 }
1905 return kTwoPointRadial_BitmapType;
1906 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001907
reed@google.com8e6d9142011-12-07 15:30:34 +00001908 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001909 if (info) {
1910 commonAsAGradient(info);
1911 info->fPoint[0] = fCenter1;
1912 info->fPoint[1] = fCenter2;
1913 info->fRadius[0] = fRadius1;
1914 info->fRadius[1] = fRadius2;
1915 }
1916 return kRadial2_GradientType;
1917 }
1918
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001919 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1920 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001921 SkASSERT(count > 0);
1922
1923 // Zero difference between radii: fill with transparent black.
1924 if (fDiffRadius == 0) {
1925 sk_bzero(dstC, count * sizeof(*dstC));
1926 return;
1927 }
1928 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1929 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001930 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001931
1932 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001933 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001934 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001935 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001936 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1937 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001938 SkScalar dx, fx = srcPt.fX;
1939 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001940
reed@google.com61eb0402011-04-15 12:11:12 +00001941 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001942 SkFixed fixedX, fixedY;
1943 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1944 dx = SkFixedToScalar(fixedX);
1945 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001946 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001947 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001948 dx = fDstToIndex.getScaleX();
1949 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001950 }
reed@google.com84e9c082011-04-13 17:44:24 +00001951 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1952 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1953 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1954 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001955
1956 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00001957 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001958 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00001959 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001960 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00001961 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001962 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001963 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001964 (*shadeProc)(fx, dx, fy, dy, b, db,
1965 fSr2D2, foura, fOneOverTwoA, posRoot,
1966 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00001967 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001968 SkScalar dstX = SkIntToScalar(x);
1969 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001970 for (; count > 0; --count) {
1971 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001972 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001973 SkScalar fx = srcPt.fX;
1974 SkScalar fy = srcPt.fY;
1975 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1976 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001977 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1978 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001979 SkFixed index = proc(t);
1980 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001981 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00001982 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001983 }
1984 }
1985 }
1986
reed@android.com6c59a172009-09-22 20:24:05 +00001987 virtual bool setContext(const SkBitmap& device,
1988 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00001989 const SkMatrix& matrix) SK_OVERRIDE {
1990 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00001991 return false;
1992 }
1993
1994 // we don't have a span16 proc
1995 fFlags &= ~kHasSpan16_Flag;
1996 return true;
1997 }
1998
djsollen@google.com54924242012-03-29 15:18:04 +00001999 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Two_Point_Radial_Gradient)
2000
2001protected:
2002 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
2003 : INHERITED(buffer),
2004 fCenter1(unflatten_point(buffer)),
2005 fCenter2(unflatten_point(buffer)),
2006 fRadius1(buffer.readScalar()),
2007 fRadius2(buffer.readScalar()) {
2008 init();
2009 };
2010
2011 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00002012 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002013 buffer.writeScalar(fCenter1.fX);
2014 buffer.writeScalar(fCenter1.fY);
2015 buffer.writeScalar(fCenter2.fX);
2016 buffer.writeScalar(fCenter2.fY);
2017 buffer.writeScalar(fRadius1);
2018 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002019 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002020
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002021private:
2022 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002023 const SkPoint fCenter1;
2024 const SkPoint fCenter2;
2025 const SkScalar fRadius1;
2026 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002027 SkPoint fDiff;
2028 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002029
2030 void init() {
2031 fDiff = fCenter1 - fCenter2;
2032 fDiffRadius = fRadius2 - fRadius1;
2033 SkScalar inv = SkScalarInvert(fDiffRadius);
2034 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2035 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2036 fStartRadius = SkScalarMul(fRadius1, inv);
2037 fSr2D2 = SkScalarSquare(fStartRadius);
2038 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002039 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002040
2041 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2042 fPtsToUnit.postScale(inv, inv);
2043 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002044};
2045
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046///////////////////////////////////////////////////////////////////////////////
2047
2048class Sweep_Gradient : public Gradient_Shader {
2049public:
2050 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2051 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002052 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2053 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054 {
2055 fPtsToUnit.setTranslate(-cx, -cy);
2056 }
reed@google.com7716afb2011-12-07 15:17:50 +00002057 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2058 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002059
2060 virtual BitmapType asABitmap(SkBitmap* bitmap,
2061 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002062 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002063 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002064 if (bitmap) {
2065 this->commonAsABitmap(bitmap);
2066 }
2067 if (matrix) {
2068 *matrix = fPtsToUnit;
2069 }
2070 if (xy) {
2071 xy[0] = fTileMode;
2072 xy[1] = kClamp_TileMode;
2073 }
2074 return kSweep_BitmapType;
2075 }
2076
reed@google.com7716afb2011-12-07 15:17:50 +00002077 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002078 if (info) {
2079 commonAsAGradient(info);
2080 info->fPoint[0] = fCenter;
2081 }
2082 return kSweep_GradientType;
2083 }
2084
djsollen@google.comba28d032012-03-26 17:57:35 +00002085 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Sweep_Gradient)
2086
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002088 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
djsollen@google.com54924242012-03-29 15:18:04 +00002089 : INHERITED(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002090 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002091 }
djsollen@google.com54924242012-03-29 15:18:04 +00002092 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
2093 this->INHERITED::flatten(buffer);
2094 buffer.writeScalar(fCenter.fX);
2095 buffer.writeScalar(fCenter.fY);
2096 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002097
reed@android.com8a1c16f2008-12-17 15:59:43 +00002098private:
2099 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002100 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002101};
2102
2103#ifdef COMPUTE_SWEEP_TABLE
2104#define PI 3.14159265
2105static bool gSweepTableReady;
2106static uint8_t gSweepTable[65];
2107
2108/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2109 We scale the results to [0..32]
2110*/
reed@google.com61eb0402011-04-15 12:11:12 +00002111static const uint8_t* build_sweep_table() {
2112 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002113 const int N = 65;
2114 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002115
reed@android.com8a1c16f2008-12-17 15:59:43 +00002116 for (int i = 0; i < N; i++)
2117 {
2118 double arg = i / DENOM;
2119 double v = atan(arg);
2120 int iv = (int)round(v * DENOM * 2 / PI);
2121// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2122 printf("%d, ", iv);
2123 gSweepTable[i] = iv;
2124 }
2125 gSweepTableReady = true;
2126 }
2127 return gSweepTable;
2128}
2129#else
2130static const uint8_t gSweepTable[] = {
2131 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2132 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2133 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2134 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2135 32
2136};
2137static const uint8_t* build_sweep_table() { return gSweepTable; }
2138#endif
2139
2140// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2141// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2142// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2143
2144//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002145static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 SkASSERT(numer <= denom);
2147 SkASSERT(numer > 0);
2148 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002149
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 int nbits = SkCLZ(numer);
2151 int dbits = SkCLZ(denom);
2152 int bits = 6 - nbits + dbits;
2153 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002154
reed@google.com61eb0402011-04-15 12:11:12 +00002155 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002157 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158
2159 denom <<= dbits - 1;
2160 numer <<= nbits - 1;
2161
2162 unsigned result = 0;
2163
2164 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002165 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002167 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002169 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002170
reed@android.com8a1c16f2008-12-17 15:59:43 +00002171 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002172 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002173 // make room for the rest of the answer bits
2174 result <<= bits;
2175 switch (bits) {
2176 case 6:
2177 if ((numer = (numer << 1) - denom) >= 0)
2178 result |= 32;
2179 else
2180 numer += denom;
2181 case 5:
2182 if ((numer = (numer << 1) - denom) >= 0)
2183 result |= 16;
2184 else
2185 numer += denom;
2186 case 4:
2187 if ((numer = (numer << 1) - denom) >= 0)
2188 result |= 8;
2189 else
2190 numer += denom;
2191 case 3:
2192 if ((numer = (numer << 1) - denom) >= 0)
2193 result |= 4;
2194 else
2195 numer += denom;
2196 case 2:
2197 if ((numer = (numer << 1) - denom) >= 0)
2198 result |= 2;
2199 else
2200 numer += denom;
2201 case 1:
2202 default: // not strictly need, but makes GCC make better ARM code
2203 if ((numer = (numer << 1) - denom) >= 0)
2204 result |= 1;
2205 else
2206 numer += denom;
2207 }
2208 }
2209 return result;
2210}
2211
2212// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002213static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002214#ifdef SK_DEBUG
2215 {
2216 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002217 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002218 gOnce = true;
2219 SkASSERT(div_64(55, 55) == 64);
2220 SkASSERT(div_64(128, 256) == 32);
2221 SkASSERT(div_64(2326528, 4685824) == 31);
2222 SkASSERT(div_64(753664, 5210112) == 9);
2223 SkASSERT(div_64(229376, 4882432) == 3);
2224 SkASSERT(div_64(2, 64) == 2);
2225 SkASSERT(div_64(1, 64) == 1);
2226 // test that we handle underflow correctly
2227 SkASSERT(div_64(12345, 0x54321234) == 0);
2228 }
2229 }
2230#endif
2231
2232 SkASSERT(y > 0 && x > 0);
2233 const uint8_t* table = build_sweep_table();
2234
2235 unsigned result;
2236 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002237 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002238 // first part of the atan(v) = PI/2 - atan(1/v) identity
2239 // since our div_64 and table want v <= 1, where v = y/x
2240 SkTSwap<SkFixed>(x, y);
2241 }
2242
2243 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002244
reed@android.com8a1c16f2008-12-17 15:59:43 +00002245#ifdef SK_DEBUG
2246 {
2247 unsigned result2 = SkDivBits(y, x, 6);
2248 SkASSERT(result2 == result ||
2249 (result == 1 && result2 == 0));
2250 }
2251#endif
2252
2253 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2254 result = table[result];
2255
reed@google.com61eb0402011-04-15 12:11:12 +00002256 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002257 // complete the atan(v) = PI/2 - atan(1/v) identity
2258 result = 64 - result;
2259 // pin to 63
2260 result -= result >> 6;
2261 }
2262
2263 SkASSERT(result <= 63);
2264 return result;
2265}
2266
2267// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002268#ifdef SK_SCALAR_IS_FLOAT
2269static unsigned SkATan2_255(float y, float x) {
2270 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2271 static const float g255Over2PI = 40.584510488433314f;
2272
2273 float result = sk_float_atan2(y, x);
2274 if (result < 0) {
2275 result += 2 * SK_ScalarPI;
2276 }
2277 SkASSERT(result >= 0);
2278 // since our value is always >= 0, we can cast to int, which is faster than
2279 // calling floorf()
2280 int ir = (int)(result * g255Over2PI);
2281 SkASSERT(ir >= 0 && ir <= 255);
2282 return ir;
2283}
2284#else
reed@google.com61eb0402011-04-15 12:11:12 +00002285static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2286 if (x == 0) {
2287 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002288 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002289 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002290 return y < 0 ? 192 : 64;
2291 }
reed@google.com61eb0402011-04-15 12:11:12 +00002292 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002293 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002294 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002295
reed@android.com8a1c16f2008-12-17 15:59:43 +00002296 /* Find the right quadrant for x,y
2297 Since atan_0_90 only handles the first quadrant, we rotate x,y
2298 appropriately before calling it, and then add the right amount
2299 to account for the real quadrant.
2300 quadrant 0 : add 0 | x > 0 && y > 0
2301 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2302 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2303 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002304
reed@android.com8a1c16f2008-12-17 15:59:43 +00002305 map x<0 to (1 << 6)
2306 map y<0 to (3 << 6)
2307 add = map_x ^ map_y
2308 */
2309 int xsign = x >> 31;
2310 int ysign = y >> 31;
2311 int add = ((-xsign) ^ (ysign & 3)) << 6;
2312
2313#ifdef SK_DEBUG
2314 if (0 == add)
2315 SkASSERT(x > 0 && y > 0);
2316 else if (64 == add)
2317 SkASSERT(x < 0 && y > 0);
2318 else if (128 == add)
2319 SkASSERT(x < 0 && y < 0);
2320 else if (192 == add)
2321 SkASSERT(x > 0 && y < 0);
2322 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002323 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002324#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002325
reed@android.com8a1c16f2008-12-17 15:59:43 +00002326 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2327 where we need to rotate x,y by 90 or -90
2328 */
2329 x = (x ^ xsign) - xsign;
2330 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002331 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002332 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002333 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002334
2335 unsigned result = add + atan_0_90(y, x);
2336 SkASSERT(result < 256);
2337 return result;
2338}
reed@google.com51baf5a2011-09-21 13:38:36 +00002339#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002340
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002341void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2342 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002343 SkMatrix::MapXYProc proc = fDstToIndexProc;
2344 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002345 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002346 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002347
reed@google.com61eb0402011-04-15 12:11:12 +00002348 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002349 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2350 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002351 SkScalar dx, fx = srcPt.fX;
2352 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002353
reed@google.com61eb0402011-04-15 12:11:12 +00002354 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002355 SkFixed storage[2];
2356 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2357 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002358 dx = SkFixedToScalar(storage[0]);
2359 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002360 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002361 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002362 dx = matrix.getScaleX();
2363 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002364 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002365
reed@google.com61eb0402011-04-15 12:11:12 +00002366 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002367 *dstC++ = cache[SkATan2_255(fy, fx)];
2368 fx += dx;
2369 fy += dy;
2370 }
reed@google.com61eb0402011-04-15 12:11:12 +00002371 } else { // perspective case
2372 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002373 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002374 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2375 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002376 }
2377 }
2378}
2379
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002380void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2381 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002382 SkMatrix::MapXYProc proc = fDstToIndexProc;
2383 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002384 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002385 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002386 SkPoint srcPt;
2387
reed@google.com61eb0402011-04-15 12:11:12 +00002388 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002389 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2390 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002391 SkScalar dx, fx = srcPt.fX;
2392 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002393
reed@google.com61eb0402011-04-15 12:11:12 +00002394 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002395 SkFixed storage[2];
2396 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2397 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002398 dx = SkFixedToScalar(storage[0]);
2399 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002400 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002401 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002402 dx = matrix.getScaleX();
2403 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002404 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002405
reed@google.com61eb0402011-04-15 12:11:12 +00002406 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002407 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2408 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002409 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002410 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,
2416 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002417
reed@google.com51baf5a2011-09-21 13:38:36 +00002418 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002419 index >>= (8 - kCache16Bits);
2420 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002421 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002422 }
2423 }
2424}
2425
reed@google.com61eb0402011-04-15 12:11:12 +00002426///////////////////////////////////////////////////////////////////////////////
2427///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002428
2429// assumes colors is SkColor* and pos is SkScalar*
2430#define EXPAND_1_COLOR(count) \
2431 SkColor tmp[2]; \
2432 do { \
2433 if (1 == count) { \
2434 tmp[0] = tmp[1] = colors[0]; \
2435 colors = tmp; \
2436 pos = NULL; \
2437 count = 2; \
2438 } \
2439 } while (0)
2440
reed@google.com61eb0402011-04-15 12:11:12 +00002441SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2442 const SkColor colors[],
2443 const SkScalar pos[], int colorCount,
2444 SkShader::TileMode mode,
2445 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002446 if (NULL == pts || NULL == colors || colorCount < 1) {
2447 return NULL;
2448 }
2449 EXPAND_1_COLOR(colorCount);
2450
reed@android.comab840b82009-07-01 17:00:03 +00002451 return SkNEW_ARGS(Linear_Gradient,
2452 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002453}
2454
reed@google.com61eb0402011-04-15 12:11:12 +00002455SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2456 const SkColor colors[],
2457 const SkScalar pos[], int colorCount,
2458 SkShader::TileMode mode,
2459 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002460 if (radius <= 0 || NULL == colors || colorCount < 1) {
2461 return NULL;
2462 }
2463 EXPAND_1_COLOR(colorCount);
2464
reed@android.comab840b82009-07-01 17:00:03 +00002465 return SkNEW_ARGS(Radial_Gradient,
2466 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002467}
2468
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002469SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2470 SkScalar startRadius,
2471 const SkPoint& end,
2472 SkScalar endRadius,
2473 const SkColor colors[],
2474 const SkScalar pos[],
2475 int colorCount,
2476 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002477 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002478 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2479 return NULL;
2480 }
2481 EXPAND_1_COLOR(colorCount);
2482
2483 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002484 (start, startRadius, end, endRadius, colors, pos,
2485 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002486}
2487
reed@android.com8a1c16f2008-12-17 15:59:43 +00002488SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2489 const SkColor colors[],
2490 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002491 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002492 if (NULL == colors || count < 1) {
2493 return NULL;
2494 }
2495 EXPAND_1_COLOR(count);
2496
2497 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2498}
2499
caryclark@google.comd26147a2011-12-15 14:16:43 +00002500SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2501 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2502 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002503 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002504 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2505SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END