blob: cfae9aa6d2f7a98be80711abe9bb078501b69f9d [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
digit@google.com1771cbf2012-01-26 21:26:40 +0000758 SK_DECLARE_STATIC_MUTEX(gMutex);
reed@google.comdc731fd2010-12-23 15:19:47 +0000759 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.com480e21f2012-02-02 14:11:18 +00001560 *dstC++ = cache[toggle + \
1561 (sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift)]; \
1562 toggle ^= Gradient_Shader::kDitherStride32; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001563 fx += dx; \
1564 fy += dy;
1565
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001566typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1567 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001568 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001569 int count, int toggle);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001570
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001571// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001572void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
1573 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001574 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001575 int count, int toggle) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001576 // Floating point seems to be slower than fixed point,
1577 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001578 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001579 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1580 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1581 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1582 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001583 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001584 unsigned fi = Gradient_Shader::kGradient32Length;
1585 sk_memset32_dither(dstC,
1586 cache[toggle + fi],
1587 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
1588 count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001589 } else if ((count > 4) &&
1590 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1591 unsigned fi;
1592 // 4x unroll appears to be no faster than 2x unroll on Linux
1593 while (count > 1) {
1594 UNPINNED_RADIAL_STEP;
1595 UNPINNED_RADIAL_STEP;
1596 count -= 2;
1597 }
1598 if (count) {
1599 UNPINNED_RADIAL_STEP;
1600 }
1601 }
1602 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001603 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1604 if (dy == 0) {
1605 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1606 yy *= yy;
1607 do {
1608 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1609 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1610 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001611 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1612 Gradient_Shader::kSqrt32Shift)];
1613 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001614 fx += dx;
1615 } while (--count != 0);
1616 } else {
1617 do {
1618 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1619 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1620 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1621 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001622 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1623 Gradient_Shader::kSqrt32Shift)];
1624 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001625 fx += dx;
1626 fy += dy;
1627 } while (--count != 0);
1628 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001629 }
1630}
1631
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001632// Unrolling this loop doesn't seem to help (when float); we're stalling to
1633// get the results of the sqrt (?), and don't have enough extra registers to
1634// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001635void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1636 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001637 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001638 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001639 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001640#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001641 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1642 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001643#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001644 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1645 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001646 if (magnitudeSquared < 0) // Overflow.
1647 magnitudeSquared = SK_FixedMax;
1648 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001649#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001650 unsigned fi = mirror_tileproc(dist);
1651 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001652 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1653 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001654 sfx += sdx;
1655 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001656 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001657}
1658
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001659void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1660 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001661 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001662 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001663 SkFixed fx = SkScalarToFixed(sfx);
1664 SkFixed dx = SkScalarToFixed(sdx);
1665 SkFixed fy = SkScalarToFixed(sfy);
1666 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001667 do {
1668 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1669 SkFixedSquare(fy);
1670 if (magnitudeSquared < 0) // Overflow.
1671 magnitudeSquared = SK_FixedMax;
1672 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1673 unsigned fi = repeat_tileproc(dist);
1674 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001675 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1676 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001677 fx += dx;
1678 fy += dy;
1679 } while (--count != 0);
1680}
1681}
1682
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001683void Radial_Gradient::shadeSpan(int x, int y,
1684 SkPMColor* SK_RESTRICT dstC, int count) {
1685 SkASSERT(count > 0);
1686
1687 SkPoint srcPt;
1688 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1689 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001690 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001691#ifdef USE_DITHER_32BIT_GRADIENT
1692 int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32;
1693#else
1694 int toggle = 0;
1695#endif
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001696
1697 if (fDstToIndexClass != kPerspective_MatrixClass) {
1698 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1699 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001700 SkScalar sdx = fDstToIndex.getScaleX();
1701 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001702
1703 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1704 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001705 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1706 &storage[0], &storage[1]);
1707 sdx = SkFixedToScalar(storage[0]);
1708 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001709 } else {
1710 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001711 }
1712
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001713 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001714 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001715 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001716 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001717 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001718 } else {
1719 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001720 }
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001721 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001722 } else { // perspective case
1723 SkScalar dstX = SkIntToScalar(x);
1724 SkScalar dstY = SkIntToScalar(y);
1725 do {
1726 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1727 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1728 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001729 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001730 dstX += SK_Scalar1;
1731 } while (--count != 0);
1732 }
1733}
1734
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001735/* Two-point radial gradients are specified by two circles, each with a center
1736 point and radius. The gradient can be considered to be a series of
1737 concentric circles, with the color interpolated from the start circle
1738 (at t=0) to the end circle (at t=1).
1739
1740 For each point (x, y) in the span, we want to find the
1741 interpolated circle that intersects that point. The center
1742 of the desired circle (Cx, Cy) falls at some distance t
1743 along the line segment between the start point (Sx, Sy) and
1744 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001745
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001746 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1747 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001748
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001749 The radius of the desired circle (r) is also a linear interpolation t
1750 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001751
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001752 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001753
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001754 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001755
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001756 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001757
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001758 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001759
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001760 (x - ((1 - t) * Sx + t * Ex))^2
1761 + (y - ((1 - t) * Sy + t * Ey))^2
1762 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001763
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001764 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001765
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001766 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1767 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1768 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001769
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001770 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1771
1772 [Dx^2 + Dy^2 - Dr^2)] * t^2
1773 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1774 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001775
1776 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001777 possible circles on which the point may fall. Solving for t yields
1778 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001779
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001780 If a<0, the start circle is entirely contained in the
1781 end circle, and one of the roots will be <0 or >1 (off the line
1782 segment). If a>0, the start circle falls at least partially
1783 outside the end circle (or vice versa), and the gradient
1784 defines a "tube" where a point may be on one circle (on the
1785 inside of the tube) or the other (outside of the tube). We choose
1786 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001787
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001788 In order to keep the math to within the limits of fixed point,
1789 we divide the entire quadratic by Dr^2, and replace
1790 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001791
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001792 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1793 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1794 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001795
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001796 (x' and y' are computed by appending the subtract and scale to the
1797 fDstToIndex matrix in the constructor).
1798
1799 Since the 'A' component of the quadratic is independent of x' and y', it
1800 is precomputed in the constructor. Since the 'B' component is linear in
1801 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001802 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001803 a perspective projection), it must be computed in the loop.
1804
1805*/
1806
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001807namespace {
1808
1809inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1810 SkScalar sr2d2, SkScalar foura,
1811 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001812 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001813 if (0 == foura) {
1814 return SkScalarToFixed(SkScalarDiv(-c, b));
1815 }
1816
reed@google.com84e9c082011-04-13 17:44:24 +00001817 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001818 if (discrim < 0) {
1819 discrim = -discrim;
1820 }
reed@google.com84e9c082011-04-13 17:44:24 +00001821 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1822 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001823 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001824 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001825 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001826 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001827 }
reed@google.com84e9c082011-04-13 17:44:24 +00001828 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001829}
1830
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001831typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1832 SkScalar fy, SkScalar dy,
1833 SkScalar b, SkScalar db,
1834 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001835 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001836 int count);
1837
1838void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1839 SkScalar fy, SkScalar dy,
1840 SkScalar b, SkScalar db,
1841 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001842 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001843 int count) {
1844 for (; count > 0; --count) {
1845 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1846 fOneOverTwoA, posRoot);
1847 SkFixed index = SkClampMax(t, 0xFFFF);
1848 SkASSERT(index <= 0xFFFF);
1849 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1850 fx += dx;
1851 fy += dy;
1852 b += db;
1853 }
1854}
1855void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1856 SkScalar fy, SkScalar dy,
1857 SkScalar b, SkScalar db,
1858 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001859 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001860 int count) {
1861 for (; count > 0; --count) {
1862 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1863 fOneOverTwoA, posRoot);
1864 SkFixed index = mirror_tileproc(t);
1865 SkASSERT(index <= 0xFFFF);
1866 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1867 fx += dx;
1868 fy += dy;
1869 b += db;
1870 }
1871}
1872
1873void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1874 SkScalar fy, SkScalar dy,
1875 SkScalar b, SkScalar db,
1876 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001877 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001878 int count) {
1879 for (; count > 0; --count) {
1880 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1881 fOneOverTwoA, posRoot);
1882 SkFixed index = repeat_tileproc(t);
1883 SkASSERT(index <= 0xFFFF);
1884 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1885 fx += dx;
1886 fy += dy;
1887 b += db;
1888 }
1889}
1890
1891
1892
1893}
1894
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001895class Two_Point_Radial_Gradient : public Gradient_Shader {
1896public:
1897 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1898 const SkPoint& end, SkScalar endRadius,
1899 const SkColor colors[], const SkScalar pos[],
1900 int colorCount, SkShader::TileMode mode,
1901 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001902 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1903 fCenter1(start),
1904 fCenter2(end),
1905 fRadius1(startRadius),
1906 fRadius2(endRadius) {
1907 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001908 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001909
1910 virtual BitmapType asABitmap(SkBitmap* bitmap,
1911 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001912 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001913 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001914 if (bitmap) {
1915 this->commonAsABitmap(bitmap);
1916 }
1917 SkScalar diffL = 0; // just to avoid gcc warning
1918 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001919 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001920 SkScalarSquare(fDiff.fY));
1921 }
1922 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001923 if (diffL) {
1924 SkScalar invDiffL = SkScalarInvert(diffL);
1925 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1926 SkScalarMul(invDiffL, fDiff.fX));
1927 } else {
1928 matrix->reset();
1929 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001930 matrix->preConcat(fPtsToUnit);
1931 }
1932 if (xy) {
1933 xy[0] = fTileMode;
1934 xy[1] = kClamp_TileMode;
1935 }
1936 if (NULL != twoPointRadialParams) {
1937 twoPointRadialParams[0] = diffL;
1938 twoPointRadialParams[1] = fStartRadius;
1939 twoPointRadialParams[2] = fDiffRadius;
1940 }
1941 return kTwoPointRadial_BitmapType;
1942 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001943
reed@google.com8e6d9142011-12-07 15:30:34 +00001944 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001945 if (info) {
1946 commonAsAGradient(info);
1947 info->fPoint[0] = fCenter1;
1948 info->fPoint[1] = fCenter2;
1949 info->fRadius[0] = fRadius1;
1950 info->fRadius[1] = fRadius2;
1951 }
1952 return kRadial2_GradientType;
1953 }
1954
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001955 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1956 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001957 SkASSERT(count > 0);
1958
1959 // Zero difference between radii: fill with transparent black.
1960 if (fDiffRadius == 0) {
1961 sk_bzero(dstC, count * sizeof(*dstC));
1962 return;
1963 }
1964 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1965 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001966 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001967
1968 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001969 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001970 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001971 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001972 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1973 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001974 SkScalar dx, fx = srcPt.fX;
1975 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001976
reed@google.com61eb0402011-04-15 12:11:12 +00001977 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001978 SkFixed fixedX, fixedY;
1979 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1980 dx = SkFixedToScalar(fixedX);
1981 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001982 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001983 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001984 dx = fDstToIndex.getScaleX();
1985 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001986 }
reed@google.com84e9c082011-04-13 17:44:24 +00001987 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1988 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1989 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1990 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001991
1992 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00001993 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001994 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00001995 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001996 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00001997 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001998 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001999 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002000 (*shadeProc)(fx, dx, fy, dy, b, db,
2001 fSr2D2, foura, fOneOverTwoA, posRoot,
2002 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00002003 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00002004 SkScalar dstX = SkIntToScalar(x);
2005 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002006 for (; count > 0; --count) {
2007 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00002008 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00002009 SkScalar fx = srcPt.fX;
2010 SkScalar fy = srcPt.fY;
2011 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
2012 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002013 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
2014 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002015 SkFixed index = proc(t);
2016 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002017 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00002018 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002019 }
2020 }
2021 }
2022
reed@android.com6c59a172009-09-22 20:24:05 +00002023 virtual bool setContext(const SkBitmap& device,
2024 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00002025 const SkMatrix& matrix) SK_OVERRIDE {
2026 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00002027 return false;
2028 }
2029
2030 // we don't have a span16 proc
2031 fFlags &= ~kHasSpan16_Flag;
2032 return true;
2033 }
2034
reed@google.com8e6d9142011-12-07 15:30:34 +00002035 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002036 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
2037 }
2038
reed@google.com7716afb2011-12-07 15:17:50 +00002039 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00002040 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002041 buffer.writeScalar(fCenter1.fX);
2042 buffer.writeScalar(fCenter1.fY);
2043 buffer.writeScalar(fCenter2.fX);
2044 buffer.writeScalar(fCenter2.fY);
2045 buffer.writeScalar(fRadius1);
2046 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002047 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002048
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002049protected:
reed@android.combcfc7332009-11-10 16:19:39 +00002050 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002051 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002052 fCenter1(unflatten_point(buffer)),
2053 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002054 fRadius1(buffer.readScalar()),
2055 fRadius2(buffer.readScalar()) {
2056 init();
reed@android.combcfc7332009-11-10 16:19:39 +00002057 };
reed@google.com7716afb2011-12-07 15:17:50 +00002058 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002059
2060private:
2061 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002062 const SkPoint fCenter1;
2063 const SkPoint fCenter2;
2064 const SkScalar fRadius1;
2065 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002066 SkPoint fDiff;
2067 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002068
2069 void init() {
2070 fDiff = fCenter1 - fCenter2;
2071 fDiffRadius = fRadius2 - fRadius1;
2072 SkScalar inv = SkScalarInvert(fDiffRadius);
2073 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2074 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2075 fStartRadius = SkScalarMul(fRadius1, inv);
2076 fSr2D2 = SkScalarSquare(fStartRadius);
2077 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002078 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002079
2080 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2081 fPtsToUnit.postScale(inv, inv);
2082 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002083};
2084
reed@android.com8a1c16f2008-12-17 15:59:43 +00002085///////////////////////////////////////////////////////////////////////////////
2086
2087class Sweep_Gradient : public Gradient_Shader {
2088public:
2089 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2090 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002091 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2092 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093 {
2094 fPtsToUnit.setTranslate(-cx, -cy);
2095 }
reed@google.com7716afb2011-12-07 15:17:50 +00002096 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2097 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002098
2099 virtual BitmapType asABitmap(SkBitmap* bitmap,
2100 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002101 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002102 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002103 if (bitmap) {
2104 this->commonAsABitmap(bitmap);
2105 }
2106 if (matrix) {
2107 *matrix = fPtsToUnit;
2108 }
2109 if (xy) {
2110 xy[0] = fTileMode;
2111 xy[1] = kClamp_TileMode;
2112 }
2113 return kSweep_BitmapType;
2114 }
2115
reed@google.com7716afb2011-12-07 15:17:50 +00002116 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002117 if (info) {
2118 commonAsAGradient(info);
2119 info->fPoint[0] = fCenter;
2120 }
2121 return kSweep_GradientType;
2122 }
2123
reed@google.com8e6d9142011-12-07 15:30:34 +00002124 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002125 return SkNEW_ARGS(Sweep_Gradient, (buffer));
2126 }
2127
reed@google.com7716afb2011-12-07 15:17:50 +00002128 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002129 this->INHERITED::flatten(buffer);
2130 buffer.writeScalar(fCenter.fX);
2131 buffer.writeScalar(fCenter.fY);
2132 }
2133
reed@android.com8a1c16f2008-12-17 15:59:43 +00002134protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002135 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
2136 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002137 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002138 }
2139
reed@google.com7716afb2011-12-07 15:17:50 +00002140 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141
2142private:
2143 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002144 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145};
2146
2147#ifdef COMPUTE_SWEEP_TABLE
2148#define PI 3.14159265
2149static bool gSweepTableReady;
2150static uint8_t gSweepTable[65];
2151
2152/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2153 We scale the results to [0..32]
2154*/
reed@google.com61eb0402011-04-15 12:11:12 +00002155static const uint8_t* build_sweep_table() {
2156 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157 const int N = 65;
2158 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002159
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 for (int i = 0; i < N; i++)
2161 {
2162 double arg = i / DENOM;
2163 double v = atan(arg);
2164 int iv = (int)round(v * DENOM * 2 / PI);
2165// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2166 printf("%d, ", iv);
2167 gSweepTable[i] = iv;
2168 }
2169 gSweepTableReady = true;
2170 }
2171 return gSweepTable;
2172}
2173#else
2174static const uint8_t gSweepTable[] = {
2175 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2176 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2177 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2178 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2179 32
2180};
2181static const uint8_t* build_sweep_table() { return gSweepTable; }
2182#endif
2183
2184// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2185// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2186// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2187
2188//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002189static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190 SkASSERT(numer <= denom);
2191 SkASSERT(numer > 0);
2192 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002193
reed@android.com8a1c16f2008-12-17 15:59:43 +00002194 int nbits = SkCLZ(numer);
2195 int dbits = SkCLZ(denom);
2196 int bits = 6 - nbits + dbits;
2197 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002198
reed@google.com61eb0402011-04-15 12:11:12 +00002199 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002200 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002201 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002202
2203 denom <<= dbits - 1;
2204 numer <<= nbits - 1;
2205
2206 unsigned result = 0;
2207
2208 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002209 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002210 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002211 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002212 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002213 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002214
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002216 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002217 // make room for the rest of the answer bits
2218 result <<= bits;
2219 switch (bits) {
2220 case 6:
2221 if ((numer = (numer << 1) - denom) >= 0)
2222 result |= 32;
2223 else
2224 numer += denom;
2225 case 5:
2226 if ((numer = (numer << 1) - denom) >= 0)
2227 result |= 16;
2228 else
2229 numer += denom;
2230 case 4:
2231 if ((numer = (numer << 1) - denom) >= 0)
2232 result |= 8;
2233 else
2234 numer += denom;
2235 case 3:
2236 if ((numer = (numer << 1) - denom) >= 0)
2237 result |= 4;
2238 else
2239 numer += denom;
2240 case 2:
2241 if ((numer = (numer << 1) - denom) >= 0)
2242 result |= 2;
2243 else
2244 numer += denom;
2245 case 1:
2246 default: // not strictly need, but makes GCC make better ARM code
2247 if ((numer = (numer << 1) - denom) >= 0)
2248 result |= 1;
2249 else
2250 numer += denom;
2251 }
2252 }
2253 return result;
2254}
2255
2256// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002257static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002258#ifdef SK_DEBUG
2259 {
2260 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002261 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002262 gOnce = true;
2263 SkASSERT(div_64(55, 55) == 64);
2264 SkASSERT(div_64(128, 256) == 32);
2265 SkASSERT(div_64(2326528, 4685824) == 31);
2266 SkASSERT(div_64(753664, 5210112) == 9);
2267 SkASSERT(div_64(229376, 4882432) == 3);
2268 SkASSERT(div_64(2, 64) == 2);
2269 SkASSERT(div_64(1, 64) == 1);
2270 // test that we handle underflow correctly
2271 SkASSERT(div_64(12345, 0x54321234) == 0);
2272 }
2273 }
2274#endif
2275
2276 SkASSERT(y > 0 && x > 0);
2277 const uint8_t* table = build_sweep_table();
2278
2279 unsigned result;
2280 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002281 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002282 // first part of the atan(v) = PI/2 - atan(1/v) identity
2283 // since our div_64 and table want v <= 1, where v = y/x
2284 SkTSwap<SkFixed>(x, y);
2285 }
2286
2287 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002288
reed@android.com8a1c16f2008-12-17 15:59:43 +00002289#ifdef SK_DEBUG
2290 {
2291 unsigned result2 = SkDivBits(y, x, 6);
2292 SkASSERT(result2 == result ||
2293 (result == 1 && result2 == 0));
2294 }
2295#endif
2296
2297 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2298 result = table[result];
2299
reed@google.com61eb0402011-04-15 12:11:12 +00002300 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002301 // complete the atan(v) = PI/2 - atan(1/v) identity
2302 result = 64 - result;
2303 // pin to 63
2304 result -= result >> 6;
2305 }
2306
2307 SkASSERT(result <= 63);
2308 return result;
2309}
2310
2311// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002312#ifdef SK_SCALAR_IS_FLOAT
2313static unsigned SkATan2_255(float y, float x) {
2314 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2315 static const float g255Over2PI = 40.584510488433314f;
2316
2317 float result = sk_float_atan2(y, x);
2318 if (result < 0) {
2319 result += 2 * SK_ScalarPI;
2320 }
2321 SkASSERT(result >= 0);
2322 // since our value is always >= 0, we can cast to int, which is faster than
2323 // calling floorf()
2324 int ir = (int)(result * g255Over2PI);
2325 SkASSERT(ir >= 0 && ir <= 255);
2326 return ir;
2327}
2328#else
reed@google.com61eb0402011-04-15 12:11:12 +00002329static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2330 if (x == 0) {
2331 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002332 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002333 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002334 return y < 0 ? 192 : 64;
2335 }
reed@google.com61eb0402011-04-15 12:11:12 +00002336 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002337 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002338 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002339
reed@android.com8a1c16f2008-12-17 15:59:43 +00002340 /* Find the right quadrant for x,y
2341 Since atan_0_90 only handles the first quadrant, we rotate x,y
2342 appropriately before calling it, and then add the right amount
2343 to account for the real quadrant.
2344 quadrant 0 : add 0 | x > 0 && y > 0
2345 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2346 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2347 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002348
reed@android.com8a1c16f2008-12-17 15:59:43 +00002349 map x<0 to (1 << 6)
2350 map y<0 to (3 << 6)
2351 add = map_x ^ map_y
2352 */
2353 int xsign = x >> 31;
2354 int ysign = y >> 31;
2355 int add = ((-xsign) ^ (ysign & 3)) << 6;
2356
2357#ifdef SK_DEBUG
2358 if (0 == add)
2359 SkASSERT(x > 0 && y > 0);
2360 else if (64 == add)
2361 SkASSERT(x < 0 && y > 0);
2362 else if (128 == add)
2363 SkASSERT(x < 0 && y < 0);
2364 else if (192 == add)
2365 SkASSERT(x > 0 && y < 0);
2366 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002367 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002368#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002369
reed@android.com8a1c16f2008-12-17 15:59:43 +00002370 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2371 where we need to rotate x,y by 90 or -90
2372 */
2373 x = (x ^ xsign) - xsign;
2374 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002375 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002376 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002377 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002378
2379 unsigned result = add + atan_0_90(y, x);
2380 SkASSERT(result < 256);
2381 return result;
2382}
reed@google.com51baf5a2011-09-21 13:38:36 +00002383#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002384
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002385void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2386 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002387 SkMatrix::MapXYProc proc = fDstToIndexProc;
2388 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002389 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002390 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002391
reed@google.com61eb0402011-04-15 12:11:12 +00002392 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002393 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2394 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002395 SkScalar dx, fx = srcPt.fX;
2396 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002397
reed@google.com61eb0402011-04-15 12:11:12 +00002398 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002399 SkFixed storage[2];
2400 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2401 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002402 dx = SkFixedToScalar(storage[0]);
2403 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002404 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002405 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002406 dx = matrix.getScaleX();
2407 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002408 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002409
reed@google.com61eb0402011-04-15 12:11:12 +00002410 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002411 *dstC++ = cache[SkATan2_255(fy, fx)];
2412 fx += dx;
2413 fy += dy;
2414 }
reed@google.com61eb0402011-04-15 12:11:12 +00002415 } else { // perspective case
2416 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002417 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002418 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2419 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002420 }
2421 }
2422}
2423
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002424void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2425 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002426 SkMatrix::MapXYProc proc = fDstToIndexProc;
2427 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002428 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002429 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002430 SkPoint srcPt;
2431
reed@google.com61eb0402011-04-15 12:11:12 +00002432 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002433 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2434 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002435 SkScalar dx, fx = srcPt.fX;
2436 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002437
reed@google.com61eb0402011-04-15 12:11:12 +00002438 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002439 SkFixed storage[2];
2440 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2441 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002442 dx = SkFixedToScalar(storage[0]);
2443 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002444 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002445 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002446 dx = matrix.getScaleX();
2447 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002448 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002449
reed@google.com61eb0402011-04-15 12:11:12 +00002450 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002451 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2452 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002453 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002454 fx += dx;
2455 fy += dy;
2456 }
reed@google.com61eb0402011-04-15 12:11:12 +00002457 } else { // perspective case
2458 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002459 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2460 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002461
reed@google.com51baf5a2011-09-21 13:38:36 +00002462 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002463 index >>= (8 - kCache16Bits);
2464 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002465 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002466 }
2467 }
2468}
2469
reed@google.com61eb0402011-04-15 12:11:12 +00002470///////////////////////////////////////////////////////////////////////////////
2471///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002472
2473// assumes colors is SkColor* and pos is SkScalar*
2474#define EXPAND_1_COLOR(count) \
2475 SkColor tmp[2]; \
2476 do { \
2477 if (1 == count) { \
2478 tmp[0] = tmp[1] = colors[0]; \
2479 colors = tmp; \
2480 pos = NULL; \
2481 count = 2; \
2482 } \
2483 } while (0)
2484
reed@google.com61eb0402011-04-15 12:11:12 +00002485SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2486 const SkColor colors[],
2487 const SkScalar pos[], int colorCount,
2488 SkShader::TileMode mode,
2489 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002490 if (NULL == pts || NULL == colors || colorCount < 1) {
2491 return NULL;
2492 }
2493 EXPAND_1_COLOR(colorCount);
2494
reed@android.comab840b82009-07-01 17:00:03 +00002495 return SkNEW_ARGS(Linear_Gradient,
2496 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002497}
2498
reed@google.com61eb0402011-04-15 12:11:12 +00002499SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2500 const SkColor colors[],
2501 const SkScalar pos[], int colorCount,
2502 SkShader::TileMode mode,
2503 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002504 if (radius <= 0 || NULL == colors || colorCount < 1) {
2505 return NULL;
2506 }
2507 EXPAND_1_COLOR(colorCount);
2508
reed@android.comab840b82009-07-01 17:00:03 +00002509 return SkNEW_ARGS(Radial_Gradient,
2510 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002511}
2512
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002513SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2514 SkScalar startRadius,
2515 const SkPoint& end,
2516 SkScalar endRadius,
2517 const SkColor colors[],
2518 const SkScalar pos[],
2519 int colorCount,
2520 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002521 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002522 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2523 return NULL;
2524 }
2525 EXPAND_1_COLOR(colorCount);
2526
2527 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002528 (start, startRadius, end, endRadius, colors, pos,
2529 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002530}
2531
reed@android.com8a1c16f2008-12-17 15:59:43 +00002532SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2533 const SkColor colors[],
2534 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002535 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002536 if (NULL == colors || count < 1) {
2537 return NULL;
2538 }
2539 EXPAND_1_COLOR(count);
2540
2541 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2542}
2543
caryclark@google.comd26147a2011-12-15 14:16:43 +00002544SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2545 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2546 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002547
caryclark@google.comd26147a2011-12-15 14:16:43 +00002548 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002549
caryclark@google.comd26147a2011-12-15 14:16:43 +00002550 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2551SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END