blob: 240a7a4b2582eefc3a4945ce10281f78e219f444 [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
52///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000053
54typedef SkFixed (*TileProc)(SkFixed);
55
reed@android.com41bccf52009-04-03 13:33:51 +000056static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 return SkClampMax(x, 0xFFFF);
58}
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@android.com41bccf52009-04-03 13:33:51 +000064static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 int s = x << 15 >> 31;
66 return (x ^ s) & 0xFFFF;
67}
68
69static const TileProc gTileProcs[] = {
70 clamp_tileproc,
71 repeat_tileproc,
72 mirror_tileproc
73};
74
reed@google.com61eb0402011-04-15 12:11:12 +000075///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000076
reed@android.com200645d2009-12-14 16:41:57 +000077static inline int repeat_bits(int x, const int bits) {
78 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000079}
80
reed@android.com200645d2009-12-14 16:41:57 +000081static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000082#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000083 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000085 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000086#else
reed@android.com200645d2009-12-14 16:41:57 +000087 int s = x << (31 - bits) >> 31;
88 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000089#endif
90}
91
reed@android.com41bccf52009-04-03 13:33:51 +000092static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000093 return x & 0xFF;
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
reed@google.com61eb0402011-04-15 12:11:12 +0000108///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109
110class Gradient_Shader : public SkShader {
111public:
112 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000113 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 virtual ~Gradient_Shader();
115
116 // overrides
reed@google.com7716afb2011-12-07 15:17:50 +0000117 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
118 virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000119 virtual bool isOpaque() const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000121 enum {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000122 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
123 /// it, use a larger cache.
124 kCache16Bits = 8,
125 kGradient16Length = (1 << kCache16Bits),
126 /// Each cache gets 1 extra entry at the end so we don't have to
127 /// test for end-of-cache in lerps. This is also the value used
128 /// to stride *writes* into the dither cache; it must not be zero.
129 /// Total space for a cache is 2x kCache16Count entries: one
130 /// regular cache, one for dithering.
131 kCache16Count = kGradient16Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000132 kCache16Shift = 16 - kCache16Bits,
133 kSqrt16Shift = 8 - kCache16Bits,
134
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000135 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
136 /// it, use a larger cache.
137 kCache32Bits = 8,
138 kGradient32Length = (1 << kCache32Bits),
139 /// Each cache gets 1 extra entry at the end so we don't have to
140 /// test for end-of-cache in lerps. This is also the value used
141 /// to stride *writes* into the dither cache; it must not be zero.
142 /// Total space for a cache is 2x kCache32Count entries: one
143 /// regular cache, one for dithering.
144 kCache32Count = kGradient32Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000145 kCache32Shift = 16 - kCache32Bits,
146 kSqrt32Shift = 8 - kCache32Bits,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000147
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000148 /// This value is used to *read* the dither cache; it may be 0
149 /// if dithering is disabled.
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000150#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000151 kDitherStride32 = kCache32Count,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000152#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000153 kDitherStride32 = 0,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000154#endif
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000155 kDitherStride16 = kCache16Count,
156 kLerpRemainderMask32 = (1 << (16 - kCache32Bits)) - 1
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000157 };
158
159
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160protected:
161 Gradient_Shader(SkFlattenableReadBuffer& );
162 SkUnitMapper* fMapper;
163 SkMatrix fPtsToUnit; // set by subclass
164 SkMatrix fDstToIndex;
165 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166 TileMode fTileMode;
167 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000168 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169 uint8_t fDstToIndexClass;
170 uint8_t fFlags;
171
172 struct Rec {
173 SkFixed fPos; // 0...1
174 uint32_t fScale; // (1 << 24) / range
175 };
176 Rec* fRecs;
177
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000179 const uint16_t* getCache16() const;
180 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000181
reed@google.com7c2f27d2011-03-07 19:29:00 +0000182 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000183 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000184
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185private:
186 enum {
187 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
188
reed@android.com1c12abe2009-07-02 15:01:02 +0000189 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000190 };
191 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000192 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
193 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194
reed@google.com7c2f27d2011-03-07 19:29:00 +0000195 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
196 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000197
reed@google.com7c2f27d2011-03-07 19:29:00 +0000198 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
199 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000200 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 +0000201
reed@android.com512a8762009-12-14 15:25:36 +0000202 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000203 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
204 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000205 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000206 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000207
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208 typedef SkShader INHERITED;
209};
210
reed@android.com41bccf52009-04-03 13:33:51 +0000211static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 SkASSERT(x >= 0 && x <= SK_Scalar1);
213
214#ifdef SK_SCALAR_IS_FLOAT
215 return (unsigned)(x * 0xFFFF);
216#else
217 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
218#endif
219}
220
reed@android.com41bccf52009-04-03 13:33:51 +0000221Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
222 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223 SkASSERT(colorCount > 1);
224
225 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
226
227 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000228 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000229
reed@android.com8a1c16f2008-12-17 15:59:43 +0000230 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
231 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
232 fTileMode = mode;
233 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000234
reed@android.com41bccf52009-04-03 13:33:51 +0000235 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000236 fCache32 = NULL;
237 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000238
reed@android.com41bccf52009-04-03 13:33:51 +0000239 /* Note: we let the caller skip the first and/or last position.
240 i.e. pos[0] = 0.3, pos[1] = 0.7
241 In these cases, we insert dummy entries to ensure that the final data
242 will be bracketed by [0, 1].
243 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
244
245 Thus colorCount (the caller's value, and fColorCount (our value) may
246 differ by up to 2. In the above example:
247 colorCount = 2
248 fColorCount = 4
249 */
250 fColorCount = colorCount;
251 // check if we need to add in dummy start and/or end position/colors
252 bool dummyFirst = false;
253 bool dummyLast = false;
254 if (pos) {
255 dummyFirst = pos[0] != 0;
256 dummyLast = pos[colorCount - 1] != SK_Scalar1;
257 fColorCount += dummyFirst + dummyLast;
258 }
259
260 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000261 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000262 fOrigColors = reinterpret_cast<SkColor*>(
263 sk_malloc_throw(size * fColorCount));
264 }
265 else {
266 fOrigColors = fStorage;
267 }
268
269 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000270 {
reed@android.com41bccf52009-04-03 13:33:51 +0000271 SkColor* origColors = fOrigColors;
272 if (dummyFirst) {
273 *origColors++ = colors[0];
274 }
275 memcpy(origColors, colors, colorCount * sizeof(SkColor));
276 if (dummyLast) {
277 origColors += colorCount;
278 *origColors = colors[colorCount - 1];
279 }
280 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000281
reed@android.com1c12abe2009-07-02 15:01:02 +0000282 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000283 if (fColorCount > 2) {
284 Rec* recs = fRecs;
285 recs->fPos = 0;
286 // recs->fScale = 0; // unused;
287 recs += 1;
288 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289 /* We need to convert the user's array of relative positions into
290 fixed-point positions and scale factors. We need these results
291 to be strictly monotonic (no two values equal or out of order).
292 Hence this complex loop that just jams a zero for the scale
293 value if it sees a segment out of order, and it assures that
294 we start at 0 and end at 1.0
295 */
296 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000297 int startIndex = dummyFirst ? 0 : 1;
298 int count = colorCount + dummyLast;
299 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000300 // force the last value to be 1.0
301 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000302 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000304 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306 }
reed@android.com41bccf52009-04-03 13:33:51 +0000307 // pin curr withing range
308 if (curr < 0) {
309 curr = 0;
310 } else if (curr > SK_Fixed1) {
311 curr = SK_Fixed1;
312 }
313 recs->fPos = curr;
314 if (curr > prev) {
315 recs->fScale = (1 << 24) / (curr - prev);
316 } else {
317 recs->fScale = 0; // ignore this segment
318 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319 // get ready for the next value
320 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000321 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 }
reed@android.com41bccf52009-04-03 13:33:51 +0000323 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000324 SkFixed dp = SK_Fixed1 / (colorCount - 1);
325 SkFixed p = dp;
326 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000327 for (int i = 1; i < colorCount; i++) {
328 recs->fPos = p;
329 recs->fScale = scale;
330 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 p += dp;
332 }
333 }
334 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000335 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336}
337
338Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000339 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340 fCacheAlpha = 256;
341
342 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
343
344 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000345 fCache32 = NULL;
346 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347
reed@android.com41bccf52009-04-03 13:33:51 +0000348 int colorCount = fColorCount = buffer.readU32();
349 if (colorCount > kColorStorageCount) {
350 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
351 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
352 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000353 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000354 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356
357 fTileMode = (TileMode)buffer.readU8();
358 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000359 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 if (colorCount > 2) {
361 Rec* recs = fRecs;
362 recs[0].fPos = 0;
363 for (int i = 1; i < colorCount; i++) {
364 recs[i].fPos = buffer.readS32();
365 recs[i].fScale = buffer.readU32();
366 }
367 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000368 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000369 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000370}
371
reed@android.com41bccf52009-04-03 13:33:51 +0000372Gradient_Shader::~Gradient_Shader() {
373 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000374 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000375 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000376 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000377 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000378 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000379 }
reed@google.com82065d62011-02-07 15:30:46 +0000380 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000381}
382
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000383void Gradient_Shader::initCommon() {
384 fFlags = 0;
385 unsigned colorAlpha = 0xFF;
386 for (int i = 0; i < fColorCount; i++) {
387 colorAlpha &= SkColorGetA(fOrigColors[i]);
388 }
389 fColorsAreOpaque = colorAlpha == 0xFF;
390}
391
reed@android.com41bccf52009-04-03 13:33:51 +0000392void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000393 this->INHERITED::flatten(buffer);
394 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000395 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000396 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
397 buffer.write8(fTileMode);
398 if (fColorCount > 2) {
399 Rec* recs = fRecs;
400 for (int i = 1; i < fColorCount; i++) {
401 buffer.write32(recs[i].fPos);
402 buffer.write32(recs[i].fScale);
403 }
404 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000405 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406}
407
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000408bool Gradient_Shader::isOpaque() const {
409 return fColorsAreOpaque;
410}
411
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412bool Gradient_Shader::setContext(const SkBitmap& device,
413 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000414 const SkMatrix& matrix) {
415 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000416 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000417 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000418
419 const SkMatrix& inverse = this->getTotalInverse();
420
421 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
422 return false;
423 }
424
425 fDstToIndexProc = fDstToIndex.getMapXYProc();
426 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
427
428 // now convert our colors in to PMColors
429 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430
431 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000432 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433 fFlags |= kOpaqueAlpha_Flag;
434 }
435 // we can do span16 as long as our individual colors are opaque,
436 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000437 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000438 fFlags |= kHasSpan16_Flag;
439 }
440
reed@google.com95eed982011-07-05 17:01:56 +0000441 this->setCacheAlpha(paintAlpha);
442 return true;
443}
444
445void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000446 // if the new alpha differs from the previous time we were called, inval our cache
447 // this will trigger the cache to be rebuilt.
448 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000449 if (fCacheAlpha != alpha) {
450 fCache16 = NULL; // inval the cache
451 fCache32 = NULL; // inval the cache
452 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000453 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000454 if (fCache32PixelRef) {
455 fCache32PixelRef->notifyPixelsChanged();
456 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000457 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000458}
459
reed@android.com41bccf52009-04-03 13:33:51 +0000460static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000461 SkASSERT(a == SkToU8(a));
462 SkASSERT(b == SkToU8(b));
463 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000464 return a + ((b - a) * scale >> 8);
465}
466
reed@android.com41bccf52009-04-03 13:33:51 +0000467static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
468 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000469#if 0
470 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
471 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
472 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
473 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
474
475 return SkPackARGB32(a, r, g, b);
476#else
477 int otherBlend = 256 - blend;
478
479#if 0
480 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
481 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
482 SkASSERT((t0 & t1) == 0);
483 return t0 | t1;
484#else
485 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
486 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
487#endif
488
489#endif
490}
491
492#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
493
reed@android.com41bccf52009-04-03 13:33:51 +0000494/** We take the original colors, not our premultiplied PMColors, since we can
495 build a 16bit table as long as the original colors are opaque, even if the
496 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000497*/
reed@android.com512a8762009-12-14 15:25:36 +0000498void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
499 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000500 SkASSERT(count > 1);
501 SkASSERT(SkColorGetA(c0) == 0xFF);
502 SkASSERT(SkColorGetA(c1) == 0xFF);
503
504 SkFixed r = SkColorGetR(c0);
505 SkFixed g = SkColorGetG(c0);
506 SkFixed b = SkColorGetB(c0);
507
508 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
509 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
510 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
511
512 r = SkIntToFixed(r) + 0x8000;
513 g = SkIntToFixed(g) + 0x8000;
514 b = SkIntToFixed(b) + 0x8000;
515
516 do {
517 unsigned rr = r >> 16;
518 unsigned gg = g >> 16;
519 unsigned bb = b >> 16;
520 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000521 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 cache += 1;
523 r += dr;
524 g += dg;
525 b += db;
526 } while (--count != 0);
527}
528
reed@google.com55b8e8c2011-01-13 16:22:35 +0000529/*
530 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
531 * semantics of how we 2x2 dither 32->16
532 */
533static inline U8CPU dither_fixed_to_8(SkFixed n) {
534 n >>= 8;
535 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
536}
537
538/*
539 * For dithering with premultiply, we want to ceiling the alpha component,
540 * to ensure that it is always >= any color component.
541 */
542static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
543 n >>= 8;
544 return ((n << 1) - (n | (n >> 8))) >> 8;
545}
546
547void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
548 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000549 SkASSERT(count > 1);
550
reed@android.com1c12abe2009-07-02 15:01:02 +0000551 // need to apply paintAlpha to our two endpoints
552 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
553 SkFixed da;
554 {
555 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
556 da = SkIntToFixed(tmp - a) / (count - 1);
557 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000558
reed@android.com1c12abe2009-07-02 15:01:02 +0000559 SkFixed r = SkColorGetR(c0);
560 SkFixed g = SkColorGetG(c0);
561 SkFixed b = SkColorGetB(c0);
562 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
563 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
564 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000565
566 a = SkIntToFixed(a) + 0x8000;
567 r = SkIntToFixed(r) + 0x8000;
568 g = SkIntToFixed(g) + 0x8000;
569 b = SkIntToFixed(b) + 0x8000;
570
571 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000572 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000573 cache[kCache32Count] =
574 SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
575 dither_fixed_to_8(r),
576 dither_fixed_to_8(g),
577 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000578 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000579 a += da;
580 r += dr;
581 g += dg;
582 b += db;
583 } while (--count != 0);
584}
585
reed@android.com41bccf52009-04-03 13:33:51 +0000586static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000587 SkASSERT((unsigned)x <= SK_Fixed1);
588 return x - (x >> 16);
589}
590
reed@android.com200645d2009-12-14 16:41:57 +0000591static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000592 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000593 if (6 == bits) {
594 return (x << 10) | (x << 4) | (x >> 2);
595 }
596 if (8 == bits) {
597 return (x << 8) | x;
598 }
599 sk_throw();
600 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601}
602
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000603/** We duplicate the last value in each half of the cache so that
604 interpolation doesn't have to special-case being at the last point.
605*/
606static void complete_16bit_cache(uint16_t* cache, int stride) {
607 cache[stride - 1] = cache[stride - 2];
608 cache[2 * stride - 1] = cache[2 * stride - 2];
609}
610
reed@google.com7c2f27d2011-03-07 19:29:00 +0000611const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000612 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000613 // double the count for dither entries
614 const int entryCount = kCache16Count * 2;
615 const size_t allocSize = sizeof(uint16_t) * entryCount;
616
reed@android.com3c9b2a42009-08-27 19:28:37 +0000617 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000618 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000619 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000620 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000621 if (fColorCount == 2) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000622 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
623 kGradient16Length);
reed@android.com41bccf52009-04-03 13:33:51 +0000624 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 Rec* rec = fRecs;
626 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000627 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000628 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 SkASSERT(nextIndex < kCache16Count);
630
631 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000632 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 prevIndex = nextIndex;
634 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000635 // one extra space left over at the end for complete_16bit_cache()
636 SkASSERT(prevIndex == kGradient16Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000637 }
638
reed@android.com41bccf52009-04-03 13:33:51 +0000639 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000640 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641 uint16_t* linear = fCache16; // just computed linear data
642 uint16_t* mapped = fCache16Storage; // storage for mapped data
643 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000644 for (int i = 0; i < kGradient16Length; i++) {
reed@android.com200645d2009-12-14 16:41:57 +0000645 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000647 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 }
649 sk_free(fCache16);
650 fCache16 = fCache16Storage;
651 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000652 complete_16bit_cache(fCache16, kCache16Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 }
654 return fCache16;
655}
656
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000657/** We duplicate the last value in each half of the cache so that
658 interpolation doesn't have to special-case being at the last point.
659*/
660static void complete_32bit_cache(SkPMColor* cache, int stride) {
661 cache[stride - 1] = cache[stride - 2];
662 cache[2 * stride - 1] = cache[2 * stride - 2];
663}
664
reed@google.com7c2f27d2011-03-07 19:29:00 +0000665const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000666 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000667 // double the count for dither entries
668 const int entryCount = kCache32Count * 2;
669 const size_t allocSize = sizeof(SkPMColor) * entryCount;
670
reed@google.comdc731fd2010-12-23 15:19:47 +0000671 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000672 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
673 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000674 }
675 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000676 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000677 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000678 kGradient32Length, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000679 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680 Rec* rec = fRecs;
681 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000682 for (int i = 1; i < fColorCount; i++) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000683 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000684 SkASSERT(nextIndex < kGradient32Length);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685
686 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000687 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
688 fOrigColors[i],
689 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690 prevIndex = nextIndex;
691 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000692 SkASSERT(prevIndex == kGradient32Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693 }
694
reed@android.com41bccf52009-04-03 13:33:51 +0000695 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000696 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000697 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000699 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000701 for (int i = 0; i < kGradient32Length; i++) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000702 int index = map->mapUnit16((i << 8) | i) >> 8;
703 mapped[i] = linear[index];
704 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000705 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000706 fCache32PixelRef->unref();
707 fCache32PixelRef = newPR;
708 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000710 complete_32bit_cache(fCache32, kCache32Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000711 }
712 return fCache32;
713}
714
reed@google.comdc731fd2010-12-23 15:19:47 +0000715/*
716 * Because our caller might rebuild the same (logically the same) gradient
717 * over and over, we'd like to return exactly the same "bitmap" if possible,
718 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
719 * To do that, we maintain a private cache of built-bitmaps, based on our
720 * colors and positions. Note: we don't try to flatten the fMapper, so if one
721 * is present, we skip the cache for now.
722 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000723void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000724 // our caller assumes no external alpha, so we ensure that our cache is
725 // built with 0xFF
726 this->setCacheAlpha(0xFF);
727
reed@google.comdc731fd2010-12-23 15:19:47 +0000728 // don't have a way to put the mapper into our cache-key yet
729 if (fMapper) {
730 // force our cahce32pixelref to be built
731 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000732 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000733 bitmap->setPixelRef(fCache32PixelRef);
734 return;
735 }
736
737 // build our key: [numColors + colors[] + {positions[]} ]
738 int count = 1 + fColorCount;
739 if (fColorCount > 2) {
740 count += fColorCount - 1; // fRecs[].fPos
741 }
742
743 SkAutoSTMalloc<16, int32_t> storage(count);
744 int32_t* buffer = storage.get();
745
746 *buffer++ = fColorCount;
747 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
748 buffer += fColorCount;
749 if (fColorCount > 2) {
750 for (int i = 1; i < fColorCount; i++) {
751 *buffer++ = fRecs[i].fPos;
752 }
753 }
754 SkASSERT(buffer - storage.get() == count);
755
756 ///////////////////////////////////
757
758 static SkMutex gMutex;
759 static SkBitmapCache* gCache;
760 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
761 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
762 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000763
reed@google.comdc731fd2010-12-23 15:19:47 +0000764 if (NULL == gCache) {
765 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
766 }
767 size_t size = count * sizeof(int32_t);
768
769 if (!gCache->find(storage.get(), size, bitmap)) {
770 // force our cahce32pixelref to be built
771 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000772 // Only expose the linear section of the cache; don't let the caller
773 // know about the padding at the end to make interpolation faster.
774 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000775 bitmap->setPixelRef(fCache32PixelRef);
776
777 gCache->add(storage.get(), size, *bitmap);
778 }
779}
780
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000781void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
782 if (info) {
783 if (info->fColorCount >= fColorCount) {
784 if (info->fColors) {
785 memcpy(info->fColors, fOrigColors,
786 fColorCount * sizeof(SkColor));
787 }
788 if (info->fColorOffsets) {
789 if (fColorCount == 2) {
790 info->fColorOffsets[0] = 0;
791 info->fColorOffsets[1] = SK_Scalar1;
792 } else if (fColorCount > 2) {
793 for (int i = 0; i < fColorCount; i++)
794 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
795 }
796 }
797 }
798 info->fColorCount = fColorCount;
799 info->fTileMode = fTileMode;
800 }
801}
802
reed@google.com61eb0402011-04-15 12:11:12 +0000803///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804
reed@android.com41bccf52009-04-03 13:33:51 +0000805static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806 SkVector vec = pts[1] - pts[0];
807 SkScalar mag = vec.length();
808 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
809
810 vec.scale(inv);
811 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
812 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
813 matrix->postScale(inv, inv);
814}
815
816///////////////////////////////////////////////////////////////////////////////
817
818class Linear_Gradient : public Gradient_Shader {
819public:
820 Linear_Gradient(const SkPoint pts[2],
821 const SkColor colors[], const SkScalar pos[], int colorCount,
822 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000823 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
824 fStart(pts[0]),
825 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826 {
827 pts_to_unit_matrix(pts, &fPtsToUnit);
828 }
reed@android.com9b46e772009-06-05 12:24:41 +0000829
reed@google.com7716afb2011-12-07 15:17:50 +0000830 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
831 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
832 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
833 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
834 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
835 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836
reed@google.com55b8e8c2011-01-13 16:22:35 +0000837 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000838 return SkNEW_ARGS(Linear_Gradient, (buffer));
839 }
840
reed@google.com7716afb2011-12-07 15:17:50 +0000841 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000842 this->INHERITED::flatten(buffer);
843 buffer.writeScalar(fStart.fX);
844 buffer.writeScalar(fStart.fY);
845 buffer.writeScalar(fEnd.fX);
846 buffer.writeScalar(fEnd.fY);
847 }
848
caryclark@google.comd26147a2011-12-15 14:16:43 +0000849 SK_DECLARE_FLATTENABLE_REGISTRAR()
850
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000852 Linear_Gradient(SkFlattenableReadBuffer& buffer)
853 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000854 fStart(unflatten_point(buffer)),
855 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000856 }
reed@google.com7716afb2011-12-07 15:17:50 +0000857 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858
859private:
860 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000861 const SkPoint fStart;
862 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863};
864
reed@android.com5119bdb2009-06-12 21:27:03 +0000865bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
866 const SkMatrix& matrix) {
867 if (!this->INHERITED::setContext(device, paint, matrix)) {
868 return false;
869 }
870
871 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
872 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000873 fFlags |= SkShader::kConstInY32_Flag;
874 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
875 // only claim this if we do have a 16bit mode (i.e. none of our
876 // colors have alpha), and if we are not dithering (which obviously
877 // is not const in Y).
878 fFlags |= SkShader::kConstInY16_Flag;
879 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000880 }
881 return true;
882}
883
reed@google.com5eb158d2011-04-15 15:50:34 +0000884#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000885 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000886 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000887 SkASSERT(fi <= 0xFF); \
888 fx += dx; \
889 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000890 toggle ^= Gradient_Shader::kDitherStride32; \
reed@google.com13659f12011-04-18 19:59:38 +0000891 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000892
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000893namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000894
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000895typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000896 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000897 int toggle, int count);
898
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000899// This function is deprecated, and will be replaced by
900// shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it.
901void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
902 SkPMColor* SK_RESTRICT dstC,
903 const SkPMColor* SK_RESTRICT cache,
904 int toggle, int count) {
905 // We're a vertical gradient, so no change in a span.
906 // If colors change sharply across the gradient, dithering is
907 // insufficient (it subsamples the color space) and we need to lerp.
908 unsigned fullIndex = proc(fx);
909 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
910 sk_memset32_dither(dstC,
911 cache[toggle + fi],
912 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
913 count);
914}
915
916// Linear interpolation (lerp) is unnecessary if there are no sharp
917// discontinuities in the gradient - which must be true if there are
918// only 2 colors - but it's cheap.
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000919void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
920 SkPMColor* SK_RESTRICT dstC,
921 const SkPMColor* SK_RESTRICT cache,
922 int toggle, int count) {
923 // We're a vertical gradient, so no change in a span.
924 // If colors change sharply across the gradient, dithering is
925 // insufficient (it subsamples the color space) and we need to lerp.
926 unsigned fullIndex = proc(fx);
927 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
928 unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32;
929 SkPMColor lerp =
930 SkFastFourByteInterp(
931 cache[toggle + fi + 1],
932 cache[toggle + fi], remainder);
933 SkPMColor dlerp =
934 SkFastFourByteInterp(
935 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1],
936 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], remainder);
937 sk_memset32_dither(dstC, lerp, dlerp, count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000938}
939
940void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
941 SkPMColor* SK_RESTRICT dstC,
942 const SkPMColor* SK_RESTRICT cache,
943 int toggle, int count) {
944 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000945 range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000946
947 if ((count = range.fCount0) > 0) {
948 sk_memset32_dither(dstC,
949 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000950 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000951 count);
952 dstC += count;
953 }
954 if ((count = range.fCount1) > 0) {
955 int unroll = count >> 3;
956 fx = range.fFx1;
957 for (int i = 0; i < unroll; i++) {
958 NO_CHECK_ITER; NO_CHECK_ITER;
959 NO_CHECK_ITER; NO_CHECK_ITER;
960 NO_CHECK_ITER; NO_CHECK_ITER;
961 NO_CHECK_ITER; NO_CHECK_ITER;
962 }
963 if ((count &= 7) > 0) {
964 do {
965 NO_CHECK_ITER;
966 } while (--count != 0);
967 }
968 }
969 if ((count = range.fCount2) > 0) {
970 sk_memset32_dither(dstC,
971 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000972 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000973 count);
974 }
975}
976
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000977void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
978 SkPMColor* SK_RESTRICT dstC,
979 const SkPMColor* SK_RESTRICT cache,
980 int toggle, int count) {
981 do {
982 unsigned fi = mirror_8bits(fx >> 8);
983 SkASSERT(fi <= 0xFF);
984 fx += dx;
985 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000986 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000987 } while (--count != 0);
988}
989
990void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
991 SkPMColor* SK_RESTRICT dstC,
992 const SkPMColor* SK_RESTRICT cache,
993 int toggle, int count) {
994 do {
995 unsigned fi = repeat_8bits(fx >> 8);
996 SkASSERT(fi <= 0xFF);
997 fx += dx;
998 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000999 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001000 } while (--count != 0);
1001}
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001002
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001003}
1004
1005void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1006 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007 SkASSERT(count > 0);
1008
1009 SkPoint srcPt;
1010 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1011 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001012 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +00001013#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001014 int toggle = ((x ^ y) & 1) * kDitherStride32;
reed@google.com0e734bd2011-12-08 17:24:44 +00001015#else
1016 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +00001017#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001018
reed@android.comc552a432009-06-12 20:02:50 +00001019 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001020 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1021 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1023
reed@android.comc552a432009-06-12 20:02:50 +00001024 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001025 SkFixed dxStorage[1];
1026 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1027 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +00001028 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001029 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1030 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1031 }
1032
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001033 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +00001034 if (SkFixedNearlyZero(dx)) {
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001035#ifdef SK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS
1036 if (fColorCount > 2) {
1037 shadeProc = shadeSpan_linear_vertical_lerp;
1038 } else {
1039 shadeProc = shadeSpan_linear_vertical;
1040 }
1041#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001042 shadeProc = shadeSpan_linear_vertical_lerp;
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001043#endif
reed@android.comc552a432009-06-12 20:02:50 +00001044 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001045 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +00001046 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001047 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +00001048 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001049 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001050 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001051 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +00001052 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001053 SkScalar dstX = SkIntToScalar(x);
1054 SkScalar dstY = SkIntToScalar(y);
1055 do {
1056 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1057 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1058 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001059 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001060 toggle ^= Gradient_Shader::kDitherStride32;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061 dstX += SK_Scalar1;
1062 } while (--count != 0);
1063 }
1064}
1065
reed@google.com55b8e8c2011-01-13 16:22:35 +00001066SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +00001067 SkMatrix* matrix,
1068 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +00001069 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001071 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072 }
1073 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001074 matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 matrix->preConcat(fPtsToUnit);
1076 }
1077 if (xy) {
1078 xy[0] = fTileMode;
1079 xy[1] = kClamp_TileMode;
1080 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001081 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082}
1083
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001084SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1085 if (info) {
1086 commonAsAGradient(info);
1087 info->fPoint[0] = fStart;
1088 info->fPoint[1] = fEnd;
1089 }
1090 return kLinear_GradientType;
1091}
1092
reed@android.com3c9b2a42009-08-27 19:28:37 +00001093static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1094 int count) {
1095 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001096 *dst++ = value;
1097 count -= 1;
1098 SkTSwap(value, other);
1099 }
1100
1101 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001102
reed@android.com3c9b2a42009-08-27 19:28:37 +00001103 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001104 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001105 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001106}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001107
reed@google.com5eb158d2011-04-15 15:50:34 +00001108#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001109 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001110 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001111 SkASSERT(fi < Gradient_Shader::kCache16Count); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001112 fx += dx; \
1113 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001114 toggle ^= Gradient_Shader::kDitherStride16; \
reed@google.com13659f12011-04-18 19:59:38 +00001115 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001116
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001117namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001118
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001119typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001120 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001121 int toggle, int count);
1122
1123void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1124 uint16_t* SK_RESTRICT dstC,
1125 const uint16_t* SK_RESTRICT cache,
1126 int toggle, int count) {
1127 // we're a vertical gradient, so no change in a span
1128 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001129 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001130 dither_memset16(dstC, cache[toggle + fi],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001131 cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001132
1133}
1134
1135void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1136 uint16_t* SK_RESTRICT dstC,
1137 const uint16_t* SK_RESTRICT cache,
1138 int toggle, int count) {
1139 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001140 range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001141
1142 if ((count = range.fCount0) > 0) {
1143 dither_memset16(dstC,
1144 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001145 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001146 count);
1147 dstC += count;
1148 }
1149 if ((count = range.fCount1) > 0) {
1150 int unroll = count >> 3;
1151 fx = range.fFx1;
1152 for (int i = 0; i < unroll; i++) {
1153 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1154 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1155 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1156 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1157 }
1158 if ((count &= 7) > 0) {
1159 do {
1160 NO_CHECK_ITER_16;
1161 } while (--count != 0);
1162 }
1163 }
1164 if ((count = range.fCount2) > 0) {
1165 dither_memset16(dstC,
1166 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001167 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001168 count);
1169 }
1170}
1171
1172void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1173 uint16_t* SK_RESTRICT dstC,
1174 const uint16_t* SK_RESTRICT cache,
1175 int toggle, int count) {
1176 do {
1177 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1178 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001179 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001180 fx += dx;
1181 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001182 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001183 } while (--count != 0);
1184}
1185
1186void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1187 uint16_t* SK_RESTRICT dstC,
1188 const uint16_t* SK_RESTRICT cache,
1189 int toggle, int count) {
1190 SkASSERT(proc == repeat_tileproc);
1191 do {
1192 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1193 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001194 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001195 fx += dx;
1196 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001197 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001198 } while (--count != 0);
1199}
1200}
1201
1202void Linear_Gradient::shadeSpan16(int x, int y,
1203 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 SkASSERT(count > 0);
1205
1206 SkPoint srcPt;
1207 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1208 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001209 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001210 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001212 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001213 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1214 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1216
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001217 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001218 SkFixed dxStorage[1];
1219 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1220 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001221 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1223 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1224 }
1225
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001226 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001227 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001228 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001229 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001230 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001231 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001232 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001233 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001236 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001237 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238 SkScalar dstX = SkIntToScalar(x);
1239 SkScalar dstY = SkIntToScalar(y);
1240 do {
1241 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1242 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1243 SkASSERT(fi <= 0xFFFF);
1244
reed@android.com512a8762009-12-14 15:25:36 +00001245 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001247 toggle ^= Gradient_Shader::kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248
1249 dstX += SK_Scalar1;
1250 } while (--count != 0);
1251 }
1252}
1253
1254///////////////////////////////////////////////////////////////////////////////
1255
1256#define kSQRT_TABLE_BITS 11
1257#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1258
1259#include "SkRadialGradient_Table.h"
1260
1261#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1262
1263#include <stdio.h>
1264
reed@google.com61eb0402011-04-15 12:11:12 +00001265void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001266 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1267
1268 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1269 SkASSERT(file);
1270 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1271
reed@google.com61eb0402011-04-15 12:11:12 +00001272 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1273 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001274 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001275 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276
1277 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1278
1279 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001280 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001281 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001282 }
1283 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001285 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286 }
1287 ::fprintf(file, "};\n");
1288 ::fclose(file);
1289}
1290
1291#endif
1292
1293
reed@google.com61eb0402011-04-15 12:11:12 +00001294static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1295 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 SkScalar inv = SkScalarInvert(radius);
1297
1298 matrix->setTranslate(-center.fX, -center.fY);
1299 matrix->postScale(inv, inv);
1300}
1301
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001302
1303namespace {
1304
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001305typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
1306 SkScalar sfy, SkScalar sdy,
1307 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001308 int toggle, int count);
1309
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001310void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
1311 SkScalar sfy, SkScalar sdy,
1312 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001313 int toggle, int count) {
1314 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1315
1316 /* knock these down so we can pin against +- 0x7FFF, which is an
1317 immediate load, rather than 0xFFFF which is slower. This is a
1318 compromise, since it reduces our precision, but that appears
1319 to be visually OK. If we decide this is OK for all of our cases,
1320 we could (it seems) put this scale-down into fDstToIndex,
1321 to avoid having to do these extra shifts each time.
1322 */
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001323 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1324 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1325 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1326 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001327 // might perform this check for the other modes,
1328 // but the win will be a smaller % of the total
1329 if (dy == 0) {
1330 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1331 fy *= fy;
1332 do {
1333 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1334 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1335 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1336 fx += dx;
1337 *dstC++ = cache[toggle +
1338 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001339 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001340 } while (--count != 0);
1341 } else {
1342 do {
1343 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1344 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1345 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1346 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1347 fx += dx;
1348 fy += dy;
1349 *dstC++ = cache[toggle +
1350 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001351 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001352 } while (--count != 0);
1353 }
1354}
1355
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001356void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
1357 SkScalar sfy, SkScalar sdy,
1358 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001359 int toggle, int count) {
1360 do {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001361#ifdef SK_SCALAR_IS_FLOAT
1362 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1363 SkFixed dist = SkFloatToFixed(fdist);
1364#else
1365 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1366 SkFixedSquare(sfy);
1367 if (magnitudeSquared < 0) // Overflow.
1368 magnitudeSquared = SK_FixedMax;
1369 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1370#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001371 unsigned fi = mirror_tileproc(dist);
1372 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001373 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001374 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001375 sfx += sdx;
1376 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001377 } while (--count != 0);
1378}
1379
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001380void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
1381 SkScalar sfy, SkScalar sdy,
1382 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001383 int toggle, int count) {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001384 SkFixed fx = SkScalarToFixed(sfx);
1385 SkFixed dx = SkScalarToFixed(sdx);
1386 SkFixed fy = SkScalarToFixed(sfy);
1387 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001388 do {
1389 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1390 unsigned fi = repeat_tileproc(dist);
1391 SkASSERT(fi <= 0xFFFF);
1392 fx += dx;
1393 fy += dy;
1394 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001395 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001396 } while (--count != 0);
1397}
1398
1399}
1400
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401class Radial_Gradient : public Gradient_Shader {
1402public:
1403 Radial_Gradient(const SkPoint& center, SkScalar radius,
1404 const SkColor colors[], const SkScalar pos[], int colorCount,
1405 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001406 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1407 fCenter(center),
1408 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001409 {
1410 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1411 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1412
1413 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1414 }
reed@google.com61eb0402011-04-15 12:11:12 +00001415
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001416 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
1417 SK_OVERRIDE;
1418 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
1419 int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001420 SkASSERT(count > 0);
1421
1422 SkPoint srcPt;
1423 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1424 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001425 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001426 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427
reed@android.com3c9b2a42009-08-27 19:28:37 +00001428 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001429 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1430 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001431
1432 SkScalar sdx = fDstToIndex.getScaleX();
1433 SkScalar sdy = fDstToIndex.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434
reed@android.com3c9b2a42009-08-27 19:28:37 +00001435 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 SkFixed storage[2];
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001437 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1438 &storage[0], &storage[1]);
1439 sdx = SkFixedToScalar(storage[0]);
1440 sdy = SkFixedToScalar(storage[1]);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001441 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 }
1444
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001445 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001446 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001447 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001448 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001449 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001450 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452 }
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001453 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
1454 cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001455 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 SkScalar dstX = SkIntToScalar(x);
1457 SkScalar dstY = SkIntToScalar(y);
1458 do {
1459 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1460 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1461 SkASSERT(fi <= 0xFFFF);
1462
1463 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001465 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466
1467 dstX += SK_Scalar1;
1468 } while (--count != 0);
1469 }
1470 }
1471
reed@google.com55b8e8c2011-01-13 16:22:35 +00001472 virtual BitmapType asABitmap(SkBitmap* bitmap,
1473 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001474 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001475 SkScalar* twoPointRadialParams)
1476 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001477 if (bitmap) {
1478 this->commonAsABitmap(bitmap);
1479 }
1480 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001481 matrix->setScale(SkIntToScalar(kGradient32Length),
1482 SkIntToScalar(kGradient32Length));
reed@google.comdc731fd2010-12-23 15:19:47 +00001483 matrix->preConcat(fPtsToUnit);
1484 }
1485 if (xy) {
1486 xy[0] = fTileMode;
1487 xy[1] = kClamp_TileMode;
1488 }
1489 return kRadial_BitmapType;
1490 }
reed@google.com7716afb2011-12-07 15:17:50 +00001491 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001492 if (info) {
1493 commonAsAGradient(info);
1494 info->fPoint[0] = fCenter;
1495 info->fRadius[0] = fRadius;
1496 }
1497 return kRadial_GradientType;
1498 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001499
reed@google.com8e6d9142011-12-07 15:30:34 +00001500 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501 return SkNEW_ARGS(Radial_Gradient, (buffer));
1502 }
1503
reed@google.com7716afb2011-12-07 15:17:50 +00001504 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001505 this->INHERITED::flatten(buffer);
1506 buffer.writeScalar(fCenter.fX);
1507 buffer.writeScalar(fCenter.fY);
1508 buffer.writeScalar(fRadius);
1509 }
1510
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001512 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1513 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001514 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001515 fRadius(buffer.readScalar()) {
1516 }
reed@google.com7716afb2011-12-07 15:17:50 +00001517 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518
1519private:
1520 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001521 const SkPoint fCenter;
1522 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523};
1524
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001525namespace {
1526
1527inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001528 // fast, overly-conservative test: checks unit square instead
1529 // of unit circle
1530 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1531 (fx <= -SK_FixedHalf && dx <= 0);
1532 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1533 (fy <= -SK_FixedHalf && dy <= 0);
1534
1535 return xClamped || yClamped;
1536}
1537
1538// Return true if (fx * fy) is always inside the unit circle
1539// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1540// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001541inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001542 int fy, int dy, int count) {
1543 SkASSERT(count > 0);
1544 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1545 return false;
1546 }
1547 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1548 return false;
1549 }
1550 fx += (count - 1) * dx;
1551 fy += (count - 1) * dy;
1552 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1553 return false;
1554 }
1555 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1556}
1557
1558#define UNPINNED_RADIAL_STEP \
1559 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001560 *dstC++ = cache[sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift]; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001561 fx += dx; \
1562 fy += dy;
1563
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001564typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1565 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001566 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001567 int count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001568
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001569// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001570void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
1571 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001572 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001573 int count) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001574 // Floating point seems to be slower than fixed point,
1575 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001576 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001577 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1578 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1579 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1580 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001581 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001582 sk_memset32(dstC, cache[Gradient_Shader::kGradient32Length], count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001583 } else if ((count > 4) &&
1584 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1585 unsigned fi;
1586 // 4x unroll appears to be no faster than 2x unroll on Linux
1587 while (count > 1) {
1588 UNPINNED_RADIAL_STEP;
1589 UNPINNED_RADIAL_STEP;
1590 count -= 2;
1591 }
1592 if (count) {
1593 UNPINNED_RADIAL_STEP;
1594 }
1595 }
1596 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001597 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1598 if (dy == 0) {
1599 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1600 yy *= yy;
1601 do {
1602 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1603 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1604 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1605 *dstC++ = cache[sqrt_table[fi] >>
1606 Gradient_Shader::kSqrt32Shift];
1607 fx += dx;
1608 } while (--count != 0);
1609 } else {
1610 do {
1611 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1612 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1613 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1614 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1615 *dstC++ = cache[sqrt_table[fi] >>
1616 Gradient_Shader::kSqrt32Shift];
1617 fx += dx;
1618 fy += dy;
1619 } while (--count != 0);
1620 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001621 }
1622}
1623
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001624// Unrolling this loop doesn't seem to help (when float); we're stalling to
1625// get the results of the sqrt (?), and don't have enough extra registers to
1626// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001627void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1628 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001629 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001630 int count) {
1631 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001632#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001633 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1634 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001635#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001636 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1637 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001638 if (magnitudeSquared < 0) // Overflow.
1639 magnitudeSquared = SK_FixedMax;
1640 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001641#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001642 unsigned fi = mirror_tileproc(dist);
1643 SkASSERT(fi <= 0xFFFF);
1644 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001645 sfx += sdx;
1646 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001647 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001648}
1649
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001650void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1651 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001652 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001653 int count) {
1654 SkFixed fx = SkScalarToFixed(sfx);
1655 SkFixed dx = SkScalarToFixed(sdx);
1656 SkFixed fy = SkScalarToFixed(sfy);
1657 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001658 do {
1659 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1660 SkFixedSquare(fy);
1661 if (magnitudeSquared < 0) // Overflow.
1662 magnitudeSquared = SK_FixedMax;
1663 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1664 unsigned fi = repeat_tileproc(dist);
1665 SkASSERT(fi <= 0xFFFF);
1666 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
1667 fx += dx;
1668 fy += dy;
1669 } while (--count != 0);
1670}
1671}
1672
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001673void Radial_Gradient::shadeSpan(int x, int y,
1674 SkPMColor* SK_RESTRICT dstC, int count) {
1675 SkASSERT(count > 0);
1676
1677 SkPoint srcPt;
1678 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1679 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001680 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001681
1682 if (fDstToIndexClass != kPerspective_MatrixClass) {
1683 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1684 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001685 SkScalar sdx = fDstToIndex.getScaleX();
1686 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001687
1688 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1689 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001690 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1691 &storage[0], &storage[1]);
1692 sdx = SkFixedToScalar(storage[0]);
1693 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001694 } else {
1695 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001696 }
1697
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001698 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001699 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001700 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001701 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001702 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001703 } else {
1704 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001705 }
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001706 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001707 } else { // perspective case
1708 SkScalar dstX = SkIntToScalar(x);
1709 SkScalar dstY = SkIntToScalar(y);
1710 do {
1711 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1712 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1713 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001714 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001715 dstX += SK_Scalar1;
1716 } while (--count != 0);
1717 }
1718}
1719
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001720/* Two-point radial gradients are specified by two circles, each with a center
1721 point and radius. The gradient can be considered to be a series of
1722 concentric circles, with the color interpolated from the start circle
1723 (at t=0) to the end circle (at t=1).
1724
1725 For each point (x, y) in the span, we want to find the
1726 interpolated circle that intersects that point. The center
1727 of the desired circle (Cx, Cy) falls at some distance t
1728 along the line segment between the start point (Sx, Sy) and
1729 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001730
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001731 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1732 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001733
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001734 The radius of the desired circle (r) is also a linear interpolation t
1735 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001736
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001737 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001738
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001739 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001740
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001741 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001742
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001743 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001744
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001745 (x - ((1 - t) * Sx + t * Ex))^2
1746 + (y - ((1 - t) * Sy + t * Ey))^2
1747 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001748
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001749 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001750
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001751 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1752 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1753 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001754
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001755 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1756
1757 [Dx^2 + Dy^2 - Dr^2)] * t^2
1758 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1759 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001760
1761 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001762 possible circles on which the point may fall. Solving for t yields
1763 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001764
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001765 If a<0, the start circle is entirely contained in the
1766 end circle, and one of the roots will be <0 or >1 (off the line
1767 segment). If a>0, the start circle falls at least partially
1768 outside the end circle (or vice versa), and the gradient
1769 defines a "tube" where a point may be on one circle (on the
1770 inside of the tube) or the other (outside of the tube). We choose
1771 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001772
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001773 In order to keep the math to within the limits of fixed point,
1774 we divide the entire quadratic by Dr^2, and replace
1775 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001776
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001777 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1778 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1779 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001780
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001781 (x' and y' are computed by appending the subtract and scale to the
1782 fDstToIndex matrix in the constructor).
1783
1784 Since the 'A' component of the quadratic is independent of x' and y', it
1785 is precomputed in the constructor. Since the 'B' component is linear in
1786 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001787 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001788 a perspective projection), it must be computed in the loop.
1789
1790*/
1791
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001792namespace {
1793
1794inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1795 SkScalar sr2d2, SkScalar foura,
1796 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001797 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001798 if (0 == foura) {
1799 return SkScalarToFixed(SkScalarDiv(-c, b));
1800 }
1801
reed@google.com84e9c082011-04-13 17:44:24 +00001802 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001803 if (discrim < 0) {
1804 discrim = -discrim;
1805 }
reed@google.com84e9c082011-04-13 17:44:24 +00001806 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1807 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001808 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001809 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001810 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001811 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001812 }
reed@google.com84e9c082011-04-13 17:44:24 +00001813 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001814}
1815
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001816typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1817 SkScalar fy, SkScalar dy,
1818 SkScalar b, SkScalar db,
1819 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001820 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001821 int count);
1822
1823void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1824 SkScalar fy, SkScalar dy,
1825 SkScalar b, SkScalar db,
1826 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001827 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001828 int count) {
1829 for (; count > 0; --count) {
1830 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1831 fOneOverTwoA, posRoot);
1832 SkFixed index = SkClampMax(t, 0xFFFF);
1833 SkASSERT(index <= 0xFFFF);
1834 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1835 fx += dx;
1836 fy += dy;
1837 b += db;
1838 }
1839}
1840void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1841 SkScalar fy, SkScalar dy,
1842 SkScalar b, SkScalar db,
1843 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001844 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001845 int count) {
1846 for (; count > 0; --count) {
1847 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1848 fOneOverTwoA, posRoot);
1849 SkFixed index = mirror_tileproc(t);
1850 SkASSERT(index <= 0xFFFF);
1851 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1852 fx += dx;
1853 fy += dy;
1854 b += db;
1855 }
1856}
1857
1858void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1859 SkScalar fy, SkScalar dy,
1860 SkScalar b, SkScalar db,
1861 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001862 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001863 int count) {
1864 for (; count > 0; --count) {
1865 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1866 fOneOverTwoA, posRoot);
1867 SkFixed index = repeat_tileproc(t);
1868 SkASSERT(index <= 0xFFFF);
1869 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1870 fx += dx;
1871 fy += dy;
1872 b += db;
1873 }
1874}
1875
1876
1877
1878}
1879
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001880class Two_Point_Radial_Gradient : public Gradient_Shader {
1881public:
1882 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1883 const SkPoint& end, SkScalar endRadius,
1884 const SkColor colors[], const SkScalar pos[],
1885 int colorCount, SkShader::TileMode mode,
1886 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001887 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1888 fCenter1(start),
1889 fCenter2(end),
1890 fRadius1(startRadius),
1891 fRadius2(endRadius) {
1892 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001893 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001894
1895 virtual BitmapType asABitmap(SkBitmap* bitmap,
1896 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001897 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001898 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001899 if (bitmap) {
1900 this->commonAsABitmap(bitmap);
1901 }
1902 SkScalar diffL = 0; // just to avoid gcc warning
1903 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001904 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001905 SkScalarSquare(fDiff.fY));
1906 }
1907 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001908 if (diffL) {
1909 SkScalar invDiffL = SkScalarInvert(diffL);
1910 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1911 SkScalarMul(invDiffL, fDiff.fX));
1912 } else {
1913 matrix->reset();
1914 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001915 matrix->preConcat(fPtsToUnit);
1916 }
1917 if (xy) {
1918 xy[0] = fTileMode;
1919 xy[1] = kClamp_TileMode;
1920 }
1921 if (NULL != twoPointRadialParams) {
1922 twoPointRadialParams[0] = diffL;
1923 twoPointRadialParams[1] = fStartRadius;
1924 twoPointRadialParams[2] = fDiffRadius;
1925 }
1926 return kTwoPointRadial_BitmapType;
1927 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001928
reed@google.com8e6d9142011-12-07 15:30:34 +00001929 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001930 if (info) {
1931 commonAsAGradient(info);
1932 info->fPoint[0] = fCenter1;
1933 info->fPoint[1] = fCenter2;
1934 info->fRadius[0] = fRadius1;
1935 info->fRadius[1] = fRadius2;
1936 }
1937 return kRadial2_GradientType;
1938 }
1939
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001940 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1941 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001942 SkASSERT(count > 0);
1943
1944 // Zero difference between radii: fill with transparent black.
1945 if (fDiffRadius == 0) {
1946 sk_bzero(dstC, count * sizeof(*dstC));
1947 return;
1948 }
1949 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1950 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001951 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001952
1953 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001954 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001955 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001956 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001957 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1958 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001959 SkScalar dx, fx = srcPt.fX;
1960 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001961
reed@google.com61eb0402011-04-15 12:11:12 +00001962 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001963 SkFixed fixedX, fixedY;
1964 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1965 dx = SkFixedToScalar(fixedX);
1966 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001967 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001968 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001969 dx = fDstToIndex.getScaleX();
1970 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001971 }
reed@google.com84e9c082011-04-13 17:44:24 +00001972 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1973 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1974 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1975 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001976
1977 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00001978 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001979 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00001980 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001981 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00001982 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001983 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001984 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001985 (*shadeProc)(fx, dx, fy, dy, b, db,
1986 fSr2D2, foura, fOneOverTwoA, posRoot,
1987 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00001988 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001989 SkScalar dstX = SkIntToScalar(x);
1990 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001991 for (; count > 0; --count) {
1992 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001993 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001994 SkScalar fx = srcPt.fX;
1995 SkScalar fy = srcPt.fY;
1996 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1997 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001998 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1999 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002000 SkFixed index = proc(t);
2001 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002002 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00002003 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002004 }
2005 }
2006 }
2007
reed@android.com6c59a172009-09-22 20:24:05 +00002008 virtual bool setContext(const SkBitmap& device,
2009 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00002010 const SkMatrix& matrix) SK_OVERRIDE {
2011 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00002012 return false;
2013 }
2014
2015 // we don't have a span16 proc
2016 fFlags &= ~kHasSpan16_Flag;
2017 return true;
2018 }
2019
reed@google.com8e6d9142011-12-07 15:30:34 +00002020 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002021 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
2022 }
2023
reed@google.com7716afb2011-12-07 15:17:50 +00002024 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00002025 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002026 buffer.writeScalar(fCenter1.fX);
2027 buffer.writeScalar(fCenter1.fY);
2028 buffer.writeScalar(fCenter2.fX);
2029 buffer.writeScalar(fCenter2.fY);
2030 buffer.writeScalar(fRadius1);
2031 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002032 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002033
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002034protected:
reed@android.combcfc7332009-11-10 16:19:39 +00002035 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002036 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002037 fCenter1(unflatten_point(buffer)),
2038 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002039 fRadius1(buffer.readScalar()),
2040 fRadius2(buffer.readScalar()) {
2041 init();
reed@android.combcfc7332009-11-10 16:19:39 +00002042 };
reed@google.com7716afb2011-12-07 15:17:50 +00002043 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002044
2045private:
2046 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002047 const SkPoint fCenter1;
2048 const SkPoint fCenter2;
2049 const SkScalar fRadius1;
2050 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002051 SkPoint fDiff;
2052 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002053
2054 void init() {
2055 fDiff = fCenter1 - fCenter2;
2056 fDiffRadius = fRadius2 - fRadius1;
2057 SkScalar inv = SkScalarInvert(fDiffRadius);
2058 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2059 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2060 fStartRadius = SkScalarMul(fRadius1, inv);
2061 fSr2D2 = SkScalarSquare(fStartRadius);
2062 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002063 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002064
2065 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2066 fPtsToUnit.postScale(inv, inv);
2067 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002068};
2069
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070///////////////////////////////////////////////////////////////////////////////
2071
2072class Sweep_Gradient : public Gradient_Shader {
2073public:
2074 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2075 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002076 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2077 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002078 {
2079 fPtsToUnit.setTranslate(-cx, -cy);
2080 }
reed@google.com7716afb2011-12-07 15:17:50 +00002081 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2082 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002083
2084 virtual BitmapType asABitmap(SkBitmap* bitmap,
2085 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002086 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002087 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002088 if (bitmap) {
2089 this->commonAsABitmap(bitmap);
2090 }
2091 if (matrix) {
2092 *matrix = fPtsToUnit;
2093 }
2094 if (xy) {
2095 xy[0] = fTileMode;
2096 xy[1] = kClamp_TileMode;
2097 }
2098 return kSweep_BitmapType;
2099 }
2100
reed@google.com7716afb2011-12-07 15:17:50 +00002101 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002102 if (info) {
2103 commonAsAGradient(info);
2104 info->fPoint[0] = fCenter;
2105 }
2106 return kSweep_GradientType;
2107 }
2108
reed@google.com8e6d9142011-12-07 15:30:34 +00002109 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002110 return SkNEW_ARGS(Sweep_Gradient, (buffer));
2111 }
2112
reed@google.com7716afb2011-12-07 15:17:50 +00002113 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002114 this->INHERITED::flatten(buffer);
2115 buffer.writeScalar(fCenter.fX);
2116 buffer.writeScalar(fCenter.fY);
2117 }
2118
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002120 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
2121 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002122 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002123 }
2124
reed@google.com7716afb2011-12-07 15:17:50 +00002125 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126
2127private:
2128 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002129 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130};
2131
2132#ifdef COMPUTE_SWEEP_TABLE
2133#define PI 3.14159265
2134static bool gSweepTableReady;
2135static uint8_t gSweepTable[65];
2136
2137/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2138 We scale the results to [0..32]
2139*/
reed@google.com61eb0402011-04-15 12:11:12 +00002140static const uint8_t* build_sweep_table() {
2141 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142 const int N = 65;
2143 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002144
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 for (int i = 0; i < N; i++)
2146 {
2147 double arg = i / DENOM;
2148 double v = atan(arg);
2149 int iv = (int)round(v * DENOM * 2 / PI);
2150// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2151 printf("%d, ", iv);
2152 gSweepTable[i] = iv;
2153 }
2154 gSweepTableReady = true;
2155 }
2156 return gSweepTable;
2157}
2158#else
2159static const uint8_t gSweepTable[] = {
2160 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2161 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2162 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2163 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2164 32
2165};
2166static const uint8_t* build_sweep_table() { return gSweepTable; }
2167#endif
2168
2169// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2170// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2171// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2172
2173//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002174static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175 SkASSERT(numer <= denom);
2176 SkASSERT(numer > 0);
2177 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002178
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179 int nbits = SkCLZ(numer);
2180 int dbits = SkCLZ(denom);
2181 int bits = 6 - nbits + dbits;
2182 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002183
reed@google.com61eb0402011-04-15 12:11:12 +00002184 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002185 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002186 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002187
2188 denom <<= dbits - 1;
2189 numer <<= nbits - 1;
2190
2191 unsigned result = 0;
2192
2193 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002194 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002196 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002198 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002199
reed@android.com8a1c16f2008-12-17 15:59:43 +00002200 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002201 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002202 // make room for the rest of the answer bits
2203 result <<= bits;
2204 switch (bits) {
2205 case 6:
2206 if ((numer = (numer << 1) - denom) >= 0)
2207 result |= 32;
2208 else
2209 numer += denom;
2210 case 5:
2211 if ((numer = (numer << 1) - denom) >= 0)
2212 result |= 16;
2213 else
2214 numer += denom;
2215 case 4:
2216 if ((numer = (numer << 1) - denom) >= 0)
2217 result |= 8;
2218 else
2219 numer += denom;
2220 case 3:
2221 if ((numer = (numer << 1) - denom) >= 0)
2222 result |= 4;
2223 else
2224 numer += denom;
2225 case 2:
2226 if ((numer = (numer << 1) - denom) >= 0)
2227 result |= 2;
2228 else
2229 numer += denom;
2230 case 1:
2231 default: // not strictly need, but makes GCC make better ARM code
2232 if ((numer = (numer << 1) - denom) >= 0)
2233 result |= 1;
2234 else
2235 numer += denom;
2236 }
2237 }
2238 return result;
2239}
2240
2241// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002242static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002243#ifdef SK_DEBUG
2244 {
2245 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002246 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002247 gOnce = true;
2248 SkASSERT(div_64(55, 55) == 64);
2249 SkASSERT(div_64(128, 256) == 32);
2250 SkASSERT(div_64(2326528, 4685824) == 31);
2251 SkASSERT(div_64(753664, 5210112) == 9);
2252 SkASSERT(div_64(229376, 4882432) == 3);
2253 SkASSERT(div_64(2, 64) == 2);
2254 SkASSERT(div_64(1, 64) == 1);
2255 // test that we handle underflow correctly
2256 SkASSERT(div_64(12345, 0x54321234) == 0);
2257 }
2258 }
2259#endif
2260
2261 SkASSERT(y > 0 && x > 0);
2262 const uint8_t* table = build_sweep_table();
2263
2264 unsigned result;
2265 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002266 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002267 // first part of the atan(v) = PI/2 - atan(1/v) identity
2268 // since our div_64 and table want v <= 1, where v = y/x
2269 SkTSwap<SkFixed>(x, y);
2270 }
2271
2272 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002273
reed@android.com8a1c16f2008-12-17 15:59:43 +00002274#ifdef SK_DEBUG
2275 {
2276 unsigned result2 = SkDivBits(y, x, 6);
2277 SkASSERT(result2 == result ||
2278 (result == 1 && result2 == 0));
2279 }
2280#endif
2281
2282 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2283 result = table[result];
2284
reed@google.com61eb0402011-04-15 12:11:12 +00002285 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002286 // complete the atan(v) = PI/2 - atan(1/v) identity
2287 result = 64 - result;
2288 // pin to 63
2289 result -= result >> 6;
2290 }
2291
2292 SkASSERT(result <= 63);
2293 return result;
2294}
2295
2296// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002297#ifdef SK_SCALAR_IS_FLOAT
2298static unsigned SkATan2_255(float y, float x) {
2299 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2300 static const float g255Over2PI = 40.584510488433314f;
2301
2302 float result = sk_float_atan2(y, x);
2303 if (result < 0) {
2304 result += 2 * SK_ScalarPI;
2305 }
2306 SkASSERT(result >= 0);
2307 // since our value is always >= 0, we can cast to int, which is faster than
2308 // calling floorf()
2309 int ir = (int)(result * g255Over2PI);
2310 SkASSERT(ir >= 0 && ir <= 255);
2311 return ir;
2312}
2313#else
reed@google.com61eb0402011-04-15 12:11:12 +00002314static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2315 if (x == 0) {
2316 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002317 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002318 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002319 return y < 0 ? 192 : 64;
2320 }
reed@google.com61eb0402011-04-15 12:11:12 +00002321 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002322 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002323 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002324
reed@android.com8a1c16f2008-12-17 15:59:43 +00002325 /* Find the right quadrant for x,y
2326 Since atan_0_90 only handles the first quadrant, we rotate x,y
2327 appropriately before calling it, and then add the right amount
2328 to account for the real quadrant.
2329 quadrant 0 : add 0 | x > 0 && y > 0
2330 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2331 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2332 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002333
reed@android.com8a1c16f2008-12-17 15:59:43 +00002334 map x<0 to (1 << 6)
2335 map y<0 to (3 << 6)
2336 add = map_x ^ map_y
2337 */
2338 int xsign = x >> 31;
2339 int ysign = y >> 31;
2340 int add = ((-xsign) ^ (ysign & 3)) << 6;
2341
2342#ifdef SK_DEBUG
2343 if (0 == add)
2344 SkASSERT(x > 0 && y > 0);
2345 else if (64 == add)
2346 SkASSERT(x < 0 && y > 0);
2347 else if (128 == add)
2348 SkASSERT(x < 0 && y < 0);
2349 else if (192 == add)
2350 SkASSERT(x > 0 && y < 0);
2351 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002352 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002353#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002354
reed@android.com8a1c16f2008-12-17 15:59:43 +00002355 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2356 where we need to rotate x,y by 90 or -90
2357 */
2358 x = (x ^ xsign) - xsign;
2359 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002360 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002361 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002362 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002363
2364 unsigned result = add + atan_0_90(y, x);
2365 SkASSERT(result < 256);
2366 return result;
2367}
reed@google.com51baf5a2011-09-21 13:38:36 +00002368#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002369
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002370void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2371 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002372 SkMatrix::MapXYProc proc = fDstToIndexProc;
2373 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002374 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002375 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002376
reed@google.com61eb0402011-04-15 12:11:12 +00002377 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002378 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2379 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002380 SkScalar dx, fx = srcPt.fX;
2381 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002382
reed@google.com61eb0402011-04-15 12:11:12 +00002383 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002384 SkFixed storage[2];
2385 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2386 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002387 dx = SkFixedToScalar(storage[0]);
2388 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002389 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002390 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002391 dx = matrix.getScaleX();
2392 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002393 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002394
reed@google.com61eb0402011-04-15 12:11:12 +00002395 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002396 *dstC++ = cache[SkATan2_255(fy, fx)];
2397 fx += dx;
2398 fy += dy;
2399 }
reed@google.com61eb0402011-04-15 12:11:12 +00002400 } else { // perspective case
2401 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002402 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002403 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2404 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002405 }
2406 }
2407}
2408
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002409void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2410 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002411 SkMatrix::MapXYProc proc = fDstToIndexProc;
2412 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002413 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002414 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002415 SkPoint srcPt;
2416
reed@google.com61eb0402011-04-15 12:11:12 +00002417 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002418 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2419 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002420 SkScalar dx, fx = srcPt.fX;
2421 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002422
reed@google.com61eb0402011-04-15 12:11:12 +00002423 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002424 SkFixed storage[2];
2425 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2426 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002427 dx = SkFixedToScalar(storage[0]);
2428 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002429 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002430 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002431 dx = matrix.getScaleX();
2432 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002433 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002434
reed@google.com61eb0402011-04-15 12:11:12 +00002435 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002436 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2437 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002438 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002439 fx += dx;
2440 fy += dy;
2441 }
reed@google.com61eb0402011-04-15 12:11:12 +00002442 } else { // perspective case
2443 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002444 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2445 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002446
reed@google.com51baf5a2011-09-21 13:38:36 +00002447 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002448 index >>= (8 - kCache16Bits);
2449 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002450 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002451 }
2452 }
2453}
2454
reed@google.com61eb0402011-04-15 12:11:12 +00002455///////////////////////////////////////////////////////////////////////////////
2456///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002457
2458// assumes colors is SkColor* and pos is SkScalar*
2459#define EXPAND_1_COLOR(count) \
2460 SkColor tmp[2]; \
2461 do { \
2462 if (1 == count) { \
2463 tmp[0] = tmp[1] = colors[0]; \
2464 colors = tmp; \
2465 pos = NULL; \
2466 count = 2; \
2467 } \
2468 } while (0)
2469
reed@google.com61eb0402011-04-15 12:11:12 +00002470SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2471 const SkColor colors[],
2472 const SkScalar pos[], int colorCount,
2473 SkShader::TileMode mode,
2474 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002475 if (NULL == pts || NULL == colors || colorCount < 1) {
2476 return NULL;
2477 }
2478 EXPAND_1_COLOR(colorCount);
2479
reed@android.comab840b82009-07-01 17:00:03 +00002480 return SkNEW_ARGS(Linear_Gradient,
2481 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002482}
2483
reed@google.com61eb0402011-04-15 12:11:12 +00002484SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2485 const SkColor colors[],
2486 const SkScalar pos[], int colorCount,
2487 SkShader::TileMode mode,
2488 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002489 if (radius <= 0 || NULL == colors || colorCount < 1) {
2490 return NULL;
2491 }
2492 EXPAND_1_COLOR(colorCount);
2493
reed@android.comab840b82009-07-01 17:00:03 +00002494 return SkNEW_ARGS(Radial_Gradient,
2495 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002496}
2497
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002498SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2499 SkScalar startRadius,
2500 const SkPoint& end,
2501 SkScalar endRadius,
2502 const SkColor colors[],
2503 const SkScalar pos[],
2504 int colorCount,
2505 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002506 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002507 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2508 return NULL;
2509 }
2510 EXPAND_1_COLOR(colorCount);
2511
2512 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002513 (start, startRadius, end, endRadius, colors, pos,
2514 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002515}
2516
reed@android.com8a1c16f2008-12-17 15:59:43 +00002517SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2518 const SkColor colors[],
2519 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002520 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002521 if (NULL == colors || count < 1) {
2522 return NULL;
2523 }
2524 EXPAND_1_COLOR(count);
2525
2526 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2527}
2528
caryclark@google.comd26147a2011-12-15 14:16:43 +00002529SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2530 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2531 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002532
caryclark@google.comd26147a2011-12-15 14:16:43 +00002533 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002534
caryclark@google.comd26147a2011-12-15 14:16:43 +00002535 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2536SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END