blob: ef3edc04f0333b36e8b54855b24ed9729b54fe42 [file] [log] [blame]
reed@google.com0e734bd2011-12-08 17:24:44 +00001
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkGradientShader.h"
tomhudson@google.come8c984d2012-01-09 13:45:36 +000011#include "SkClampRange.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000013#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000014#include "SkUnitMapper.h"
15#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000016#include "SkTemplates.h"
17#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000018
reed@google.com0e734bd2011-12-08 17:24:44 +000019#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
20 #define USE_DITHER_32BIT_GRADIENT
21#endif
22
reed@google.com5eb158d2011-04-15 15:50:34 +000023static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
24 int count) {
25 if (count > 0) {
26 if (v0 == v1) {
27 sk_memset32(dst, v0, count);
28 } else {
29 int pairs = count >> 1;
30 for (int i = 0; i < pairs; i++) {
31 *dst++ = v0;
32 *dst++ = v1;
33 }
34 if (count & 1) {
35 *dst = v0;
36 }
37 }
38 }
39}
40
reed@google.com61eb0402011-04-15 12:11:12 +000041///////////////////////////////////////////////////////////////////////////////
tomhudson@google.com9ce767c2011-04-25 20:49:39 +000042// Can't use a two-argument function with side effects like this in a
43// constructor's initializer's argument list because the order of
44// evaluations in that context is undefined (and backwards on linux/gcc).
45static SkPoint unflatten_point(SkReader32& buffer) {
46 SkPoint retval;
47 retval.fX = buffer.readScalar();
48 retval.fY = buffer.readScalar();
49 return retval;
50}
51
reed@google.comc98a0aa2012-02-02 19:33:08 +000052// Clamp
reed@android.com8a1c16f2008-12-17 15:59:43 +000053
reed@android.com41bccf52009-04-03 13:33:51 +000054static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000055 return SkClampMax(x, 0xFFFF);
56}
57
reed@google.comc98a0aa2012-02-02 19:33:08 +000058// Repeat
59
reed@android.com41bccf52009-04-03 13:33:51 +000060static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 return x & 0xFFFF;
62}
63
reed@google.comc98a0aa2012-02-02 19:33:08 +000064static inline int repeat_bits(int x, const int bits) {
65 return x & ((1 << bits) - 1);
66}
67
68static inline int repeat_8bits(int x) {
69 return x & 0xFF;
70}
71
72// Mirror
73
reed@android.com41bccf52009-04-03 13:33:51 +000074static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 int s = x << 15 >> 31;
76 return (x ^ s) & 0xFFFF;
77}
78
reed@android.com200645d2009-12-14 16:41:57 +000079static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000080#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000081 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000083 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000084#else
reed@android.com200645d2009-12-14 16:41:57 +000085 int s = x << (31 - bits) >> 31;
86 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000087#endif
88}
89
reed@android.com41bccf52009-04-03 13:33:51 +000090static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000091#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000092 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000093 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +000094 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 return x & 255;
96#else
97 int s = x << 23 >> 31;
98 return (x ^ s) & 0xFF;
99#endif
100}
101
reed@google.com61eb0402011-04-15 12:11:12 +0000102///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103
reed@google.comc98a0aa2012-02-02 19:33:08 +0000104typedef SkFixed (*TileProc)(SkFixed);
105
106static const TileProc gTileProcs[] = {
107 clamp_tileproc,
108 repeat_tileproc,
109 mirror_tileproc
110};
111
112///////////////////////////////////////////////////////////////////////////////
113///////////////////////////////////////////////////////////////////////////////
114
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115class Gradient_Shader : public SkShader {
116public:
117 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000118 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119 virtual ~Gradient_Shader();
120
121 // overrides
reed@google.com7716afb2011-12-07 15:17:50 +0000122 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
123 virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000124 virtual bool isOpaque() const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000126 enum {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000127 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
128 /// it, use a larger cache.
129 kCache16Bits = 8,
130 kGradient16Length = (1 << kCache16Bits),
131 /// Each cache gets 1 extra entry at the end so we don't have to
132 /// test for end-of-cache in lerps. This is also the value used
133 /// to stride *writes* into the dither cache; it must not be zero.
134 /// Total space for a cache is 2x kCache16Count entries: one
135 /// regular cache, one for dithering.
136 kCache16Count = kGradient16Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000137 kCache16Shift = 16 - kCache16Bits,
138 kSqrt16Shift = 8 - kCache16Bits,
139
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000140 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
141 /// it, use a larger cache.
142 kCache32Bits = 8,
143 kGradient32Length = (1 << kCache32Bits),
144 /// Each cache gets 1 extra entry at the end so we don't have to
145 /// test for end-of-cache in lerps. This is also the value used
146 /// to stride *writes* into the dither cache; it must not be zero.
147 /// Total space for a cache is 2x kCache32Count entries: one
148 /// regular cache, one for dithering.
149 kCache32Count = kGradient32Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000150 kCache32Shift = 16 - kCache32Bits,
151 kSqrt32Shift = 8 - kCache32Bits,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000152
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000153 /// This value is used to *read* the dither cache; it may be 0
154 /// if dithering is disabled.
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000155#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000156 kDitherStride32 = kCache32Count,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000157#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000158 kDitherStride32 = 0,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000159#endif
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000160 kDitherStride16 = kCache16Count,
161 kLerpRemainderMask32 = (1 << (16 - kCache32Bits)) - 1
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000162 };
163
164
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165protected:
166 Gradient_Shader(SkFlattenableReadBuffer& );
167 SkUnitMapper* fMapper;
168 SkMatrix fPtsToUnit; // set by subclass
169 SkMatrix fDstToIndex;
170 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171 TileMode fTileMode;
172 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000173 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 uint8_t fDstToIndexClass;
175 uint8_t fFlags;
176
177 struct Rec {
178 SkFixed fPos; // 0...1
179 uint32_t fScale; // (1 << 24) / range
180 };
181 Rec* fRecs;
182
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000184 const uint16_t* getCache16() const;
185 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186
reed@google.com7c2f27d2011-03-07 19:29:00 +0000187 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000188 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000189
reed@android.com8a1c16f2008-12-17 15:59:43 +0000190private:
191 enum {
192 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
193
reed@android.com1c12abe2009-07-02 15:01:02 +0000194 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 };
196 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000197 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
198 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000199
reed@google.com7c2f27d2011-03-07 19:29:00 +0000200 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
201 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202
reed@google.com7c2f27d2011-03-07 19:29:00 +0000203 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
204 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000205 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 +0000206
reed@android.com512a8762009-12-14 15:25:36 +0000207 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000208 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
209 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000210 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000211 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000212
reed@android.com8a1c16f2008-12-17 15:59:43 +0000213 typedef SkShader INHERITED;
214};
215
reed@android.com41bccf52009-04-03 13:33:51 +0000216static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000217 SkASSERT(x >= 0 && x <= SK_Scalar1);
218
219#ifdef SK_SCALAR_IS_FLOAT
220 return (unsigned)(x * 0xFFFF);
221#else
222 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
223#endif
224}
225
reed@android.com41bccf52009-04-03 13:33:51 +0000226Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
227 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000228 SkASSERT(colorCount > 1);
229
230 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
231
232 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000233 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234
reed@android.com8a1c16f2008-12-17 15:59:43 +0000235 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
236 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
237 fTileMode = mode;
238 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000239
reed@android.com41bccf52009-04-03 13:33:51 +0000240 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000241 fCache32 = NULL;
242 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243
reed@android.com41bccf52009-04-03 13:33:51 +0000244 /* Note: we let the caller skip the first and/or last position.
245 i.e. pos[0] = 0.3, pos[1] = 0.7
246 In these cases, we insert dummy entries to ensure that the final data
247 will be bracketed by [0, 1].
248 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
249
250 Thus colorCount (the caller's value, and fColorCount (our value) may
251 differ by up to 2. In the above example:
252 colorCount = 2
253 fColorCount = 4
254 */
255 fColorCount = colorCount;
256 // check if we need to add in dummy start and/or end position/colors
257 bool dummyFirst = false;
258 bool dummyLast = false;
259 if (pos) {
260 dummyFirst = pos[0] != 0;
261 dummyLast = pos[colorCount - 1] != SK_Scalar1;
262 fColorCount += dummyFirst + dummyLast;
263 }
264
265 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000266 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000267 fOrigColors = reinterpret_cast<SkColor*>(
268 sk_malloc_throw(size * fColorCount));
269 }
270 else {
271 fOrigColors = fStorage;
272 }
273
274 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000275 {
reed@android.com41bccf52009-04-03 13:33:51 +0000276 SkColor* origColors = fOrigColors;
277 if (dummyFirst) {
278 *origColors++ = colors[0];
279 }
280 memcpy(origColors, colors, colorCount * sizeof(SkColor));
281 if (dummyLast) {
282 origColors += colorCount;
283 *origColors = colors[colorCount - 1];
284 }
285 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286
reed@android.com1c12abe2009-07-02 15:01:02 +0000287 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000288 if (fColorCount > 2) {
289 Rec* recs = fRecs;
290 recs->fPos = 0;
291 // recs->fScale = 0; // unused;
292 recs += 1;
293 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000294 /* We need to convert the user's array of relative positions into
295 fixed-point positions and scale factors. We need these results
296 to be strictly monotonic (no two values equal or out of order).
297 Hence this complex loop that just jams a zero for the scale
298 value if it sees a segment out of order, and it assures that
299 we start at 0 and end at 1.0
300 */
301 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000302 int startIndex = dummyFirst ? 0 : 1;
303 int count = colorCount + dummyLast;
304 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305 // force the last value to be 1.0
306 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000307 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000308 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000309 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 }
reed@android.com41bccf52009-04-03 13:33:51 +0000312 // pin curr withing range
313 if (curr < 0) {
314 curr = 0;
315 } else if (curr > SK_Fixed1) {
316 curr = SK_Fixed1;
317 }
318 recs->fPos = curr;
319 if (curr > prev) {
320 recs->fScale = (1 << 24) / (curr - prev);
321 } else {
322 recs->fScale = 0; // ignore this segment
323 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000324 // get ready for the next value
325 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000326 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 }
reed@android.com41bccf52009-04-03 13:33:51 +0000328 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329 SkFixed dp = SK_Fixed1 / (colorCount - 1);
330 SkFixed p = dp;
331 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000332 for (int i = 1; i < colorCount; i++) {
333 recs->fPos = p;
334 recs->fScale = scale;
335 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 p += dp;
337 }
338 }
339 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000340 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000341}
342
343Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000344 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000345 fCacheAlpha = 256;
346
347 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
348
349 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000350 fCache32 = NULL;
351 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352
reed@android.com41bccf52009-04-03 13:33:51 +0000353 int colorCount = fColorCount = buffer.readU32();
354 if (colorCount > kColorStorageCount) {
355 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
356 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
357 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000359 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361
362 fTileMode = (TileMode)buffer.readU8();
363 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000364 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365 if (colorCount > 2) {
366 Rec* recs = fRecs;
367 recs[0].fPos = 0;
368 for (int i = 1; i < colorCount; i++) {
369 recs[i].fPos = buffer.readS32();
370 recs[i].fScale = buffer.readU32();
371 }
372 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000373 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000374 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000375}
376
reed@android.com41bccf52009-04-03 13:33:51 +0000377Gradient_Shader::~Gradient_Shader() {
378 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000380 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000381 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000382 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000384 }
reed@google.com82065d62011-02-07 15:30:46 +0000385 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000386}
387
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000388void Gradient_Shader::initCommon() {
389 fFlags = 0;
390 unsigned colorAlpha = 0xFF;
391 for (int i = 0; i < fColorCount; i++) {
392 colorAlpha &= SkColorGetA(fOrigColors[i]);
393 }
394 fColorsAreOpaque = colorAlpha == 0xFF;
395}
396
reed@android.com41bccf52009-04-03 13:33:51 +0000397void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000398 this->INHERITED::flatten(buffer);
399 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000400 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000401 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
402 buffer.write8(fTileMode);
403 if (fColorCount > 2) {
404 Rec* recs = fRecs;
405 for (int i = 1; i < fColorCount; i++) {
406 buffer.write32(recs[i].fPos);
407 buffer.write32(recs[i].fScale);
408 }
409 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000410 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411}
412
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000413bool Gradient_Shader::isOpaque() const {
414 return fColorsAreOpaque;
415}
416
reed@android.com8a1c16f2008-12-17 15:59:43 +0000417bool Gradient_Shader::setContext(const SkBitmap& device,
418 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000419 const SkMatrix& matrix) {
420 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000421 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000422 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000423
424 const SkMatrix& inverse = this->getTotalInverse();
425
426 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
427 return false;
428 }
429
430 fDstToIndexProc = fDstToIndex.getMapXYProc();
431 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
432
433 // now convert our colors in to PMColors
434 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435
436 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000437 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000438 fFlags |= kOpaqueAlpha_Flag;
439 }
440 // we can do span16 as long as our individual colors are opaque,
441 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000442 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000443 fFlags |= kHasSpan16_Flag;
444 }
445
reed@google.com95eed982011-07-05 17:01:56 +0000446 this->setCacheAlpha(paintAlpha);
447 return true;
448}
449
450void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451 // if the new alpha differs from the previous time we were called, inval our cache
452 // this will trigger the cache to be rebuilt.
453 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000454 if (fCacheAlpha != alpha) {
455 fCache16 = NULL; // inval the cache
456 fCache32 = NULL; // inval the cache
457 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000458 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000459 if (fCache32PixelRef) {
460 fCache32PixelRef->notifyPixelsChanged();
461 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000462 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000463}
464
reed@android.com41bccf52009-04-03 13:33:51 +0000465static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000466 SkASSERT(a == SkToU8(a));
467 SkASSERT(b == SkToU8(b));
468 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000469 return a + ((b - a) * scale >> 8);
470}
471
reed@android.com41bccf52009-04-03 13:33:51 +0000472static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
473 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000474#if 0
475 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
476 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
477 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
478 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
479
480 return SkPackARGB32(a, r, g, b);
481#else
482 int otherBlend = 256 - blend;
483
484#if 0
485 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
486 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
487 SkASSERT((t0 & t1) == 0);
488 return t0 | t1;
489#else
490 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
491 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
492#endif
493
494#endif
495}
496
497#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
498
reed@android.com41bccf52009-04-03 13:33:51 +0000499/** We take the original colors, not our premultiplied PMColors, since we can
500 build a 16bit table as long as the original colors are opaque, even if the
501 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000502*/
reed@android.com512a8762009-12-14 15:25:36 +0000503void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
504 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000505 SkASSERT(count > 1);
506 SkASSERT(SkColorGetA(c0) == 0xFF);
507 SkASSERT(SkColorGetA(c1) == 0xFF);
508
509 SkFixed r = SkColorGetR(c0);
510 SkFixed g = SkColorGetG(c0);
511 SkFixed b = SkColorGetB(c0);
512
513 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
514 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
515 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
516
517 r = SkIntToFixed(r) + 0x8000;
518 g = SkIntToFixed(g) + 0x8000;
519 b = SkIntToFixed(b) + 0x8000;
520
521 do {
522 unsigned rr = r >> 16;
523 unsigned gg = g >> 16;
524 unsigned bb = b >> 16;
525 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000526 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000527 cache += 1;
528 r += dr;
529 g += dg;
530 b += db;
531 } while (--count != 0);
532}
533
reed@google.com55b8e8c2011-01-13 16:22:35 +0000534/*
535 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
536 * semantics of how we 2x2 dither 32->16
537 */
538static inline U8CPU dither_fixed_to_8(SkFixed n) {
539 n >>= 8;
540 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
541}
542
543/*
544 * For dithering with premultiply, we want to ceiling the alpha component,
545 * to ensure that it is always >= any color component.
546 */
547static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
548 n >>= 8;
549 return ((n << 1) - (n | (n >> 8))) >> 8;
550}
551
552void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
553 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000554 SkASSERT(count > 1);
555
reed@android.com1c12abe2009-07-02 15:01:02 +0000556 // need to apply paintAlpha to our two endpoints
557 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
558 SkFixed da;
559 {
560 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
561 da = SkIntToFixed(tmp - a) / (count - 1);
562 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000563
reed@android.com1c12abe2009-07-02 15:01:02 +0000564 SkFixed r = SkColorGetR(c0);
565 SkFixed g = SkColorGetG(c0);
566 SkFixed b = SkColorGetB(c0);
567 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
568 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
569 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570
571 a = SkIntToFixed(a) + 0x8000;
572 r = SkIntToFixed(r) + 0x8000;
573 g = SkIntToFixed(g) + 0x8000;
574 b = SkIntToFixed(b) + 0x8000;
575
576 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000577 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000578 cache[kCache32Count] =
579 SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
580 dither_fixed_to_8(r),
581 dither_fixed_to_8(g),
582 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000583 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584 a += da;
585 r += dr;
586 g += dg;
587 b += db;
588 } while (--count != 0);
589}
590
reed@android.com41bccf52009-04-03 13:33:51 +0000591static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000592 SkASSERT((unsigned)x <= SK_Fixed1);
593 return x - (x >> 16);
594}
595
reed@android.com200645d2009-12-14 16:41:57 +0000596static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000597 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000598 if (6 == bits) {
599 return (x << 10) | (x << 4) | (x >> 2);
600 }
601 if (8 == bits) {
602 return (x << 8) | x;
603 }
604 sk_throw();
605 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606}
607
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000608/** We duplicate the last value in each half of the cache so that
609 interpolation doesn't have to special-case being at the last point.
610*/
611static void complete_16bit_cache(uint16_t* cache, int stride) {
612 cache[stride - 1] = cache[stride - 2];
613 cache[2 * stride - 1] = cache[2 * stride - 2];
614}
615
reed@google.com7c2f27d2011-03-07 19:29:00 +0000616const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000617 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000618 // double the count for dither entries
619 const int entryCount = kCache16Count * 2;
620 const size_t allocSize = sizeof(uint16_t) * entryCount;
621
reed@android.com3c9b2a42009-08-27 19:28:37 +0000622 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000623 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000624 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000626 if (fColorCount == 2) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000627 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
628 kGradient16Length);
reed@android.com41bccf52009-04-03 13:33:51 +0000629 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630 Rec* rec = fRecs;
631 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000632 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000633 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 SkASSERT(nextIndex < kCache16Count);
635
636 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000637 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 prevIndex = nextIndex;
639 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000640 // one extra space left over at the end for complete_16bit_cache()
641 SkASSERT(prevIndex == kGradient16Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642 }
643
reed@android.com41bccf52009-04-03 13:33:51 +0000644 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000645 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646 uint16_t* linear = fCache16; // just computed linear data
647 uint16_t* mapped = fCache16Storage; // storage for mapped data
648 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000649 for (int i = 0; i < kGradient16Length; i++) {
reed@android.com200645d2009-12-14 16:41:57 +0000650 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000652 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 }
654 sk_free(fCache16);
655 fCache16 = fCache16Storage;
656 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000657 complete_16bit_cache(fCache16, kCache16Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000658 }
659 return fCache16;
660}
661
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000662/** We duplicate the last value in each half of the cache so that
663 interpolation doesn't have to special-case being at the last point.
664*/
665static void complete_32bit_cache(SkPMColor* cache, int stride) {
666 cache[stride - 1] = cache[stride - 2];
667 cache[2 * stride - 1] = cache[2 * stride - 2];
668}
669
reed@google.com7c2f27d2011-03-07 19:29:00 +0000670const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000671 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000672 // double the count for dither entries
673 const int entryCount = kCache32Count * 2;
674 const size_t allocSize = sizeof(SkPMColor) * entryCount;
675
reed@google.comdc731fd2010-12-23 15:19:47 +0000676 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000677 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
678 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000679 }
680 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000681 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000682 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000683 kGradient32Length, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000684 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685 Rec* rec = fRecs;
686 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000687 for (int i = 1; i < fColorCount; i++) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000688 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000689 SkASSERT(nextIndex < kGradient32Length);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690
691 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000692 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
693 fOrigColors[i],
694 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 prevIndex = nextIndex;
696 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000697 SkASSERT(prevIndex == kGradient32Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698 }
699
reed@android.com41bccf52009-04-03 13:33:51 +0000700 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000701 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000702 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000704 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000706 for (int i = 0; i < kGradient32Length; i++) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000707 int index = map->mapUnit16((i << 8) | i) >> 8;
708 mapped[i] = linear[index];
709 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000710 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000711 fCache32PixelRef->unref();
712 fCache32PixelRef = newPR;
713 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000715 complete_32bit_cache(fCache32, kCache32Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 }
717 return fCache32;
718}
719
reed@google.comdc731fd2010-12-23 15:19:47 +0000720/*
721 * Because our caller might rebuild the same (logically the same) gradient
722 * over and over, we'd like to return exactly the same "bitmap" if possible,
723 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
724 * To do that, we maintain a private cache of built-bitmaps, based on our
725 * colors and positions. Note: we don't try to flatten the fMapper, so if one
726 * is present, we skip the cache for now.
727 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000728void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000729 // our caller assumes no external alpha, so we ensure that our cache is
730 // built with 0xFF
731 this->setCacheAlpha(0xFF);
732
reed@google.comdc731fd2010-12-23 15:19:47 +0000733 // don't have a way to put the mapper into our cache-key yet
734 if (fMapper) {
735 // force our cahce32pixelref to be built
736 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000737 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000738 bitmap->setPixelRef(fCache32PixelRef);
739 return;
740 }
741
742 // build our key: [numColors + colors[] + {positions[]} ]
743 int count = 1 + fColorCount;
744 if (fColorCount > 2) {
745 count += fColorCount - 1; // fRecs[].fPos
746 }
747
748 SkAutoSTMalloc<16, int32_t> storage(count);
749 int32_t* buffer = storage.get();
750
751 *buffer++ = fColorCount;
752 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
753 buffer += fColorCount;
754 if (fColorCount > 2) {
755 for (int i = 1; i < fColorCount; i++) {
756 *buffer++ = fRecs[i].fPos;
757 }
758 }
759 SkASSERT(buffer - storage.get() == count);
760
761 ///////////////////////////////////
762
digit@google.com1771cbf2012-01-26 21:26:40 +0000763 SK_DECLARE_STATIC_MUTEX(gMutex);
reed@google.comdc731fd2010-12-23 15:19:47 +0000764 static SkBitmapCache* gCache;
765 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
766 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
767 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000768
reed@google.comdc731fd2010-12-23 15:19:47 +0000769 if (NULL == gCache) {
770 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
771 }
772 size_t size = count * sizeof(int32_t);
773
774 if (!gCache->find(storage.get(), size, bitmap)) {
775 // force our cahce32pixelref to be built
776 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000777 // Only expose the linear section of the cache; don't let the caller
778 // know about the padding at the end to make interpolation faster.
779 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000780 bitmap->setPixelRef(fCache32PixelRef);
781
782 gCache->add(storage.get(), size, *bitmap);
783 }
784}
785
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000786void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
787 if (info) {
788 if (info->fColorCount >= fColorCount) {
789 if (info->fColors) {
790 memcpy(info->fColors, fOrigColors,
791 fColorCount * sizeof(SkColor));
792 }
793 if (info->fColorOffsets) {
794 if (fColorCount == 2) {
795 info->fColorOffsets[0] = 0;
796 info->fColorOffsets[1] = SK_Scalar1;
797 } else if (fColorCount > 2) {
798 for (int i = 0; i < fColorCount; i++)
799 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
800 }
801 }
802 }
803 info->fColorCount = fColorCount;
804 info->fTileMode = fTileMode;
805 }
806}
807
reed@google.com61eb0402011-04-15 12:11:12 +0000808///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809
reed@android.com41bccf52009-04-03 13:33:51 +0000810static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811 SkVector vec = pts[1] - pts[0];
812 SkScalar mag = vec.length();
813 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
814
815 vec.scale(inv);
816 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
817 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
818 matrix->postScale(inv, inv);
819}
820
821///////////////////////////////////////////////////////////////////////////////
822
823class Linear_Gradient : public Gradient_Shader {
824public:
825 Linear_Gradient(const SkPoint pts[2],
826 const SkColor colors[], const SkScalar pos[], int colorCount,
827 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000828 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
829 fStart(pts[0]),
830 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831 {
832 pts_to_unit_matrix(pts, &fPtsToUnit);
833 }
reed@android.com9b46e772009-06-05 12:24:41 +0000834
reed@google.com7716afb2011-12-07 15:17:50 +0000835 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
836 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
837 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
838 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
839 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
840 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841
reed@google.com55b8e8c2011-01-13 16:22:35 +0000842 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843 return SkNEW_ARGS(Linear_Gradient, (buffer));
844 }
845
reed@google.com7716afb2011-12-07 15:17:50 +0000846 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000847 this->INHERITED::flatten(buffer);
848 buffer.writeScalar(fStart.fX);
849 buffer.writeScalar(fStart.fY);
850 buffer.writeScalar(fEnd.fX);
851 buffer.writeScalar(fEnd.fY);
852 }
853
caryclark@google.comd26147a2011-12-15 14:16:43 +0000854 SK_DECLARE_FLATTENABLE_REGISTRAR()
855
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000857 Linear_Gradient(SkFlattenableReadBuffer& buffer)
858 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000859 fStart(unflatten_point(buffer)),
860 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000861 }
reed@google.com7716afb2011-12-07 15:17:50 +0000862 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863
864private:
865 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000866 const SkPoint fStart;
867 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868};
869
reed@android.com5119bdb2009-06-12 21:27:03 +0000870bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
871 const SkMatrix& matrix) {
872 if (!this->INHERITED::setContext(device, paint, matrix)) {
873 return false;
874 }
875
876 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
877 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000878 fFlags |= SkShader::kConstInY32_Flag;
879 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
880 // only claim this if we do have a 16bit mode (i.e. none of our
881 // colors have alpha), and if we are not dithering (which obviously
882 // is not const in Y).
883 fFlags |= SkShader::kConstInY16_Flag;
884 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000885 }
886 return true;
887}
888
reed@google.com5eb158d2011-04-15 15:50:34 +0000889#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000890 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000891 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000892 SkASSERT(fi <= 0xFF); \
893 fx += dx; \
894 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000895 toggle ^= Gradient_Shader::kDitherStride32; \
reed@google.com13659f12011-04-18 19:59:38 +0000896 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000897
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000898namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000899
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000900typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000901 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000902 int toggle, int count);
903
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000904// This function is deprecated, and will be replaced by
905// shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it.
906void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
907 SkPMColor* SK_RESTRICT dstC,
908 const SkPMColor* SK_RESTRICT cache,
909 int toggle, int count) {
910 // We're a vertical gradient, so no change in a span.
911 // If colors change sharply across the gradient, dithering is
912 // insufficient (it subsamples the color space) and we need to lerp.
913 unsigned fullIndex = proc(fx);
914 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
915 sk_memset32_dither(dstC,
916 cache[toggle + fi],
917 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
918 count);
919}
920
921// Linear interpolation (lerp) is unnecessary if there are no sharp
922// discontinuities in the gradient - which must be true if there are
923// only 2 colors - but it's cheap.
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000924void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
925 SkPMColor* SK_RESTRICT dstC,
926 const SkPMColor* SK_RESTRICT cache,
927 int toggle, int count) {
928 // We're a vertical gradient, so no change in a span.
929 // If colors change sharply across the gradient, dithering is
930 // insufficient (it subsamples the color space) and we need to lerp.
931 unsigned fullIndex = proc(fx);
932 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
933 unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32;
934 SkPMColor lerp =
935 SkFastFourByteInterp(
936 cache[toggle + fi + 1],
937 cache[toggle + fi], remainder);
938 SkPMColor dlerp =
939 SkFastFourByteInterp(
940 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1],
941 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], remainder);
942 sk_memset32_dither(dstC, lerp, dlerp, count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000943}
944
945void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
946 SkPMColor* SK_RESTRICT dstC,
947 const SkPMColor* SK_RESTRICT cache,
948 int toggle, int count) {
949 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000950 range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000951
952 if ((count = range.fCount0) > 0) {
953 sk_memset32_dither(dstC,
954 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000955 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000956 count);
957 dstC += count;
958 }
959 if ((count = range.fCount1) > 0) {
960 int unroll = count >> 3;
961 fx = range.fFx1;
962 for (int i = 0; i < unroll; i++) {
963 NO_CHECK_ITER; NO_CHECK_ITER;
964 NO_CHECK_ITER; NO_CHECK_ITER;
965 NO_CHECK_ITER; NO_CHECK_ITER;
966 NO_CHECK_ITER; NO_CHECK_ITER;
967 }
968 if ((count &= 7) > 0) {
969 do {
970 NO_CHECK_ITER;
971 } while (--count != 0);
972 }
973 }
974 if ((count = range.fCount2) > 0) {
975 sk_memset32_dither(dstC,
976 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000977 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000978 count);
979 }
980}
981
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000982void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
983 SkPMColor* SK_RESTRICT dstC,
984 const SkPMColor* SK_RESTRICT cache,
985 int toggle, int count) {
986 do {
987 unsigned fi = mirror_8bits(fx >> 8);
988 SkASSERT(fi <= 0xFF);
989 fx += dx;
990 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000991 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000992 } while (--count != 0);
993}
994
995void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
996 SkPMColor* SK_RESTRICT dstC,
997 const SkPMColor* SK_RESTRICT cache,
998 int toggle, int count) {
999 do {
1000 unsigned fi = repeat_8bits(fx >> 8);
1001 SkASSERT(fi <= 0xFF);
1002 fx += dx;
1003 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001004 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001005 } while (--count != 0);
1006}
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001007
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001008}
1009
1010void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1011 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012 SkASSERT(count > 0);
1013
1014 SkPoint srcPt;
1015 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1016 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001017 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +00001018#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001019 int toggle = ((x ^ y) & 1) * kDitherStride32;
reed@google.com0e734bd2011-12-08 17:24:44 +00001020#else
1021 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +00001022#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023
reed@android.comc552a432009-06-12 20:02:50 +00001024 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001025 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1026 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001027 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1028
reed@android.comc552a432009-06-12 20:02:50 +00001029 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001030 SkFixed dxStorage[1];
1031 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1032 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +00001033 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001034 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1035 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1036 }
1037
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001038 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +00001039 if (SkFixedNearlyZero(dx)) {
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001040#ifdef SK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS
1041 if (fColorCount > 2) {
1042 shadeProc = shadeSpan_linear_vertical_lerp;
1043 } else {
1044 shadeProc = shadeSpan_linear_vertical;
1045 }
1046#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001047 shadeProc = shadeSpan_linear_vertical_lerp;
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001048#endif
reed@android.comc552a432009-06-12 20:02:50 +00001049 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001050 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +00001051 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001052 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +00001053 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001054 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001055 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001056 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +00001057 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001058 SkScalar dstX = SkIntToScalar(x);
1059 SkScalar dstY = SkIntToScalar(y);
1060 do {
1061 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1062 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1063 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001064 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001065 toggle ^= Gradient_Shader::kDitherStride32;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001066 dstX += SK_Scalar1;
1067 } while (--count != 0);
1068 }
1069}
1070
reed@google.com55b8e8c2011-01-13 16:22:35 +00001071SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +00001072 SkMatrix* matrix,
1073 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +00001074 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001076 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001077 }
1078 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001079 matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001080 matrix->preConcat(fPtsToUnit);
1081 }
1082 if (xy) {
1083 xy[0] = fTileMode;
1084 xy[1] = kClamp_TileMode;
1085 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001086 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001087}
1088
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001089SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1090 if (info) {
1091 commonAsAGradient(info);
1092 info->fPoint[0] = fStart;
1093 info->fPoint[1] = fEnd;
1094 }
1095 return kLinear_GradientType;
1096}
1097
reed@android.com3c9b2a42009-08-27 19:28:37 +00001098static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1099 int count) {
1100 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001101 *dst++ = value;
1102 count -= 1;
1103 SkTSwap(value, other);
1104 }
1105
1106 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001107
reed@android.com3c9b2a42009-08-27 19:28:37 +00001108 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001109 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001110 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001112
reed@google.com5eb158d2011-04-15 15:50:34 +00001113#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001114 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001115 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001116 SkASSERT(fi < Gradient_Shader::kCache16Count); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001117 fx += dx; \
1118 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001119 toggle ^= Gradient_Shader::kDitherStride16; \
reed@google.com13659f12011-04-18 19:59:38 +00001120 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001121
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001122namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001123
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001124typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001125 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001126 int toggle, int count);
1127
1128void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1129 uint16_t* SK_RESTRICT dstC,
1130 const uint16_t* SK_RESTRICT cache,
1131 int toggle, int count) {
1132 // we're a vertical gradient, so no change in a span
1133 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001134 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001135 dither_memset16(dstC, cache[toggle + fi],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001136 cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001137
1138}
1139
1140void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1141 uint16_t* SK_RESTRICT dstC,
1142 const uint16_t* SK_RESTRICT cache,
1143 int toggle, int count) {
1144 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001145 range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001146
1147 if ((count = range.fCount0) > 0) {
1148 dither_memset16(dstC,
1149 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001150 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001151 count);
1152 dstC += count;
1153 }
1154 if ((count = range.fCount1) > 0) {
1155 int unroll = count >> 3;
1156 fx = range.fFx1;
1157 for (int i = 0; i < unroll; i++) {
1158 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1159 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1160 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1161 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1162 }
1163 if ((count &= 7) > 0) {
1164 do {
1165 NO_CHECK_ITER_16;
1166 } while (--count != 0);
1167 }
1168 }
1169 if ((count = range.fCount2) > 0) {
1170 dither_memset16(dstC,
1171 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001172 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001173 count);
1174 }
1175}
1176
1177void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1178 uint16_t* SK_RESTRICT dstC,
1179 const uint16_t* SK_RESTRICT cache,
1180 int toggle, int count) {
1181 do {
1182 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1183 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001184 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001185 fx += dx;
1186 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001187 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001188 } while (--count != 0);
1189}
1190
1191void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1192 uint16_t* SK_RESTRICT dstC,
1193 const uint16_t* SK_RESTRICT cache,
1194 int toggle, int count) {
1195 SkASSERT(proc == repeat_tileproc);
1196 do {
1197 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1198 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001199 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001200 fx += dx;
1201 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001202 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001203 } while (--count != 0);
1204}
1205}
1206
1207void Linear_Gradient::shadeSpan16(int x, int y,
1208 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001209 SkASSERT(count > 0);
1210
1211 SkPoint srcPt;
1212 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1213 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001214 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001215 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001216
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001217 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001218 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1219 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1221
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001222 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001223 SkFixed dxStorage[1];
1224 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1225 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001226 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1228 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1229 }
1230
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001231 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001232 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001233 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001234 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001235 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001236 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001237 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001238 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001239 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001241 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001242 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243 SkScalar dstX = SkIntToScalar(x);
1244 SkScalar dstY = SkIntToScalar(y);
1245 do {
1246 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1247 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1248 SkASSERT(fi <= 0xFFFF);
1249
reed@android.com512a8762009-12-14 15:25:36 +00001250 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001251 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001252 toggle ^= Gradient_Shader::kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253
1254 dstX += SK_Scalar1;
1255 } while (--count != 0);
1256 }
1257}
1258
1259///////////////////////////////////////////////////////////////////////////////
1260
1261#define kSQRT_TABLE_BITS 11
1262#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1263
1264#include "SkRadialGradient_Table.h"
1265
1266#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1267
1268#include <stdio.h>
1269
reed@google.com61eb0402011-04-15 12:11:12 +00001270void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1272
1273 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1274 SkASSERT(file);
1275 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1276
reed@google.com61eb0402011-04-15 12:11:12 +00001277 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1278 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001280 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001281
1282 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1283
1284 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001285 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001287 }
1288 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001290 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291 }
1292 ::fprintf(file, "};\n");
1293 ::fclose(file);
1294}
1295
1296#endif
1297
1298
reed@google.com61eb0402011-04-15 12:11:12 +00001299static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1300 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301 SkScalar inv = SkScalarInvert(radius);
1302
1303 matrix->setTranslate(-center.fX, -center.fY);
1304 matrix->postScale(inv, inv);
1305}
1306
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001307
1308namespace {
1309
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001310typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
1311 SkScalar sfy, SkScalar sdy,
1312 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001313 int toggle, int count);
1314
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001315void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
1316 SkScalar sfy, SkScalar sdy,
1317 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001318 int toggle, int count) {
1319 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1320
1321 /* knock these down so we can pin against +- 0x7FFF, which is an
1322 immediate load, rather than 0xFFFF which is slower. This is a
1323 compromise, since it reduces our precision, but that appears
1324 to be visually OK. If we decide this is OK for all of our cases,
1325 we could (it seems) put this scale-down into fDstToIndex,
1326 to avoid having to do these extra shifts each time.
1327 */
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001328 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1329 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1330 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1331 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001332 // might perform this check for the other modes,
1333 // but the win will be a smaller % of the total
1334 if (dy == 0) {
1335 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1336 fy *= fy;
1337 do {
1338 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1339 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1340 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1341 fx += dx;
1342 *dstC++ = cache[toggle +
1343 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001344 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001345 } while (--count != 0);
1346 } else {
1347 do {
1348 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1349 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1350 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1351 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1352 fx += dx;
1353 fy += dy;
1354 *dstC++ = cache[toggle +
1355 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001356 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001357 } while (--count != 0);
1358 }
1359}
1360
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001361void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
1362 SkScalar sfy, SkScalar sdy,
1363 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001364 int toggle, int count) {
1365 do {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001366#ifdef SK_SCALAR_IS_FLOAT
1367 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1368 SkFixed dist = SkFloatToFixed(fdist);
1369#else
1370 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1371 SkFixedSquare(sfy);
1372 if (magnitudeSquared < 0) // Overflow.
1373 magnitudeSquared = SK_FixedMax;
1374 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1375#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001376 unsigned fi = mirror_tileproc(dist);
1377 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001378 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001379 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001380 sfx += sdx;
1381 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001382 } while (--count != 0);
1383}
1384
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001385void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
1386 SkScalar sfy, SkScalar sdy,
1387 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001388 int toggle, int count) {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001389 SkFixed fx = SkScalarToFixed(sfx);
1390 SkFixed dx = SkScalarToFixed(sdx);
1391 SkFixed fy = SkScalarToFixed(sfy);
1392 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001393 do {
1394 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1395 unsigned fi = repeat_tileproc(dist);
1396 SkASSERT(fi <= 0xFFFF);
1397 fx += dx;
1398 fy += dy;
1399 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001400 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001401 } while (--count != 0);
1402}
1403
1404}
1405
reed@android.com8a1c16f2008-12-17 15:59:43 +00001406class Radial_Gradient : public Gradient_Shader {
1407public:
1408 Radial_Gradient(const SkPoint& center, SkScalar radius,
1409 const SkColor colors[], const SkScalar pos[], int colorCount,
1410 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001411 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1412 fCenter(center),
1413 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414 {
1415 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1416 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1417
1418 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1419 }
reed@google.com61eb0402011-04-15 12:11:12 +00001420
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001421 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
1422 SK_OVERRIDE;
1423 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
1424 int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001425 SkASSERT(count > 0);
1426
1427 SkPoint srcPt;
1428 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1429 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001430 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001431 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001432
reed@android.com3c9b2a42009-08-27 19:28:37 +00001433 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001434 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1435 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001436
1437 SkScalar sdx = fDstToIndex.getScaleX();
1438 SkScalar sdy = fDstToIndex.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001439
reed@android.com3c9b2a42009-08-27 19:28:37 +00001440 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 SkFixed storage[2];
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001442 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1443 &storage[0], &storage[1]);
1444 sdx = SkFixedToScalar(storage[0]);
1445 sdy = SkFixedToScalar(storage[1]);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001446 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448 }
1449
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001450 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001451 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001452 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001453 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001454 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001455 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 }
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001458 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
1459 cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001460 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461 SkScalar dstX = SkIntToScalar(x);
1462 SkScalar dstY = SkIntToScalar(y);
1463 do {
1464 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1465 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1466 SkASSERT(fi <= 0xFFFF);
1467
1468 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001470 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471
1472 dstX += SK_Scalar1;
1473 } while (--count != 0);
1474 }
1475 }
1476
reed@google.com55b8e8c2011-01-13 16:22:35 +00001477 virtual BitmapType asABitmap(SkBitmap* bitmap,
1478 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001479 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001480 SkScalar* twoPointRadialParams)
1481 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001482 if (bitmap) {
1483 this->commonAsABitmap(bitmap);
1484 }
1485 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001486 matrix->setScale(SkIntToScalar(kGradient32Length),
1487 SkIntToScalar(kGradient32Length));
reed@google.comdc731fd2010-12-23 15:19:47 +00001488 matrix->preConcat(fPtsToUnit);
1489 }
1490 if (xy) {
1491 xy[0] = fTileMode;
1492 xy[1] = kClamp_TileMode;
1493 }
1494 return kRadial_BitmapType;
1495 }
reed@google.com7716afb2011-12-07 15:17:50 +00001496 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001497 if (info) {
1498 commonAsAGradient(info);
1499 info->fPoint[0] = fCenter;
1500 info->fRadius[0] = fRadius;
1501 }
1502 return kRadial_GradientType;
1503 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001504
reed@google.com8e6d9142011-12-07 15:30:34 +00001505 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506 return SkNEW_ARGS(Radial_Gradient, (buffer));
1507 }
1508
reed@google.com7716afb2011-12-07 15:17:50 +00001509 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001510 this->INHERITED::flatten(buffer);
1511 buffer.writeScalar(fCenter.fX);
1512 buffer.writeScalar(fCenter.fY);
1513 buffer.writeScalar(fRadius);
1514 }
1515
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001517 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1518 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001519 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001520 fRadius(buffer.readScalar()) {
1521 }
reed@google.com7716afb2011-12-07 15:17:50 +00001522 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523
1524private:
1525 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001526 const SkPoint fCenter;
1527 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528};
1529
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001530namespace {
1531
1532inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001533 // fast, overly-conservative test: checks unit square instead
1534 // of unit circle
1535 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1536 (fx <= -SK_FixedHalf && dx <= 0);
1537 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1538 (fy <= -SK_FixedHalf && dy <= 0);
1539
1540 return xClamped || yClamped;
1541}
1542
1543// Return true if (fx * fy) is always inside the unit circle
1544// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1545// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001546inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001547 int fy, int dy, int count) {
1548 SkASSERT(count > 0);
1549 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1550 return false;
1551 }
1552 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1553 return false;
1554 }
1555 fx += (count - 1) * dx;
1556 fy += (count - 1) * dy;
1557 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1558 return false;
1559 }
1560 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1561}
1562
1563#define UNPINNED_RADIAL_STEP \
1564 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001565 *dstC++ = cache[toggle + \
1566 (sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift)]; \
1567 toggle ^= Gradient_Shader::kDitherStride32; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001568 fx += dx; \
1569 fy += dy;
1570
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001571typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1572 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001573 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001574 int count, int toggle);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001575
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001576// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001577void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
1578 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001579 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001580 int count, int toggle) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001581 // Floating point seems to be slower than fixed point,
1582 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001583 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001584 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1585 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1586 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1587 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001588 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001589 unsigned fi = Gradient_Shader::kGradient32Length;
1590 sk_memset32_dither(dstC,
1591 cache[toggle + fi],
1592 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
1593 count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001594 } else if ((count > 4) &&
1595 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1596 unsigned fi;
1597 // 4x unroll appears to be no faster than 2x unroll on Linux
1598 while (count > 1) {
1599 UNPINNED_RADIAL_STEP;
1600 UNPINNED_RADIAL_STEP;
1601 count -= 2;
1602 }
1603 if (count) {
1604 UNPINNED_RADIAL_STEP;
1605 }
1606 }
1607 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001608 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1609 if (dy == 0) {
1610 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1611 yy *= yy;
1612 do {
1613 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1614 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1615 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001616 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1617 Gradient_Shader::kSqrt32Shift)];
1618 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001619 fx += dx;
1620 } while (--count != 0);
1621 } else {
1622 do {
1623 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1624 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1625 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1626 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001627 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1628 Gradient_Shader::kSqrt32Shift)];
1629 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001630 fx += dx;
1631 fy += dy;
1632 } while (--count != 0);
1633 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001634 }
1635}
1636
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001637// Unrolling this loop doesn't seem to help (when float); we're stalling to
1638// get the results of the sqrt (?), and don't have enough extra registers to
1639// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001640void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1641 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001642 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001643 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001644 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001645#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001646 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1647 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001648#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001649 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1650 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001651 if (magnitudeSquared < 0) // Overflow.
1652 magnitudeSquared = SK_FixedMax;
1653 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001654#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001655 unsigned fi = mirror_tileproc(dist);
1656 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001657 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1658 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001659 sfx += sdx;
1660 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001661 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001662}
1663
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001664void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1665 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001666 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001667 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001668 SkFixed fx = SkScalarToFixed(sfx);
1669 SkFixed dx = SkScalarToFixed(sdx);
1670 SkFixed fy = SkScalarToFixed(sfy);
1671 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001672 do {
1673 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1674 SkFixedSquare(fy);
1675 if (magnitudeSquared < 0) // Overflow.
1676 magnitudeSquared = SK_FixedMax;
1677 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1678 unsigned fi = repeat_tileproc(dist);
1679 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001680 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1681 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001682 fx += dx;
1683 fy += dy;
1684 } while (--count != 0);
1685}
1686}
1687
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001688void Radial_Gradient::shadeSpan(int x, int y,
1689 SkPMColor* SK_RESTRICT dstC, int count) {
1690 SkASSERT(count > 0);
1691
1692 SkPoint srcPt;
1693 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1694 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001695 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001696#ifdef USE_DITHER_32BIT_GRADIENT
1697 int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32;
1698#else
1699 int toggle = 0;
1700#endif
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001701
1702 if (fDstToIndexClass != kPerspective_MatrixClass) {
1703 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1704 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001705 SkScalar sdx = fDstToIndex.getScaleX();
1706 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001707
1708 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1709 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001710 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1711 &storage[0], &storage[1]);
1712 sdx = SkFixedToScalar(storage[0]);
1713 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001714 } else {
1715 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001716 }
1717
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001718 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001719 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001720 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001721 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001722 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001723 } else {
1724 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001725 }
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001726 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001727 } else { // perspective case
1728 SkScalar dstX = SkIntToScalar(x);
1729 SkScalar dstY = SkIntToScalar(y);
1730 do {
1731 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1732 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1733 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001734 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001735 dstX += SK_Scalar1;
1736 } while (--count != 0);
1737 }
1738}
1739
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001740/* Two-point radial gradients are specified by two circles, each with a center
1741 point and radius. The gradient can be considered to be a series of
1742 concentric circles, with the color interpolated from the start circle
1743 (at t=0) to the end circle (at t=1).
1744
1745 For each point (x, y) in the span, we want to find the
1746 interpolated circle that intersects that point. The center
1747 of the desired circle (Cx, Cy) falls at some distance t
1748 along the line segment between the start point (Sx, Sy) and
1749 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001750
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001751 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1752 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001753
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001754 The radius of the desired circle (r) is also a linear interpolation t
1755 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001756
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001757 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001758
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001759 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001760
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001761 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001762
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001763 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001764
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001765 (x - ((1 - t) * Sx + t * Ex))^2
1766 + (y - ((1 - t) * Sy + t * Ey))^2
1767 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001768
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001769 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001770
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001771 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1772 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1773 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001774
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001775 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1776
1777 [Dx^2 + Dy^2 - Dr^2)] * t^2
1778 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1779 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001780
1781 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001782 possible circles on which the point may fall. Solving for t yields
1783 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001784
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001785 If a<0, the start circle is entirely contained in the
1786 end circle, and one of the roots will be <0 or >1 (off the line
1787 segment). If a>0, the start circle falls at least partially
1788 outside the end circle (or vice versa), and the gradient
1789 defines a "tube" where a point may be on one circle (on the
1790 inside of the tube) or the other (outside of the tube). We choose
1791 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001792
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001793 In order to keep the math to within the limits of fixed point,
1794 we divide the entire quadratic by Dr^2, and replace
1795 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001796
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001797 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1798 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1799 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001800
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001801 (x' and y' are computed by appending the subtract and scale to the
1802 fDstToIndex matrix in the constructor).
1803
1804 Since the 'A' component of the quadratic is independent of x' and y', it
1805 is precomputed in the constructor. Since the 'B' component is linear in
1806 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001807 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001808 a perspective projection), it must be computed in the loop.
1809
1810*/
1811
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001812namespace {
1813
1814inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1815 SkScalar sr2d2, SkScalar foura,
1816 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001817 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001818 if (0 == foura) {
1819 return SkScalarToFixed(SkScalarDiv(-c, b));
1820 }
1821
reed@google.com84e9c082011-04-13 17:44:24 +00001822 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001823 if (discrim < 0) {
1824 discrim = -discrim;
1825 }
reed@google.com84e9c082011-04-13 17:44:24 +00001826 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1827 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001828 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001829 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001830 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001831 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001832 }
reed@google.com84e9c082011-04-13 17:44:24 +00001833 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001834}
1835
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001836typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1837 SkScalar fy, SkScalar dy,
1838 SkScalar b, SkScalar db,
1839 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001840 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001841 int count);
1842
1843void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1844 SkScalar fy, SkScalar dy,
1845 SkScalar b, SkScalar db,
1846 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001847 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001848 int count) {
1849 for (; count > 0; --count) {
1850 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1851 fOneOverTwoA, posRoot);
1852 SkFixed index = SkClampMax(t, 0xFFFF);
1853 SkASSERT(index <= 0xFFFF);
1854 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1855 fx += dx;
1856 fy += dy;
1857 b += db;
1858 }
1859}
1860void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1861 SkScalar fy, SkScalar dy,
1862 SkScalar b, SkScalar db,
1863 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001864 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001865 int count) {
1866 for (; count > 0; --count) {
1867 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1868 fOneOverTwoA, posRoot);
1869 SkFixed index = mirror_tileproc(t);
1870 SkASSERT(index <= 0xFFFF);
1871 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1872 fx += dx;
1873 fy += dy;
1874 b += db;
1875 }
1876}
1877
1878void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1879 SkScalar fy, SkScalar dy,
1880 SkScalar b, SkScalar db,
1881 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001882 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001883 int count) {
1884 for (; count > 0; --count) {
1885 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1886 fOneOverTwoA, posRoot);
1887 SkFixed index = repeat_tileproc(t);
1888 SkASSERT(index <= 0xFFFF);
1889 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1890 fx += dx;
1891 fy += dy;
1892 b += db;
1893 }
1894}
1895
1896
1897
1898}
1899
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001900class Two_Point_Radial_Gradient : public Gradient_Shader {
1901public:
1902 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1903 const SkPoint& end, SkScalar endRadius,
1904 const SkColor colors[], const SkScalar pos[],
1905 int colorCount, SkShader::TileMode mode,
1906 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001907 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1908 fCenter1(start),
1909 fCenter2(end),
1910 fRadius1(startRadius),
1911 fRadius2(endRadius) {
1912 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001913 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001914
1915 virtual BitmapType asABitmap(SkBitmap* bitmap,
1916 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001917 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001918 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001919 if (bitmap) {
1920 this->commonAsABitmap(bitmap);
1921 }
1922 SkScalar diffL = 0; // just to avoid gcc warning
1923 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001924 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001925 SkScalarSquare(fDiff.fY));
1926 }
1927 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001928 if (diffL) {
1929 SkScalar invDiffL = SkScalarInvert(diffL);
1930 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1931 SkScalarMul(invDiffL, fDiff.fX));
1932 } else {
1933 matrix->reset();
1934 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001935 matrix->preConcat(fPtsToUnit);
1936 }
1937 if (xy) {
1938 xy[0] = fTileMode;
1939 xy[1] = kClamp_TileMode;
1940 }
1941 if (NULL != twoPointRadialParams) {
1942 twoPointRadialParams[0] = diffL;
1943 twoPointRadialParams[1] = fStartRadius;
1944 twoPointRadialParams[2] = fDiffRadius;
1945 }
1946 return kTwoPointRadial_BitmapType;
1947 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001948
reed@google.com8e6d9142011-12-07 15:30:34 +00001949 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001950 if (info) {
1951 commonAsAGradient(info);
1952 info->fPoint[0] = fCenter1;
1953 info->fPoint[1] = fCenter2;
1954 info->fRadius[0] = fRadius1;
1955 info->fRadius[1] = fRadius2;
1956 }
1957 return kRadial2_GradientType;
1958 }
1959
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001960 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1961 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001962 SkASSERT(count > 0);
1963
1964 // Zero difference between radii: fill with transparent black.
1965 if (fDiffRadius == 0) {
1966 sk_bzero(dstC, count * sizeof(*dstC));
1967 return;
1968 }
1969 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1970 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001971 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001972
1973 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001974 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001975 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001976 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001977 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1978 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001979 SkScalar dx, fx = srcPt.fX;
1980 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001981
reed@google.com61eb0402011-04-15 12:11:12 +00001982 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001983 SkFixed fixedX, fixedY;
1984 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1985 dx = SkFixedToScalar(fixedX);
1986 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001987 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001988 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001989 dx = fDstToIndex.getScaleX();
1990 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001991 }
reed@google.com84e9c082011-04-13 17:44:24 +00001992 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1993 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1994 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1995 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001996
1997 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00001998 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001999 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00002000 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002001 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00002002 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002003 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002004 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002005 (*shadeProc)(fx, dx, fy, dy, b, db,
2006 fSr2D2, foura, fOneOverTwoA, posRoot,
2007 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00002008 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00002009 SkScalar dstX = SkIntToScalar(x);
2010 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002011 for (; count > 0; --count) {
2012 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00002013 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00002014 SkScalar fx = srcPt.fX;
2015 SkScalar fy = srcPt.fY;
2016 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
2017 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002018 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
2019 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002020 SkFixed index = proc(t);
2021 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002022 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00002023 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002024 }
2025 }
2026 }
2027
reed@android.com6c59a172009-09-22 20:24:05 +00002028 virtual bool setContext(const SkBitmap& device,
2029 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00002030 const SkMatrix& matrix) SK_OVERRIDE {
2031 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00002032 return false;
2033 }
2034
2035 // we don't have a span16 proc
2036 fFlags &= ~kHasSpan16_Flag;
2037 return true;
2038 }
2039
reed@google.com8e6d9142011-12-07 15:30:34 +00002040 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002041 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
2042 }
2043
reed@google.com7716afb2011-12-07 15:17:50 +00002044 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00002045 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002046 buffer.writeScalar(fCenter1.fX);
2047 buffer.writeScalar(fCenter1.fY);
2048 buffer.writeScalar(fCenter2.fX);
2049 buffer.writeScalar(fCenter2.fY);
2050 buffer.writeScalar(fRadius1);
2051 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002052 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002053
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002054protected:
reed@android.combcfc7332009-11-10 16:19:39 +00002055 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002056 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002057 fCenter1(unflatten_point(buffer)),
2058 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002059 fRadius1(buffer.readScalar()),
2060 fRadius2(buffer.readScalar()) {
2061 init();
reed@android.combcfc7332009-11-10 16:19:39 +00002062 };
reed@google.com7716afb2011-12-07 15:17:50 +00002063 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002064
2065private:
2066 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002067 const SkPoint fCenter1;
2068 const SkPoint fCenter2;
2069 const SkScalar fRadius1;
2070 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002071 SkPoint fDiff;
2072 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002073
2074 void init() {
2075 fDiff = fCenter1 - fCenter2;
2076 fDiffRadius = fRadius2 - fRadius1;
2077 SkScalar inv = SkScalarInvert(fDiffRadius);
2078 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2079 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2080 fStartRadius = SkScalarMul(fRadius1, inv);
2081 fSr2D2 = SkScalarSquare(fStartRadius);
2082 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002083 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002084
2085 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2086 fPtsToUnit.postScale(inv, inv);
2087 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002088};
2089
reed@android.com8a1c16f2008-12-17 15:59:43 +00002090///////////////////////////////////////////////////////////////////////////////
2091
2092class Sweep_Gradient : public Gradient_Shader {
2093public:
2094 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2095 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002096 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2097 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002098 {
2099 fPtsToUnit.setTranslate(-cx, -cy);
2100 }
reed@google.com7716afb2011-12-07 15:17:50 +00002101 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2102 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002103
2104 virtual BitmapType asABitmap(SkBitmap* bitmap,
2105 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002106 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002107 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002108 if (bitmap) {
2109 this->commonAsABitmap(bitmap);
2110 }
2111 if (matrix) {
2112 *matrix = fPtsToUnit;
2113 }
2114 if (xy) {
2115 xy[0] = fTileMode;
2116 xy[1] = kClamp_TileMode;
2117 }
2118 return kSweep_BitmapType;
2119 }
2120
reed@google.com7716afb2011-12-07 15:17:50 +00002121 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002122 if (info) {
2123 commonAsAGradient(info);
2124 info->fPoint[0] = fCenter;
2125 }
2126 return kSweep_GradientType;
2127 }
2128
reed@google.com8e6d9142011-12-07 15:30:34 +00002129 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130 return SkNEW_ARGS(Sweep_Gradient, (buffer));
2131 }
2132
reed@google.com7716afb2011-12-07 15:17:50 +00002133 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002134 this->INHERITED::flatten(buffer);
2135 buffer.writeScalar(fCenter.fX);
2136 buffer.writeScalar(fCenter.fY);
2137 }
2138
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002140 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
2141 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002142 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002143 }
2144
reed@google.com7716afb2011-12-07 15:17:50 +00002145 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146
2147private:
2148 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002149 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150};
2151
2152#ifdef COMPUTE_SWEEP_TABLE
2153#define PI 3.14159265
2154static bool gSweepTableReady;
2155static uint8_t gSweepTable[65];
2156
2157/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2158 We scale the results to [0..32]
2159*/
reed@google.com61eb0402011-04-15 12:11:12 +00002160static const uint8_t* build_sweep_table() {
2161 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002162 const int N = 65;
2163 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002164
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165 for (int i = 0; i < N; i++)
2166 {
2167 double arg = i / DENOM;
2168 double v = atan(arg);
2169 int iv = (int)round(v * DENOM * 2 / PI);
2170// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2171 printf("%d, ", iv);
2172 gSweepTable[i] = iv;
2173 }
2174 gSweepTableReady = true;
2175 }
2176 return gSweepTable;
2177}
2178#else
2179static const uint8_t gSweepTable[] = {
2180 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2181 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2182 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2183 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2184 32
2185};
2186static const uint8_t* build_sweep_table() { return gSweepTable; }
2187#endif
2188
2189// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2190// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2191// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2192
2193//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002194static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195 SkASSERT(numer <= denom);
2196 SkASSERT(numer > 0);
2197 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002198
reed@android.com8a1c16f2008-12-17 15:59:43 +00002199 int nbits = SkCLZ(numer);
2200 int dbits = SkCLZ(denom);
2201 int bits = 6 - nbits + dbits;
2202 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002203
reed@google.com61eb0402011-04-15 12:11:12 +00002204 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002205 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002206 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002207
2208 denom <<= dbits - 1;
2209 numer <<= nbits - 1;
2210
2211 unsigned result = 0;
2212
2213 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002214 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002216 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002217 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002218 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002219
reed@android.com8a1c16f2008-12-17 15:59:43 +00002220 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002221 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002222 // make room for the rest of the answer bits
2223 result <<= bits;
2224 switch (bits) {
2225 case 6:
2226 if ((numer = (numer << 1) - denom) >= 0)
2227 result |= 32;
2228 else
2229 numer += denom;
2230 case 5:
2231 if ((numer = (numer << 1) - denom) >= 0)
2232 result |= 16;
2233 else
2234 numer += denom;
2235 case 4:
2236 if ((numer = (numer << 1) - denom) >= 0)
2237 result |= 8;
2238 else
2239 numer += denom;
2240 case 3:
2241 if ((numer = (numer << 1) - denom) >= 0)
2242 result |= 4;
2243 else
2244 numer += denom;
2245 case 2:
2246 if ((numer = (numer << 1) - denom) >= 0)
2247 result |= 2;
2248 else
2249 numer += denom;
2250 case 1:
2251 default: // not strictly need, but makes GCC make better ARM code
2252 if ((numer = (numer << 1) - denom) >= 0)
2253 result |= 1;
2254 else
2255 numer += denom;
2256 }
2257 }
2258 return result;
2259}
2260
2261// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002262static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002263#ifdef SK_DEBUG
2264 {
2265 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002266 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002267 gOnce = true;
2268 SkASSERT(div_64(55, 55) == 64);
2269 SkASSERT(div_64(128, 256) == 32);
2270 SkASSERT(div_64(2326528, 4685824) == 31);
2271 SkASSERT(div_64(753664, 5210112) == 9);
2272 SkASSERT(div_64(229376, 4882432) == 3);
2273 SkASSERT(div_64(2, 64) == 2);
2274 SkASSERT(div_64(1, 64) == 1);
2275 // test that we handle underflow correctly
2276 SkASSERT(div_64(12345, 0x54321234) == 0);
2277 }
2278 }
2279#endif
2280
2281 SkASSERT(y > 0 && x > 0);
2282 const uint8_t* table = build_sweep_table();
2283
2284 unsigned result;
2285 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002286 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002287 // first part of the atan(v) = PI/2 - atan(1/v) identity
2288 // since our div_64 and table want v <= 1, where v = y/x
2289 SkTSwap<SkFixed>(x, y);
2290 }
2291
2292 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002293
reed@android.com8a1c16f2008-12-17 15:59:43 +00002294#ifdef SK_DEBUG
2295 {
2296 unsigned result2 = SkDivBits(y, x, 6);
2297 SkASSERT(result2 == result ||
2298 (result == 1 && result2 == 0));
2299 }
2300#endif
2301
2302 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2303 result = table[result];
2304
reed@google.com61eb0402011-04-15 12:11:12 +00002305 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002306 // complete the atan(v) = PI/2 - atan(1/v) identity
2307 result = 64 - result;
2308 // pin to 63
2309 result -= result >> 6;
2310 }
2311
2312 SkASSERT(result <= 63);
2313 return result;
2314}
2315
2316// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002317#ifdef SK_SCALAR_IS_FLOAT
2318static unsigned SkATan2_255(float y, float x) {
2319 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2320 static const float g255Over2PI = 40.584510488433314f;
2321
2322 float result = sk_float_atan2(y, x);
2323 if (result < 0) {
2324 result += 2 * SK_ScalarPI;
2325 }
2326 SkASSERT(result >= 0);
2327 // since our value is always >= 0, we can cast to int, which is faster than
2328 // calling floorf()
2329 int ir = (int)(result * g255Over2PI);
2330 SkASSERT(ir >= 0 && ir <= 255);
2331 return ir;
2332}
2333#else
reed@google.com61eb0402011-04-15 12:11:12 +00002334static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2335 if (x == 0) {
2336 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002337 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002338 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002339 return y < 0 ? 192 : 64;
2340 }
reed@google.com61eb0402011-04-15 12:11:12 +00002341 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002342 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002343 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002344
reed@android.com8a1c16f2008-12-17 15:59:43 +00002345 /* Find the right quadrant for x,y
2346 Since atan_0_90 only handles the first quadrant, we rotate x,y
2347 appropriately before calling it, and then add the right amount
2348 to account for the real quadrant.
2349 quadrant 0 : add 0 | x > 0 && y > 0
2350 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2351 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2352 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002353
reed@android.com8a1c16f2008-12-17 15:59:43 +00002354 map x<0 to (1 << 6)
2355 map y<0 to (3 << 6)
2356 add = map_x ^ map_y
2357 */
2358 int xsign = x >> 31;
2359 int ysign = y >> 31;
2360 int add = ((-xsign) ^ (ysign & 3)) << 6;
2361
2362#ifdef SK_DEBUG
2363 if (0 == add)
2364 SkASSERT(x > 0 && y > 0);
2365 else if (64 == add)
2366 SkASSERT(x < 0 && y > 0);
2367 else if (128 == add)
2368 SkASSERT(x < 0 && y < 0);
2369 else if (192 == add)
2370 SkASSERT(x > 0 && y < 0);
2371 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002372 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002373#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002374
reed@android.com8a1c16f2008-12-17 15:59:43 +00002375 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2376 where we need to rotate x,y by 90 or -90
2377 */
2378 x = (x ^ xsign) - xsign;
2379 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002380 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002381 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002382 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002383
2384 unsigned result = add + atan_0_90(y, x);
2385 SkASSERT(result < 256);
2386 return result;
2387}
reed@google.com51baf5a2011-09-21 13:38:36 +00002388#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002389
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002390void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2391 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002392 SkMatrix::MapXYProc proc = fDstToIndexProc;
2393 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002394 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002395 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002396
reed@google.com61eb0402011-04-15 12:11:12 +00002397 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002398 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2399 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002400 SkScalar dx, fx = srcPt.fX;
2401 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002402
reed@google.com61eb0402011-04-15 12:11:12 +00002403 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002404 SkFixed storage[2];
2405 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2406 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002407 dx = SkFixedToScalar(storage[0]);
2408 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002409 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002410 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002411 dx = matrix.getScaleX();
2412 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002413 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002414
reed@google.com61eb0402011-04-15 12:11:12 +00002415 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002416 *dstC++ = cache[SkATan2_255(fy, fx)];
2417 fx += dx;
2418 fy += dy;
2419 }
reed@google.com61eb0402011-04-15 12:11:12 +00002420 } else { // perspective case
2421 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002422 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002423 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2424 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002425 }
2426 }
2427}
2428
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002429void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2430 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002431 SkMatrix::MapXYProc proc = fDstToIndexProc;
2432 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002433 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002434 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002435 SkPoint srcPt;
2436
reed@google.com61eb0402011-04-15 12:11:12 +00002437 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002438 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2439 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002440 SkScalar dx, fx = srcPt.fX;
2441 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002442
reed@google.com61eb0402011-04-15 12:11:12 +00002443 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002444 SkFixed storage[2];
2445 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2446 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002447 dx = SkFixedToScalar(storage[0]);
2448 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002449 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002450 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002451 dx = matrix.getScaleX();
2452 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002453 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002454
reed@google.com61eb0402011-04-15 12:11:12 +00002455 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002456 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2457 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002458 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002459 fx += dx;
2460 fy += dy;
2461 }
reed@google.com61eb0402011-04-15 12:11:12 +00002462 } else { // perspective case
2463 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002464 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2465 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002466
reed@google.com51baf5a2011-09-21 13:38:36 +00002467 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002468 index >>= (8 - kCache16Bits);
2469 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002470 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002471 }
2472 }
2473}
2474
reed@google.com61eb0402011-04-15 12:11:12 +00002475///////////////////////////////////////////////////////////////////////////////
2476///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002477
2478// assumes colors is SkColor* and pos is SkScalar*
2479#define EXPAND_1_COLOR(count) \
2480 SkColor tmp[2]; \
2481 do { \
2482 if (1 == count) { \
2483 tmp[0] = tmp[1] = colors[0]; \
2484 colors = tmp; \
2485 pos = NULL; \
2486 count = 2; \
2487 } \
2488 } while (0)
2489
reed@google.com61eb0402011-04-15 12:11:12 +00002490SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2491 const SkColor colors[],
2492 const SkScalar pos[], int colorCount,
2493 SkShader::TileMode mode,
2494 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002495 if (NULL == pts || NULL == colors || colorCount < 1) {
2496 return NULL;
2497 }
2498 EXPAND_1_COLOR(colorCount);
2499
reed@android.comab840b82009-07-01 17:00:03 +00002500 return SkNEW_ARGS(Linear_Gradient,
2501 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002502}
2503
reed@google.com61eb0402011-04-15 12:11:12 +00002504SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2505 const SkColor colors[],
2506 const SkScalar pos[], int colorCount,
2507 SkShader::TileMode mode,
2508 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002509 if (radius <= 0 || NULL == colors || colorCount < 1) {
2510 return NULL;
2511 }
2512 EXPAND_1_COLOR(colorCount);
2513
reed@android.comab840b82009-07-01 17:00:03 +00002514 return SkNEW_ARGS(Radial_Gradient,
2515 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002516}
2517
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002518SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2519 SkScalar startRadius,
2520 const SkPoint& end,
2521 SkScalar endRadius,
2522 const SkColor colors[],
2523 const SkScalar pos[],
2524 int colorCount,
2525 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002526 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002527 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2528 return NULL;
2529 }
2530 EXPAND_1_COLOR(colorCount);
2531
2532 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002533 (start, startRadius, end, endRadius, colors, pos,
2534 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002535}
2536
reed@android.com8a1c16f2008-12-17 15:59:43 +00002537SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2538 const SkColor colors[],
2539 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002540 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002541 if (NULL == colors || count < 1) {
2542 return NULL;
2543 }
2544 EXPAND_1_COLOR(count);
2545
2546 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2547}
2548
caryclark@google.comd26147a2011-12-15 14:16:43 +00002549SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2550 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2551 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002552
caryclark@google.comd26147a2011-12-15 14:16:43 +00002553 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002554
caryclark@google.comd26147a2011-12-15 14:16:43 +00002555 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2556SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END