blob: 2d99d3d6642b9c31a73c791530101176fcdf5ff0 [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"
11#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000012#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkUnitMapper.h"
14#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000015#include "SkTemplates.h"
16#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
reed@google.com51baf5a2011-09-21 13:38:36 +000018#if defined(SK_SCALAR_IS_FLOAT) && !defined(SK_DONT_USE_FLOAT_SQRT)
19 #define SK_USE_FLOAT_SQRT
20#endif
21
reed@google.com0e734bd2011-12-08 17:24:44 +000022#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
23 #define USE_DITHER_32BIT_GRADIENT
24#endif
25
reed@google.com5eb158d2011-04-15 15:50:34 +000026static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
27 int count) {
28 if (count > 0) {
29 if (v0 == v1) {
30 sk_memset32(dst, v0, count);
31 } else {
32 int pairs = count >> 1;
33 for (int i = 0; i < pairs; i++) {
34 *dst++ = v0;
35 *dst++ = v1;
36 }
37 if (count & 1) {
38 *dst = v0;
39 }
40 }
41 }
42}
43
reed@google.com61eb0402011-04-15 12:11:12 +000044///////////////////////////////////////////////////////////////////////////////
tomhudson@google.com9ce767c2011-04-25 20:49:39 +000045// Can't use a two-argument function with side effects like this in a
46// constructor's initializer's argument list because the order of
47// evaluations in that context is undefined (and backwards on linux/gcc).
48static SkPoint unflatten_point(SkReader32& buffer) {
49 SkPoint retval;
50 retval.fX = buffer.readScalar();
51 retval.fY = buffer.readScalar();
52 return retval;
53}
54
55///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000056
57typedef SkFixed (*TileProc)(SkFixed);
58
reed@android.com41bccf52009-04-03 13:33:51 +000059static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000060 return SkClampMax(x, 0xFFFF);
61}
62
reed@android.com41bccf52009-04-03 13:33:51 +000063static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 return x & 0xFFFF;
65}
66
reed@android.com41bccf52009-04-03 13:33:51 +000067static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 int s = x << 15 >> 31;
69 return (x ^ s) & 0xFFFF;
70}
71
72static const TileProc gTileProcs[] = {
73 clamp_tileproc,
74 repeat_tileproc,
75 mirror_tileproc
76};
77
reed@google.com61eb0402011-04-15 12:11:12 +000078///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000079
reed@android.com200645d2009-12-14 16:41:57 +000080static inline int repeat_bits(int x, const int bits) {
81 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082}
83
reed@android.com200645d2009-12-14 16:41:57 +000084static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000085#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000086 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000087 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000088 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000089#else
reed@android.com200645d2009-12-14 16:41:57 +000090 int s = x << (31 - bits) >> 31;
91 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000092#endif
93}
94
reed@android.com41bccf52009-04-03 13:33:51 +000095static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 return x & 0xFF;
97}
98
reed@android.com41bccf52009-04-03 13:33:51 +000099static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +0000101 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +0000103 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 return x & 255;
105#else
106 int s = x << 23 >> 31;
107 return (x ^ s) & 0xFF;
108#endif
109}
110
reed@google.com61eb0402011-04-15 12:11:12 +0000111///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112
113class Gradient_Shader : public SkShader {
114public:
115 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000116 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 virtual ~Gradient_Shader();
118
119 // overrides
reed@google.com7716afb2011-12-07 15:17:50 +0000120 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
121 virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000122 virtual bool isOpaque() const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000123
124protected:
125 Gradient_Shader(SkFlattenableReadBuffer& );
126 SkUnitMapper* fMapper;
127 SkMatrix fPtsToUnit; // set by subclass
128 SkMatrix fDstToIndex;
129 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130 TileMode fTileMode;
131 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000132 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133 uint8_t fDstToIndexClass;
134 uint8_t fFlags;
135
136 struct Rec {
137 SkFixed fPos; // 0...1
138 uint32_t fScale; // (1 << 24) / range
139 };
140 Rec* fRecs;
141
142 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000143 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000145 kCache16Mask = kCache16Count - 1,
146 kCache16Shift = 16 - kCache16Bits,
147
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148 kCache32Bits = 8, // pretty much should always be 8
149 kCache32Count = 1 << kCache32Bits
150 };
151 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000152 const uint16_t* getCache16() const;
153 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154
reed@google.com7c2f27d2011-03-07 19:29:00 +0000155 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000156 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000157
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158private:
159 enum {
160 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
161
reed@android.com1c12abe2009-07-02 15:01:02 +0000162 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163 };
164 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000165 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
166 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167
reed@google.com7c2f27d2011-03-07 19:29:00 +0000168 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
169 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170
reed@google.com7c2f27d2011-03-07 19:29:00 +0000171 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
172 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000173 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 +0000174
reed@android.com512a8762009-12-14 15:25:36 +0000175 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000176 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
177 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000178 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000179 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000180
reed@android.com8a1c16f2008-12-17 15:59:43 +0000181 typedef SkShader INHERITED;
182};
183
reed@android.com41bccf52009-04-03 13:33:51 +0000184static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185 SkASSERT(x >= 0 && x <= SK_Scalar1);
186
187#ifdef SK_SCALAR_IS_FLOAT
188 return (unsigned)(x * 0xFFFF);
189#else
190 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
191#endif
192}
193
reed@android.com41bccf52009-04-03 13:33:51 +0000194Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
195 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196 SkASSERT(colorCount > 1);
197
198 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
199
200 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000201 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
204 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
205 fTileMode = mode;
206 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000207
reed@android.com41bccf52009-04-03 13:33:51 +0000208 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000209 fCache32 = NULL;
210 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000211
reed@android.com41bccf52009-04-03 13:33:51 +0000212 /* Note: we let the caller skip the first and/or last position.
213 i.e. pos[0] = 0.3, pos[1] = 0.7
214 In these cases, we insert dummy entries to ensure that the final data
215 will be bracketed by [0, 1].
216 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
217
218 Thus colorCount (the caller's value, and fColorCount (our value) may
219 differ by up to 2. In the above example:
220 colorCount = 2
221 fColorCount = 4
222 */
223 fColorCount = colorCount;
224 // check if we need to add in dummy start and/or end position/colors
225 bool dummyFirst = false;
226 bool dummyLast = false;
227 if (pos) {
228 dummyFirst = pos[0] != 0;
229 dummyLast = pos[colorCount - 1] != SK_Scalar1;
230 fColorCount += dummyFirst + dummyLast;
231 }
232
233 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000234 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000235 fOrigColors = reinterpret_cast<SkColor*>(
236 sk_malloc_throw(size * fColorCount));
237 }
238 else {
239 fOrigColors = fStorage;
240 }
241
242 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 {
reed@android.com41bccf52009-04-03 13:33:51 +0000244 SkColor* origColors = fOrigColors;
245 if (dummyFirst) {
246 *origColors++ = colors[0];
247 }
248 memcpy(origColors, colors, colorCount * sizeof(SkColor));
249 if (dummyLast) {
250 origColors += colorCount;
251 *origColors = colors[colorCount - 1];
252 }
253 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000254
reed@android.com1c12abe2009-07-02 15:01:02 +0000255 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000256 if (fColorCount > 2) {
257 Rec* recs = fRecs;
258 recs->fPos = 0;
259 // recs->fScale = 0; // unused;
260 recs += 1;
261 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000262 /* We need to convert the user's array of relative positions into
263 fixed-point positions and scale factors. We need these results
264 to be strictly monotonic (no two values equal or out of order).
265 Hence this complex loop that just jams a zero for the scale
266 value if it sees a segment out of order, and it assures that
267 we start at 0 and end at 1.0
268 */
269 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000270 int startIndex = dummyFirst ? 0 : 1;
271 int count = colorCount + dummyLast;
272 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000273 // force the last value to be 1.0
274 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000275 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000276 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000277 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000278 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000279 }
reed@android.com41bccf52009-04-03 13:33:51 +0000280 // pin curr withing range
281 if (curr < 0) {
282 curr = 0;
283 } else if (curr > SK_Fixed1) {
284 curr = SK_Fixed1;
285 }
286 recs->fPos = curr;
287 if (curr > prev) {
288 recs->fScale = (1 << 24) / (curr - prev);
289 } else {
290 recs->fScale = 0; // ignore this segment
291 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000292 // get ready for the next value
293 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000294 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 }
reed@android.com41bccf52009-04-03 13:33:51 +0000296 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 SkFixed dp = SK_Fixed1 / (colorCount - 1);
298 SkFixed p = dp;
299 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000300 for (int i = 1; i < colorCount; i++) {
301 recs->fPos = p;
302 recs->fScale = scale;
303 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 p += dp;
305 }
306 }
307 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000308 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000309}
310
311Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000312 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313 fCacheAlpha = 256;
314
315 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
316
317 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000318 fCache32 = NULL;
319 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320
reed@android.com41bccf52009-04-03 13:33:51 +0000321 int colorCount = fColorCount = buffer.readU32();
322 if (colorCount > kColorStorageCount) {
323 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
324 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
325 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000327 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329
330 fTileMode = (TileMode)buffer.readU8();
331 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000332 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333 if (colorCount > 2) {
334 Rec* recs = fRecs;
335 recs[0].fPos = 0;
336 for (int i = 1; i < colorCount; i++) {
337 recs[i].fPos = buffer.readS32();
338 recs[i].fScale = buffer.readU32();
339 }
340 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000341 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000342 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343}
344
reed@android.com41bccf52009-04-03 13:33:51 +0000345Gradient_Shader::~Gradient_Shader() {
346 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000348 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000349 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000350 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000352 }
reed@google.com82065d62011-02-07 15:30:46 +0000353 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354}
355
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000356void Gradient_Shader::initCommon() {
357 fFlags = 0;
358 unsigned colorAlpha = 0xFF;
359 for (int i = 0; i < fColorCount; i++) {
360 colorAlpha &= SkColorGetA(fOrigColors[i]);
361 }
362 fColorsAreOpaque = colorAlpha == 0xFF;
363}
364
reed@android.com41bccf52009-04-03 13:33:51 +0000365void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366 this->INHERITED::flatten(buffer);
367 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000368 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
370 buffer.write8(fTileMode);
371 if (fColorCount > 2) {
372 Rec* recs = fRecs;
373 for (int i = 1; i < fColorCount; i++) {
374 buffer.write32(recs[i].fPos);
375 buffer.write32(recs[i].fScale);
376 }
377 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000378 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379}
380
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000381bool Gradient_Shader::isOpaque() const {
382 return fColorsAreOpaque;
383}
384
reed@android.com8a1c16f2008-12-17 15:59:43 +0000385bool Gradient_Shader::setContext(const SkBitmap& device,
386 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000387 const SkMatrix& matrix) {
388 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000390 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391
392 const SkMatrix& inverse = this->getTotalInverse();
393
394 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
395 return false;
396 }
397
398 fDstToIndexProc = fDstToIndex.getMapXYProc();
399 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
400
401 // now convert our colors in to PMColors
402 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000403
404 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000405 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406 fFlags |= kOpaqueAlpha_Flag;
407 }
408 // we can do span16 as long as our individual colors are opaque,
409 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000410 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411 fFlags |= kHasSpan16_Flag;
412 }
413
reed@google.com95eed982011-07-05 17:01:56 +0000414 this->setCacheAlpha(paintAlpha);
415 return true;
416}
417
418void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419 // if the new alpha differs from the previous time we were called, inval our cache
420 // this will trigger the cache to be rebuilt.
421 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000422 if (fCacheAlpha != alpha) {
423 fCache16 = NULL; // inval the cache
424 fCache32 = NULL; // inval the cache
425 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000426 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000427 if (fCache32PixelRef) {
428 fCache32PixelRef->notifyPixelsChanged();
429 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431}
432
reed@android.com41bccf52009-04-03 13:33:51 +0000433static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434 SkASSERT(a == SkToU8(a));
435 SkASSERT(b == SkToU8(b));
436 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000437 return a + ((b - a) * scale >> 8);
438}
439
reed@android.com41bccf52009-04-03 13:33:51 +0000440static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
441 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000442#if 0
443 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
444 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
445 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
446 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
447
448 return SkPackARGB32(a, r, g, b);
449#else
450 int otherBlend = 256 - blend;
451
452#if 0
453 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
454 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
455 SkASSERT((t0 & t1) == 0);
456 return t0 | t1;
457#else
458 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
459 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
460#endif
461
462#endif
463}
464
465#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
466
reed@android.com41bccf52009-04-03 13:33:51 +0000467/** We take the original colors, not our premultiplied PMColors, since we can
468 build a 16bit table as long as the original colors are opaque, even if the
469 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470*/
reed@android.com512a8762009-12-14 15:25:36 +0000471void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
472 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000473 SkASSERT(count > 1);
474 SkASSERT(SkColorGetA(c0) == 0xFF);
475 SkASSERT(SkColorGetA(c1) == 0xFF);
476
477 SkFixed r = SkColorGetR(c0);
478 SkFixed g = SkColorGetG(c0);
479 SkFixed b = SkColorGetB(c0);
480
481 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
482 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
483 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
484
485 r = SkIntToFixed(r) + 0x8000;
486 g = SkIntToFixed(g) + 0x8000;
487 b = SkIntToFixed(b) + 0x8000;
488
489 do {
490 unsigned rr = r >> 16;
491 unsigned gg = g >> 16;
492 unsigned bb = b >> 16;
493 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000494 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000495 cache += 1;
496 r += dr;
497 g += dg;
498 b += db;
499 } while (--count != 0);
500}
501
reed@google.com55b8e8c2011-01-13 16:22:35 +0000502/*
503 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
504 * semantics of how we 2x2 dither 32->16
505 */
506static inline U8CPU dither_fixed_to_8(SkFixed n) {
507 n >>= 8;
508 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
509}
510
511/*
512 * For dithering with premultiply, we want to ceiling the alpha component,
513 * to ensure that it is always >= any color component.
514 */
515static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
516 n >>= 8;
517 return ((n << 1) - (n | (n >> 8))) >> 8;
518}
519
520void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
521 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 SkASSERT(count > 1);
523
reed@android.com1c12abe2009-07-02 15:01:02 +0000524 // need to apply paintAlpha to our two endpoints
525 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
526 SkFixed da;
527 {
528 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
529 da = SkIntToFixed(tmp - a) / (count - 1);
530 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531
reed@android.com1c12abe2009-07-02 15:01:02 +0000532 SkFixed r = SkColorGetR(c0);
533 SkFixed g = SkColorGetG(c0);
534 SkFixed b = SkColorGetB(c0);
535 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
536 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
537 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000538
539 a = SkIntToFixed(a) + 0x8000;
540 r = SkIntToFixed(r) + 0x8000;
541 g = SkIntToFixed(g) + 0x8000;
542 b = SkIntToFixed(b) + 0x8000;
543
544 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000545 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
546 cache[kCache32Count] = SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
547 dither_fixed_to_8(r),
548 dither_fixed_to_8(g),
549 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000550 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000551 a += da;
552 r += dr;
553 g += dg;
554 b += db;
555 } while (--count != 0);
556}
557
reed@android.com41bccf52009-04-03 13:33:51 +0000558static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000559 SkASSERT((unsigned)x <= SK_Fixed1);
560 return x - (x >> 16);
561}
562
reed@android.com200645d2009-12-14 16:41:57 +0000563static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000564 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000565 if (6 == bits) {
566 return (x << 10) | (x << 4) | (x >> 2);
567 }
568 if (8 == bits) {
569 return (x << 8) | x;
570 }
571 sk_throw();
572 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573}
574
reed@google.com7c2f27d2011-03-07 19:29:00 +0000575const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000576 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000577 // double the count for dither entries
578 const int entryCount = kCache16Count * 2;
579 const size_t allocSize = sizeof(uint16_t) * entryCount;
580
reed@android.com3c9b2a42009-08-27 19:28:37 +0000581 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000582 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000583 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000585 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000586 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000587 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588 Rec* rec = fRecs;
589 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000590 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000591 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000592 SkASSERT(nextIndex < kCache16Count);
593
594 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000595 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 prevIndex = nextIndex;
597 }
598 SkASSERT(prevIndex == kCache16Count - 1);
599 }
600
reed@android.com41bccf52009-04-03 13:33:51 +0000601 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000602 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603 uint16_t* linear = fCache16; // just computed linear data
604 uint16_t* mapped = fCache16Storage; // storage for mapped data
605 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000606 for (int i = 0; i < kCache16Count; i++) {
607 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000608 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000609 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000610 }
611 sk_free(fCache16);
612 fCache16 = fCache16Storage;
613 }
614 }
615 return fCache16;
616}
617
reed@google.com7c2f27d2011-03-07 19:29:00 +0000618const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000619 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000620 // double the count for dither entries
621 const int entryCount = kCache32Count * 2;
622 const size_t allocSize = sizeof(SkPMColor) * entryCount;
623
reed@google.comdc731fd2010-12-23 15:19:47 +0000624 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000625 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
626 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000627 }
628 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000629 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000630 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
631 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000632 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 Rec* rec = fRecs;
634 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000635 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
637 SkASSERT(nextIndex < kCache32Count);
638
639 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000640 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
641 fOrigColors[i],
642 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643 prevIndex = nextIndex;
644 }
645 SkASSERT(prevIndex == kCache32Count - 1);
646 }
647
reed@android.com41bccf52009-04-03 13:33:51 +0000648 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000649 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000650 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000652 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000654 for (int i = 0; i < kCache32Count; i++) {
655 int index = map->mapUnit16((i << 8) | i) >> 8;
656 mapped[i] = linear[index];
657 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000658 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000659 fCache32PixelRef->unref();
660 fCache32PixelRef = newPR;
661 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 }
663 }
664 return fCache32;
665}
666
reed@google.comdc731fd2010-12-23 15:19:47 +0000667/*
668 * Because our caller might rebuild the same (logically the same) gradient
669 * over and over, we'd like to return exactly the same "bitmap" if possible,
670 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
671 * To do that, we maintain a private cache of built-bitmaps, based on our
672 * colors and positions. Note: we don't try to flatten the fMapper, so if one
673 * is present, we skip the cache for now.
674 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000675void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000676 // our caller assumes no external alpha, so we ensure that our cache is
677 // built with 0xFF
678 this->setCacheAlpha(0xFF);
679
reed@google.comdc731fd2010-12-23 15:19:47 +0000680 // don't have a way to put the mapper into our cache-key yet
681 if (fMapper) {
682 // force our cahce32pixelref to be built
683 (void)this->getCache32();
684 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
685 bitmap->setPixelRef(fCache32PixelRef);
686 return;
687 }
688
689 // build our key: [numColors + colors[] + {positions[]} ]
690 int count = 1 + fColorCount;
691 if (fColorCount > 2) {
692 count += fColorCount - 1; // fRecs[].fPos
693 }
694
695 SkAutoSTMalloc<16, int32_t> storage(count);
696 int32_t* buffer = storage.get();
697
698 *buffer++ = fColorCount;
699 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
700 buffer += fColorCount;
701 if (fColorCount > 2) {
702 for (int i = 1; i < fColorCount; i++) {
703 *buffer++ = fRecs[i].fPos;
704 }
705 }
706 SkASSERT(buffer - storage.get() == count);
707
708 ///////////////////////////////////
709
710 static SkMutex gMutex;
711 static SkBitmapCache* gCache;
712 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
713 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
714 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000715
reed@google.comdc731fd2010-12-23 15:19:47 +0000716 if (NULL == gCache) {
717 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
718 }
719 size_t size = count * sizeof(int32_t);
720
721 if (!gCache->find(storage.get(), size, bitmap)) {
722 // force our cahce32pixelref to be built
723 (void)this->getCache32();
724 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
725 bitmap->setPixelRef(fCache32PixelRef);
726
727 gCache->add(storage.get(), size, *bitmap);
728 }
729}
730
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000731void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
732 if (info) {
733 if (info->fColorCount >= fColorCount) {
734 if (info->fColors) {
735 memcpy(info->fColors, fOrigColors,
736 fColorCount * sizeof(SkColor));
737 }
738 if (info->fColorOffsets) {
739 if (fColorCount == 2) {
740 info->fColorOffsets[0] = 0;
741 info->fColorOffsets[1] = SK_Scalar1;
742 } else if (fColorCount > 2) {
743 for (int i = 0; i < fColorCount; i++)
744 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
745 }
746 }
747 }
748 info->fColorCount = fColorCount;
749 info->fTileMode = fTileMode;
750 }
751}
752
reed@google.com61eb0402011-04-15 12:11:12 +0000753///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000754
reed@android.com41bccf52009-04-03 13:33:51 +0000755static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000756 SkVector vec = pts[1] - pts[0];
757 SkScalar mag = vec.length();
758 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
759
760 vec.scale(inv);
761 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
762 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
763 matrix->postScale(inv, inv);
764}
765
766///////////////////////////////////////////////////////////////////////////////
767
768class Linear_Gradient : public Gradient_Shader {
769public:
770 Linear_Gradient(const SkPoint pts[2],
771 const SkColor colors[], const SkScalar pos[], int colorCount,
772 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000773 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
774 fStart(pts[0]),
775 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 {
777 pts_to_unit_matrix(pts, &fPtsToUnit);
778 }
reed@android.com9b46e772009-06-05 12:24:41 +0000779
reed@google.com7716afb2011-12-07 15:17:50 +0000780 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
781 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
782 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
783 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
784 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
785 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786
reed@google.com55b8e8c2011-01-13 16:22:35 +0000787 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788 return SkNEW_ARGS(Linear_Gradient, (buffer));
789 }
790
reed@google.com7716afb2011-12-07 15:17:50 +0000791 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000792 this->INHERITED::flatten(buffer);
793 buffer.writeScalar(fStart.fX);
794 buffer.writeScalar(fStart.fY);
795 buffer.writeScalar(fEnd.fX);
796 buffer.writeScalar(fEnd.fY);
797 }
798
caryclark@google.comd26147a2011-12-15 14:16:43 +0000799 SK_DECLARE_FLATTENABLE_REGISTRAR()
800
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000802 Linear_Gradient(SkFlattenableReadBuffer& buffer)
803 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000804 fStart(unflatten_point(buffer)),
805 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000806 }
reed@google.com7716afb2011-12-07 15:17:50 +0000807 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808
809private:
810 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000811 const SkPoint fStart;
812 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813};
814
reed@android.com5119bdb2009-06-12 21:27:03 +0000815bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
816 const SkMatrix& matrix) {
817 if (!this->INHERITED::setContext(device, paint, matrix)) {
818 return false;
819 }
820
821 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
822 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000823 fFlags |= SkShader::kConstInY32_Flag;
824 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
825 // only claim this if we do have a 16bit mode (i.e. none of our
826 // colors have alpha), and if we are not dithering (which obviously
827 // is not const in Y).
828 fFlags |= SkShader::kConstInY16_Flag;
829 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000830 }
831 return true;
832}
833
reed@android.com8a1c16f2008-12-17 15:59:43 +0000834// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@google.com61eb0402011-04-15 12:11:12 +0000835static inline bool no_need_for_clamp(int fx, int dx, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836 SkASSERT(count > 0);
837 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
838}
839
reed@google.com5eb158d2011-04-15 15:50:34 +0000840#include "SkClampRange.h"
841
842#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000843 do { \
844 unsigned fi = fx >> 8; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000845 SkASSERT(fi <= 0xFF); \
846 fx += dx; \
847 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000848 toggle ^= TOGGLE_MASK; \
849 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000850
851
reed@google.comdd5bd672011-09-20 15:56:13 +0000852void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 SkASSERT(count > 0);
854
855 SkPoint srcPt;
856 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
857 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +0000858 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +0000859#ifdef USE_DITHER_32BIT_GRADIENT
reed@google.com55b8e8c2011-01-13 16:22:35 +0000860 int toggle = ((x ^ y) & 1) << kCache32Bits;
861 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@google.com0e734bd2011-12-08 17:24:44 +0000862#else
863 int toggle = 0;
864 const int TOGGLE_MASK = 0;
865#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000866
reed@android.comc552a432009-06-12 20:02:50 +0000867 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000868 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
869 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
871
reed@android.comc552a432009-06-12 20:02:50 +0000872 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873 SkFixed dxStorage[1];
874 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
875 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000876 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
878 dx = SkScalarToFixed(fDstToIndex.getScaleX());
879 }
880
reed@android.comc552a432009-06-12 20:02:50 +0000881 if (SkFixedNearlyZero(dx)) {
882 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000883 unsigned fi = proc(fx);
884 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000885 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000886 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000887 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000888 SkClampRange range;
889 range.init(fx, dx, count, 0, 0xFF);
890
891 if ((count = range.fCount0) > 0) {
892 sk_memset32_dither(dstC,
893 cache[toggle + range.fV0],
894 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
895 count);
896 dstC += count;
897 }
898 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +0000899 int unroll = count >> 3;
900 fx = range.fFx1;
901 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000902 NO_CHECK_ITER; NO_CHECK_ITER;
903 NO_CHECK_ITER; NO_CHECK_ITER;
904 NO_CHECK_ITER; NO_CHECK_ITER;
905 NO_CHECK_ITER; NO_CHECK_ITER;
906 }
907 if ((count &= 7) > 0) {
908 do {
909 NO_CHECK_ITER;
910 } while (--count != 0);
911 }
912 }
913 if ((count = range.fCount2) > 0) {
914 sk_memset32_dither(dstC,
915 cache[toggle + range.fV1],
916 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
917 count);
918 }
reed@android.comc552a432009-06-12 20:02:50 +0000919 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000920 do {
921 unsigned fi = mirror_8bits(fx >> 8);
922 SkASSERT(fi <= 0xFF);
923 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000924 *dstC++ = cache[toggle + fi];
925 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000926 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000927 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000928 SkASSERT(proc == repeat_tileproc);
929 do {
930 unsigned fi = repeat_8bits(fx >> 8);
931 SkASSERT(fi <= 0xFF);
932 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000933 *dstC++ = cache[toggle + fi];
934 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000935 } while (--count != 0);
936 }
reed@android.comc552a432009-06-12 20:02:50 +0000937 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938 SkScalar dstX = SkIntToScalar(x);
939 SkScalar dstY = SkIntToScalar(y);
940 do {
941 dstProc(fDstToIndex, dstX, dstY, &srcPt);
942 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
943 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000944 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
945 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946 dstX += SK_Scalar1;
947 } while (--count != 0);
948 }
949}
950
reed@google.com55b8e8c2011-01-13 16:22:35 +0000951SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000952 SkMatrix* matrix,
953 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000954 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000956 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000957 }
958 if (matrix) {
959 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
960 matrix->preConcat(fPtsToUnit);
961 }
962 if (xy) {
963 xy[0] = fTileMode;
964 xy[1] = kClamp_TileMode;
965 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000966 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967}
968
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000969SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
970 if (info) {
971 commonAsAGradient(info);
972 info->fPoint[0] = fStart;
973 info->fPoint[1] = fEnd;
974 }
975 return kLinear_GradientType;
976}
977
reed@android.com3c9b2a42009-08-27 19:28:37 +0000978static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
979 int count) {
980 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000981 *dst++ = value;
982 count -= 1;
983 SkTSwap(value, other);
984 }
985
986 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000987
reed@android.com3c9b2a42009-08-27 19:28:37 +0000988 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000990 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992
reed@google.com5eb158d2011-04-15 15:50:34 +0000993#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +0000994 do { \
995 unsigned fi = fx >> kCache16Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000996 SkASSERT(fi <= kCache16Mask); \
997 fx += dx; \
998 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000999 toggle ^= TOGGLE_MASK; \
1000 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001001
1002
reed@google.comdd5bd672011-09-20 15:56:13 +00001003void Linear_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 SkASSERT(count > 0);
1005
1006 SkPoint srcPt;
1007 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1008 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001009 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@google.com5eb158d2011-04-15 15:50:34 +00001011 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001013 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001014 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1015 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001016 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1017
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001018 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019 SkFixed dxStorage[1];
1020 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1021 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001022 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1024 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1025 }
1026
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001027 if (SkFixedNearlyZero(dx)) {
1028 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +00001029 unsigned fi = proc(fx) >> kCache16Shift;
1030 SkASSERT(fi <= kCache16Mask);
reed@google.com5eb158d2011-04-15 15:50:34 +00001031 dither_memset16(dstC, cache[toggle + fi],
1032 cache[(toggle ^ TOGGLE_MASK) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001033 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001034 SkClampRange range;
1035 range.init(fx, dx, count, 0, kCache16Mask);
1036
1037 if ((count = range.fCount0) > 0) {
1038 dither_memset16(dstC,
1039 cache[toggle + range.fV0],
1040 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
1041 count);
1042 dstC += count;
1043 }
1044 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +00001045 int unroll = count >> 3;
1046 fx = range.fFx1;
1047 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001048 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1049 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1050 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1051 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1052 }
1053 if ((count &= 7) > 0) {
1054 do {
1055 NO_CHECK_ITER_16;
1056 } while (--count != 0);
1057 }
1058 }
1059 if ((count = range.fCount2) > 0) {
1060 dither_memset16(dstC,
1061 cache[toggle + range.fV1],
1062 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
1063 count);
1064 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001065 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001066 do {
reed@android.com200645d2009-12-14 16:41:57 +00001067 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001068 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001071 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001073 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074 SkASSERT(proc == repeat_tileproc);
1075 do {
reed@android.com200645d2009-12-14 16:41:57 +00001076 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001077 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001078 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001079 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001080 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001081 } while (--count != 0);
1082 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001083 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001084 SkScalar dstX = SkIntToScalar(x);
1085 SkScalar dstY = SkIntToScalar(y);
1086 do {
1087 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1088 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1089 SkASSERT(fi <= 0xFFFF);
1090
reed@android.com512a8762009-12-14 15:25:36 +00001091 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001092 *dstC++ = cache[toggle + index];
reed@google.com5eb158d2011-04-15 15:50:34 +00001093 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001094
1095 dstX += SK_Scalar1;
1096 } while (--count != 0);
1097 }
1098}
1099
1100///////////////////////////////////////////////////////////////////////////////
1101
1102#define kSQRT_TABLE_BITS 11
1103#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1104
1105#include "SkRadialGradient_Table.h"
1106
1107#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1108
1109#include <stdio.h>
1110
reed@google.com61eb0402011-04-15 12:11:12 +00001111void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001112 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1113
1114 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1115 SkASSERT(file);
1116 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1117
reed@google.com61eb0402011-04-15 12:11:12 +00001118 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1119 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001121 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122
1123 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1124
1125 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001126 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001128 }
1129 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001130 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001131 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001132 }
1133 ::fprintf(file, "};\n");
1134 ::fclose(file);
1135}
1136
1137#endif
1138
1139
reed@google.com61eb0402011-04-15 12:11:12 +00001140static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1141 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001142 SkScalar inv = SkScalarInvert(radius);
1143
1144 matrix->setTranslate(-center.fX, -center.fY);
1145 matrix->postScale(inv, inv);
1146}
1147
1148class Radial_Gradient : public Gradient_Shader {
1149public:
1150 Radial_Gradient(const SkPoint& center, SkScalar radius,
1151 const SkColor colors[], const SkScalar pos[], int colorCount,
1152 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001153 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1154 fCenter(center),
1155 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001156 {
1157 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1158 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1159
1160 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1161 }
reed@google.com61eb0402011-04-15 12:11:12 +00001162
reed@google.com8e6d9142011-12-07 15:30:34 +00001163 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count) SK_OVERRIDE;
reed@google.com7716afb2011-12-07 15:17:50 +00001164 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001165 SkASSERT(count > 0);
1166
1167 SkPoint srcPt;
1168 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1169 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001170 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001171 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172
reed@android.com3c9b2a42009-08-27 19:28:37 +00001173 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001174 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1175 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1177 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1178
reed@android.com3c9b2a42009-08-27 19:28:37 +00001179 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001180 SkFixed storage[2];
1181 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1182 dx = storage[0];
1183 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001184 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001185 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1186 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1187 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1188 }
1189
reed@android.com3c9b2a42009-08-27 19:28:37 +00001190 if (proc == clamp_tileproc) {
reed@google.comdd5bd672011-09-20 15:56:13 +00001191 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001192
1193 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1194 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1195 precision, but that appears to be visually OK. If we decide this is OK for
1196 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1197 to avoid having to do these extra shifts each time.
1198 */
1199 fx >>= 1;
1200 dx >>= 1;
1201 fy >>= 1;
1202 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001203 if (dy == 0) { // might perform this check for the other modes, but the win will be a smaller % of the total
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1205 fy *= fy;
1206 do {
1207 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1208 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1209 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1210 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1212 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001214 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215 do {
1216 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1217 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1218 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1219 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1220 fx += dx;
1221 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1223 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224 } while (--count != 0);
1225 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001226 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227 do {
1228 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1229 unsigned fi = mirror_tileproc(dist);
1230 SkASSERT(fi <= 0xFFFF);
1231 fx += dx;
1232 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1234 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001236 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 SkASSERT(proc == repeat_tileproc);
1238 do {
1239 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1240 unsigned fi = repeat_tileproc(dist);
1241 SkASSERT(fi <= 0xFFFF);
1242 fx += dx;
1243 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1245 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246 } while (--count != 0);
1247 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001248 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkScalar dstX = SkIntToScalar(x);
1250 SkScalar dstY = SkIntToScalar(y);
1251 do {
1252 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1253 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1254 SkASSERT(fi <= 0xFFFF);
1255
1256 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257 *dstC++ = cache[toggle + index];
1258 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259
1260 dstX += SK_Scalar1;
1261 } while (--count != 0);
1262 }
1263 }
1264
reed@google.com55b8e8c2011-01-13 16:22:35 +00001265 virtual BitmapType asABitmap(SkBitmap* bitmap,
1266 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001267 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00001268 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001269 if (bitmap) {
1270 this->commonAsABitmap(bitmap);
1271 }
1272 if (matrix) {
1273 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1274 matrix->preConcat(fPtsToUnit);
1275 }
1276 if (xy) {
1277 xy[0] = fTileMode;
1278 xy[1] = kClamp_TileMode;
1279 }
1280 return kRadial_BitmapType;
1281 }
reed@google.com7716afb2011-12-07 15:17:50 +00001282 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001283 if (info) {
1284 commonAsAGradient(info);
1285 info->fPoint[0] = fCenter;
1286 info->fRadius[0] = fRadius;
1287 }
1288 return kRadial_GradientType;
1289 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001290
reed@google.com8e6d9142011-12-07 15:30:34 +00001291 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 return SkNEW_ARGS(Radial_Gradient, (buffer));
1293 }
1294
reed@google.com7716afb2011-12-07 15:17:50 +00001295 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001296 this->INHERITED::flatten(buffer);
1297 buffer.writeScalar(fCenter.fX);
1298 buffer.writeScalar(fCenter.fY);
1299 buffer.writeScalar(fRadius);
1300 }
1301
reed@android.com8a1c16f2008-12-17 15:59:43 +00001302protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001303 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1304 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001305 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001306 fRadius(buffer.readScalar()) {
1307 }
reed@google.com7716afb2011-12-07 15:17:50 +00001308 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001309
1310private:
1311 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001312 const SkPoint fCenter;
1313 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314};
1315
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001316static inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
1317 // fast, overly-conservative test: checks unit square instead
1318 // of unit circle
1319 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1320 (fx <= -SK_FixedHalf && dx <= 0);
1321 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1322 (fy <= -SK_FixedHalf && dy <= 0);
1323
1324 return xClamped || yClamped;
1325}
1326
1327// Return true if (fx * fy) is always inside the unit circle
1328// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1329// so it shouldn't be run if count is small.
1330static inline bool no_need_for_radial_pin(int fx, int dx,
1331 int fy, int dy, int count) {
1332 SkASSERT(count > 0);
1333 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1334 return false;
1335 }
1336 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1337 return false;
1338 }
1339 fx += (count - 1) * dx;
1340 fy += (count - 1) * dy;
1341 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1342 return false;
1343 }
1344 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1345}
1346
1347#define UNPINNED_RADIAL_STEP \
1348 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
1349 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)]; \
1350 fx += dx; \
1351 fy += dy;
1352
1353// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
1354static void radial_clamp(SkFixed fx, SkFixed fy, SkFixed dx, SkFixed dy,
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001355 SkPMColor* SK_RESTRICT dstC, int count,
1356 const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001357 const int kCache32Bits, const int kCache32Count) {
1358 // Floating point seems to be slower than fixed point,
1359 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001360 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001361 fx >>= 1;
1362 dx >>= 1;
1363 fy >>= 1;
1364 dy >>= 1;
1365 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
1366 sk_memset32(dstC, cache[kCache32Count - 1], count);
1367 } else if ((count > 4) &&
1368 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1369 unsigned fi;
1370 // 4x unroll appears to be no faster than 2x unroll on Linux
1371 while (count > 1) {
1372 UNPINNED_RADIAL_STEP;
1373 UNPINNED_RADIAL_STEP;
1374 count -= 2;
1375 }
1376 if (count) {
1377 UNPINNED_RADIAL_STEP;
1378 }
1379 }
1380 else {
1381 do {
1382 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1383 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1384 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1385 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1386 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1387 fx += dx;
1388 fy += dy;
1389 } while (--count != 0);
1390 }
1391}
1392
1393void Radial_Gradient::shadeSpan(int x, int y,
1394 SkPMColor* SK_RESTRICT dstC, int count) {
1395 SkASSERT(count > 0);
1396
1397 SkPoint srcPt;
1398 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1399 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001400 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001401
1402 if (fDstToIndexClass != kPerspective_MatrixClass) {
1403 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1404 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1405 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1406 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1407#ifdef SK_USE_FLOAT_SQRT
1408 float fdx, fdy;
1409#endif
1410
1411 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1412 SkFixed storage[2];
1413 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1414 dx = storage[0];
1415 dy = storage[1];
1416#ifdef SK_USE_FLOAT_SQRT
1417 fdx = SkFixedToFloat(storage[0]);
1418 fdy = SkFixedToFloat(storage[1]);
1419#endif
1420 } else {
1421 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1422 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1423 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1424#ifdef SK_USE_FLOAT_SQRT
1425 fdx = fDstToIndex.getScaleX();
1426 fdy = fDstToIndex.getSkewY();
1427#endif
1428 }
1429
1430 if (proc == clamp_tileproc) {
1431 radial_clamp(fx, fy, dx, dy, dstC, count, cache,
1432 kCache32Bits, kCache32Count);
1433 } else if (proc == mirror_tileproc) {
1434#ifdef SK_USE_FLOAT_SQRT
1435 float ffx = srcPt.fX;
1436 float ffy = srcPt.fY;
1437 do {
1438 float fdist = sk_float_sqrt(ffx*ffx + ffy*ffy);
1439 unsigned fi = mirror_tileproc(SkFloatToFixed(fdist));
1440 SkASSERT(fi <= 0xFFFF);
1441 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1442 ffx += fdx;
1443 ffy += fdy;
1444 } while (--count != 0);
1445#else
1446 do {
1447 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1448 SkFixedSquare(fy);
1449 if (magnitudeSquared < 0) // Overflow.
1450 magnitudeSquared = SK_FixedMax;
1451 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1452 unsigned fi = mirror_tileproc(dist);
1453 SkASSERT(fi <= 0xFFFF);
1454 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1455 fx += dx;
1456 fy += dy;
1457 } while (--count != 0);
1458#endif
1459 } else {
1460 SkASSERT(proc == repeat_tileproc);
1461 do {
1462 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1463 SkFixedSquare(fy);
1464 if (magnitudeSquared < 0) // Overflow.
1465 magnitudeSquared = SK_FixedMax;
1466 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1467 unsigned fi = repeat_tileproc(dist);
1468 SkASSERT(fi <= 0xFFFF);
1469 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1470 fx += dx;
1471 fy += dy;
1472 } while (--count != 0);
1473 }
1474 } else { // perspective case
1475 SkScalar dstX = SkIntToScalar(x);
1476 SkScalar dstY = SkIntToScalar(y);
1477 do {
1478 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1479 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1480 SkASSERT(fi <= 0xFFFF);
1481 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1482 dstX += SK_Scalar1;
1483 } while (--count != 0);
1484 }
1485}
1486
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001487/* Two-point radial gradients are specified by two circles, each with a center
1488 point and radius. The gradient can be considered to be a series of
1489 concentric circles, with the color interpolated from the start circle
1490 (at t=0) to the end circle (at t=1).
1491
1492 For each point (x, y) in the span, we want to find the
1493 interpolated circle that intersects that point. The center
1494 of the desired circle (Cx, Cy) falls at some distance t
1495 along the line segment between the start point (Sx, Sy) and
1496 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001497
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001498 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1499 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001500
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001501 The radius of the desired circle (r) is also a linear interpolation t
1502 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001503
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001504 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001505
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001506 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001507
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001508 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001509
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001510 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001511
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001512 (x - ((1 - t) * Sx + t * Ex))^2
1513 + (y - ((1 - t) * Sy + t * Ey))^2
1514 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001515
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001516 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001517
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001518 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1519 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1520 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001521
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001522 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1523
1524 [Dx^2 + Dy^2 - Dr^2)] * t^2
1525 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1526 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001527
1528 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001529 possible circles on which the point may fall. Solving for t yields
1530 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001531
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001532 If a<0, the start circle is entirely contained in the
1533 end circle, and one of the roots will be <0 or >1 (off the line
1534 segment). If a>0, the start circle falls at least partially
1535 outside the end circle (or vice versa), and the gradient
1536 defines a "tube" where a point may be on one circle (on the
1537 inside of the tube) or the other (outside of the tube). We choose
1538 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001539
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001540 In order to keep the math to within the limits of fixed point,
1541 we divide the entire quadratic by Dr^2, and replace
1542 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001543
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001544 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1545 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1546 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001547
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001548 (x' and y' are computed by appending the subtract and scale to the
1549 fDstToIndex matrix in the constructor).
1550
1551 Since the 'A' component of the quadratic is independent of x' and y', it
1552 is precomputed in the constructor. Since the 'B' component is linear in
1553 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001554 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001555 a perspective projection), it must be computed in the loop.
1556
1557*/
1558
reed@google.com84e9c082011-04-13 17:44:24 +00001559static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1560 SkScalar sr2d2, SkScalar foura,
1561 SkScalar oneOverTwoA, bool posRoot) {
1562 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001563 if (0 == foura) {
1564 return SkScalarToFixed(SkScalarDiv(-c, b));
1565 }
1566
reed@google.com84e9c082011-04-13 17:44:24 +00001567 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001568 if (discrim < 0) {
1569 discrim = -discrim;
1570 }
reed@google.com84e9c082011-04-13 17:44:24 +00001571 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1572 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001573 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001574 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001575 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001576 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001577 }
reed@google.com84e9c082011-04-13 17:44:24 +00001578 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001579}
1580
1581class Two_Point_Radial_Gradient : public Gradient_Shader {
1582public:
1583 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1584 const SkPoint& end, SkScalar endRadius,
1585 const SkColor colors[], const SkScalar pos[],
1586 int colorCount, SkShader::TileMode mode,
1587 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001588 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1589 fCenter1(start),
1590 fCenter2(end),
1591 fRadius1(startRadius),
1592 fRadius2(endRadius) {
1593 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001594 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001595
1596 virtual BitmapType asABitmap(SkBitmap* bitmap,
1597 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001598 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001599 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001600 if (bitmap) {
1601 this->commonAsABitmap(bitmap);
1602 }
1603 SkScalar diffL = 0; // just to avoid gcc warning
1604 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001605 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001606 SkScalarSquare(fDiff.fY));
1607 }
1608 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001609 if (diffL) {
1610 SkScalar invDiffL = SkScalarInvert(diffL);
1611 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1612 SkScalarMul(invDiffL, fDiff.fX));
1613 } else {
1614 matrix->reset();
1615 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001616 matrix->preConcat(fPtsToUnit);
1617 }
1618 if (xy) {
1619 xy[0] = fTileMode;
1620 xy[1] = kClamp_TileMode;
1621 }
1622 if (NULL != twoPointRadialParams) {
1623 twoPointRadialParams[0] = diffL;
1624 twoPointRadialParams[1] = fStartRadius;
1625 twoPointRadialParams[2] = fDiffRadius;
1626 }
1627 return kTwoPointRadial_BitmapType;
1628 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001629
reed@google.com8e6d9142011-12-07 15:30:34 +00001630 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001631 if (info) {
1632 commonAsAGradient(info);
1633 info->fPoint[0] = fCenter1;
1634 info->fPoint[1] = fCenter2;
1635 info->fRadius[0] = fRadius1;
1636 info->fRadius[1] = fRadius2;
1637 }
1638 return kRadial2_GradientType;
1639 }
1640
reed@google.com8e6d9142011-12-07 15:30:34 +00001641 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001642 SkASSERT(count > 0);
1643
1644 // Zero difference between radii: fill with transparent black.
1645 if (fDiffRadius == 0) {
1646 sk_bzero(dstC, count * sizeof(*dstC));
1647 return;
1648 }
1649 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1650 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001651 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001652
1653 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001654 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001655 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001656 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001657 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1658 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001659 SkScalar dx, fx = srcPt.fX;
1660 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001661
reed@google.com61eb0402011-04-15 12:11:12 +00001662 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001663 SkFixed fixedX, fixedY;
1664 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1665 dx = SkFixedToScalar(fixedX);
1666 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001667 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001668 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001669 dx = fDstToIndex.getScaleX();
1670 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001671 }
reed@google.com84e9c082011-04-13 17:44:24 +00001672 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1673 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1674 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1675 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001676 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001677 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001678 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001679 SkFixed index = SkClampMax(t, 0xFFFF);
1680 SkASSERT(index <= 0xFFFF);
1681 *dstC++ = cache[index >> (16 - kCache32Bits)];
1682 fx += dx;
1683 fy += dy;
1684 b += db;
1685 }
reed@google.com61eb0402011-04-15 12:11:12 +00001686 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001687 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001688 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001689 SkFixed index = mirror_tileproc(t);
1690 SkASSERT(index <= 0xFFFF);
1691 *dstC++ = cache[index >> (16 - kCache32Bits)];
1692 fx += dx;
1693 fy += dy;
1694 b += db;
1695 }
reed@google.com61eb0402011-04-15 12:11:12 +00001696 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001697 SkASSERT(proc == repeat_tileproc);
1698 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001699 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001700 SkFixed index = repeat_tileproc(t);
1701 SkASSERT(index <= 0xFFFF);
1702 *dstC++ = cache[index >> (16 - kCache32Bits)];
1703 fx += dx;
1704 fy += dy;
1705 b += db;
1706 }
1707 }
reed@google.com61eb0402011-04-15 12:11:12 +00001708 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001709 SkScalar dstX = SkIntToScalar(x);
1710 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001711 for (; count > 0; --count) {
1712 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001713 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001714 SkScalar fx = srcPt.fX;
1715 SkScalar fy = srcPt.fY;
1716 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1717 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1718 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001719 SkFixed index = proc(t);
1720 SkASSERT(index <= 0xFFFF);
1721 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001722 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001723 }
1724 }
1725 }
1726
reed@android.com6c59a172009-09-22 20:24:05 +00001727 virtual bool setContext(const SkBitmap& device,
1728 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00001729 const SkMatrix& matrix) SK_OVERRIDE {
1730 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00001731 return false;
1732 }
1733
1734 // we don't have a span16 proc
1735 fFlags &= ~kHasSpan16_Flag;
1736 return true;
1737 }
1738
reed@google.com8e6d9142011-12-07 15:30:34 +00001739 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001740 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1741 }
1742
reed@google.com7716afb2011-12-07 15:17:50 +00001743 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00001744 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001745 buffer.writeScalar(fCenter1.fX);
1746 buffer.writeScalar(fCenter1.fY);
1747 buffer.writeScalar(fCenter2.fX);
1748 buffer.writeScalar(fCenter2.fY);
1749 buffer.writeScalar(fRadius1);
1750 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001751 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001752
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001753protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001754 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001755 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001756 fCenter1(unflatten_point(buffer)),
1757 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001758 fRadius1(buffer.readScalar()),
1759 fRadius2(buffer.readScalar()) {
1760 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001761 };
reed@google.com7716afb2011-12-07 15:17:50 +00001762 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001763
1764private:
1765 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001766 const SkPoint fCenter1;
1767 const SkPoint fCenter2;
1768 const SkScalar fRadius1;
1769 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001770 SkPoint fDiff;
1771 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001772
1773 void init() {
1774 fDiff = fCenter1 - fCenter2;
1775 fDiffRadius = fRadius2 - fRadius1;
1776 SkScalar inv = SkScalarInvert(fDiffRadius);
1777 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1778 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1779 fStartRadius = SkScalarMul(fRadius1, inv);
1780 fSr2D2 = SkScalarSquare(fStartRadius);
1781 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00001782 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001783
1784 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1785 fPtsToUnit.postScale(inv, inv);
1786 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001787};
1788
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789///////////////////////////////////////////////////////////////////////////////
1790
1791class Sweep_Gradient : public Gradient_Shader {
1792public:
1793 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1794 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001795 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1796 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 {
1798 fPtsToUnit.setTranslate(-cx, -cy);
1799 }
reed@google.com7716afb2011-12-07 15:17:50 +00001800 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
1801 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001802
1803 virtual BitmapType asABitmap(SkBitmap* bitmap,
1804 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001805 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00001806 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001807 if (bitmap) {
1808 this->commonAsABitmap(bitmap);
1809 }
1810 if (matrix) {
1811 *matrix = fPtsToUnit;
1812 }
1813 if (xy) {
1814 xy[0] = fTileMode;
1815 xy[1] = kClamp_TileMode;
1816 }
1817 return kSweep_BitmapType;
1818 }
1819
reed@google.com7716afb2011-12-07 15:17:50 +00001820 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001821 if (info) {
1822 commonAsAGradient(info);
1823 info->fPoint[0] = fCenter;
1824 }
1825 return kSweep_GradientType;
1826 }
1827
reed@google.com8e6d9142011-12-07 15:30:34 +00001828 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1830 }
1831
reed@google.com7716afb2011-12-07 15:17:50 +00001832 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001833 this->INHERITED::flatten(buffer);
1834 buffer.writeScalar(fCenter.fX);
1835 buffer.writeScalar(fCenter.fY);
1836 }
1837
reed@android.com8a1c16f2008-12-17 15:59:43 +00001838protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001839 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1840 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001841 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001842 }
1843
reed@google.com7716afb2011-12-07 15:17:50 +00001844 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001845
1846private:
1847 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001848 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001849};
1850
1851#ifdef COMPUTE_SWEEP_TABLE
1852#define PI 3.14159265
1853static bool gSweepTableReady;
1854static uint8_t gSweepTable[65];
1855
1856/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1857 We scale the results to [0..32]
1858*/
reed@google.com61eb0402011-04-15 12:11:12 +00001859static const uint8_t* build_sweep_table() {
1860 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 const int N = 65;
1862 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001863
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864 for (int i = 0; i < N; i++)
1865 {
1866 double arg = i / DENOM;
1867 double v = atan(arg);
1868 int iv = (int)round(v * DENOM * 2 / PI);
1869// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1870 printf("%d, ", iv);
1871 gSweepTable[i] = iv;
1872 }
1873 gSweepTableReady = true;
1874 }
1875 return gSweepTable;
1876}
1877#else
1878static const uint8_t gSweepTable[] = {
1879 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1880 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1881 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1882 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1883 32
1884};
1885static const uint8_t* build_sweep_table() { return gSweepTable; }
1886#endif
1887
1888// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1889// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1890// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1891
1892//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001893static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 SkASSERT(numer <= denom);
1895 SkASSERT(numer > 0);
1896 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001897
reed@android.com8a1c16f2008-12-17 15:59:43 +00001898 int nbits = SkCLZ(numer);
1899 int dbits = SkCLZ(denom);
1900 int bits = 6 - nbits + dbits;
1901 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001902
reed@google.com61eb0402011-04-15 12:11:12 +00001903 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001905 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001906
1907 denom <<= dbits - 1;
1908 numer <<= nbits - 1;
1909
1910 unsigned result = 0;
1911
1912 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001913 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001915 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001916 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001917 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001918
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001920 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921 // make room for the rest of the answer bits
1922 result <<= bits;
1923 switch (bits) {
1924 case 6:
1925 if ((numer = (numer << 1) - denom) >= 0)
1926 result |= 32;
1927 else
1928 numer += denom;
1929 case 5:
1930 if ((numer = (numer << 1) - denom) >= 0)
1931 result |= 16;
1932 else
1933 numer += denom;
1934 case 4:
1935 if ((numer = (numer << 1) - denom) >= 0)
1936 result |= 8;
1937 else
1938 numer += denom;
1939 case 3:
1940 if ((numer = (numer << 1) - denom) >= 0)
1941 result |= 4;
1942 else
1943 numer += denom;
1944 case 2:
1945 if ((numer = (numer << 1) - denom) >= 0)
1946 result |= 2;
1947 else
1948 numer += denom;
1949 case 1:
1950 default: // not strictly need, but makes GCC make better ARM code
1951 if ((numer = (numer << 1) - denom) >= 0)
1952 result |= 1;
1953 else
1954 numer += denom;
1955 }
1956 }
1957 return result;
1958}
1959
1960// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001961static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962#ifdef SK_DEBUG
1963 {
1964 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001965 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 gOnce = true;
1967 SkASSERT(div_64(55, 55) == 64);
1968 SkASSERT(div_64(128, 256) == 32);
1969 SkASSERT(div_64(2326528, 4685824) == 31);
1970 SkASSERT(div_64(753664, 5210112) == 9);
1971 SkASSERT(div_64(229376, 4882432) == 3);
1972 SkASSERT(div_64(2, 64) == 2);
1973 SkASSERT(div_64(1, 64) == 1);
1974 // test that we handle underflow correctly
1975 SkASSERT(div_64(12345, 0x54321234) == 0);
1976 }
1977 }
1978#endif
1979
1980 SkASSERT(y > 0 && x > 0);
1981 const uint8_t* table = build_sweep_table();
1982
1983 unsigned result;
1984 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00001985 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 // first part of the atan(v) = PI/2 - atan(1/v) identity
1987 // since our div_64 and table want v <= 1, where v = y/x
1988 SkTSwap<SkFixed>(x, y);
1989 }
1990
1991 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001992
reed@android.com8a1c16f2008-12-17 15:59:43 +00001993#ifdef SK_DEBUG
1994 {
1995 unsigned result2 = SkDivBits(y, x, 6);
1996 SkASSERT(result2 == result ||
1997 (result == 1 && result2 == 0));
1998 }
1999#endif
2000
2001 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2002 result = table[result];
2003
reed@google.com61eb0402011-04-15 12:11:12 +00002004 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 // complete the atan(v) = PI/2 - atan(1/v) identity
2006 result = 64 - result;
2007 // pin to 63
2008 result -= result >> 6;
2009 }
2010
2011 SkASSERT(result <= 63);
2012 return result;
2013}
2014
2015// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002016#ifdef SK_SCALAR_IS_FLOAT
2017static unsigned SkATan2_255(float y, float x) {
2018 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2019 static const float g255Over2PI = 40.584510488433314f;
2020
2021 float result = sk_float_atan2(y, x);
2022 if (result < 0) {
2023 result += 2 * SK_ScalarPI;
2024 }
2025 SkASSERT(result >= 0);
2026 // since our value is always >= 0, we can cast to int, which is faster than
2027 // calling floorf()
2028 int ir = (int)(result * g255Over2PI);
2029 SkASSERT(ir >= 0 && ir <= 255);
2030 return ir;
2031}
2032#else
reed@google.com61eb0402011-04-15 12:11:12 +00002033static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2034 if (x == 0) {
2035 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002037 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002038 return y < 0 ? 192 : 64;
2039 }
reed@google.com61eb0402011-04-15 12:11:12 +00002040 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002042 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002043
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 /* Find the right quadrant for x,y
2045 Since atan_0_90 only handles the first quadrant, we rotate x,y
2046 appropriately before calling it, and then add the right amount
2047 to account for the real quadrant.
2048 quadrant 0 : add 0 | x > 0 && y > 0
2049 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2050 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2051 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002052
reed@android.com8a1c16f2008-12-17 15:59:43 +00002053 map x<0 to (1 << 6)
2054 map y<0 to (3 << 6)
2055 add = map_x ^ map_y
2056 */
2057 int xsign = x >> 31;
2058 int ysign = y >> 31;
2059 int add = ((-xsign) ^ (ysign & 3)) << 6;
2060
2061#ifdef SK_DEBUG
2062 if (0 == add)
2063 SkASSERT(x > 0 && y > 0);
2064 else if (64 == add)
2065 SkASSERT(x < 0 && y > 0);
2066 else if (128 == add)
2067 SkASSERT(x < 0 && y < 0);
2068 else if (192 == add)
2069 SkASSERT(x > 0 && y < 0);
2070 else
2071 SkASSERT(!"bad value for add");
2072#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002073
reed@android.com8a1c16f2008-12-17 15:59:43 +00002074 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2075 where we need to rotate x,y by 90 or -90
2076 */
2077 x = (x ^ xsign) - xsign;
2078 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002079 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002080 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002081 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002082
2083 unsigned result = add + atan_0_90(y, x);
2084 SkASSERT(result < 256);
2085 return result;
2086}
reed@google.com51baf5a2011-09-21 13:38:36 +00002087#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088
reed@google.comdd5bd672011-09-20 15:56:13 +00002089void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002090 SkMatrix::MapXYProc proc = fDstToIndexProc;
2091 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002092 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002094
reed@google.com61eb0402011-04-15 12:11:12 +00002095 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2097 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002098 SkScalar dx, fx = srcPt.fX;
2099 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002100
reed@google.com61eb0402011-04-15 12:11:12 +00002101 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102 SkFixed storage[2];
2103 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2104 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002105 dx = SkFixedToScalar(storage[0]);
2106 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002107 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002108 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002109 dx = matrix.getScaleX();
2110 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002111 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002112
reed@google.com61eb0402011-04-15 12:11:12 +00002113 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002114 *dstC++ = cache[SkATan2_255(fy, fx)];
2115 fx += dx;
2116 fy += dy;
2117 }
reed@google.com61eb0402011-04-15 12:11:12 +00002118 } else { // perspective case
2119 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002121 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2122 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002123 }
2124 }
2125}
2126
reed@google.comdd5bd672011-09-20 15:56:13 +00002127void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002128 SkMatrix::MapXYProc proc = fDstToIndexProc;
2129 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002130 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 int toggle = ((x ^ y) & 1) << kCache16Bits;
2132 SkPoint srcPt;
2133
reed@google.com61eb0402011-04-15 12:11:12 +00002134 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2136 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002137 SkScalar dx, fx = srcPt.fX;
2138 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002139
reed@google.com61eb0402011-04-15 12:11:12 +00002140 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 SkFixed storage[2];
2142 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2143 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002144 dx = SkFixedToScalar(storage[0]);
2145 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002146 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002148 dx = matrix.getScaleX();
2149 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002151
reed@google.com61eb0402011-04-15 12:11:12 +00002152 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2154 *dstC++ = cache[toggle + index];
2155 toggle ^= (1 << kCache16Bits);
2156 fx += dx;
2157 fy += dy;
2158 }
reed@google.com61eb0402011-04-15 12:11:12 +00002159 } else { // perspective case
2160 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002161 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2162 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002163
reed@google.com51baf5a2011-09-21 13:38:36 +00002164 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165 index >>= (8 - kCache16Bits);
2166 *dstC++ = cache[toggle + index];
2167 toggle ^= (1 << kCache16Bits);
2168 }
2169 }
2170}
2171
reed@google.com61eb0402011-04-15 12:11:12 +00002172///////////////////////////////////////////////////////////////////////////////
2173///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002174
2175// assumes colors is SkColor* and pos is SkScalar*
2176#define EXPAND_1_COLOR(count) \
2177 SkColor tmp[2]; \
2178 do { \
2179 if (1 == count) { \
2180 tmp[0] = tmp[1] = colors[0]; \
2181 colors = tmp; \
2182 pos = NULL; \
2183 count = 2; \
2184 } \
2185 } while (0)
2186
reed@google.com61eb0402011-04-15 12:11:12 +00002187SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2188 const SkColor colors[],
2189 const SkScalar pos[], int colorCount,
2190 SkShader::TileMode mode,
2191 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002192 if (NULL == pts || NULL == colors || colorCount < 1) {
2193 return NULL;
2194 }
2195 EXPAND_1_COLOR(colorCount);
2196
reed@android.comab840b82009-07-01 17:00:03 +00002197 return SkNEW_ARGS(Linear_Gradient,
2198 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002199}
2200
reed@google.com61eb0402011-04-15 12:11:12 +00002201SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2202 const SkColor colors[],
2203 const SkScalar pos[], int colorCount,
2204 SkShader::TileMode mode,
2205 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002206 if (radius <= 0 || NULL == colors || colorCount < 1) {
2207 return NULL;
2208 }
2209 EXPAND_1_COLOR(colorCount);
2210
reed@android.comab840b82009-07-01 17:00:03 +00002211 return SkNEW_ARGS(Radial_Gradient,
2212 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002213}
2214
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002215SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2216 SkScalar startRadius,
2217 const SkPoint& end,
2218 SkScalar endRadius,
2219 const SkColor colors[],
2220 const SkScalar pos[],
2221 int colorCount,
2222 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002223 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002224 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2225 return NULL;
2226 }
2227 EXPAND_1_COLOR(colorCount);
2228
2229 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002230 (start, startRadius, end, endRadius, colors, pos,
2231 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002232}
2233
reed@android.com8a1c16f2008-12-17 15:59:43 +00002234SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2235 const SkColor colors[],
2236 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002237 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002238 if (NULL == colors || count < 1) {
2239 return NULL;
2240 }
2241 EXPAND_1_COLOR(count);
2242
2243 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2244}
2245
caryclark@google.comd26147a2011-12-15 14:16:43 +00002246SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2247 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2248 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002249
caryclark@google.comd26147a2011-12-15 14:16:43 +00002250 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
reed@android.com8a1c16f2008-12-17 15:59:43 +00002251
caryclark@google.comd26147a2011-12-15 14:16:43 +00002252 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2253SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END