blob: e9aec52a745a5e1c89662ad810ed4035cfce6a4f [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
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.com9c7443d2011-01-17 18:46:37 +000022#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
23 #define USE_DITHER_32BIT_GRADIENT
24#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +000025
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
120 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
121 virtual uint32_t getFlags() { return fFlags; }
122
123protected:
124 Gradient_Shader(SkFlattenableReadBuffer& );
125 SkUnitMapper* fMapper;
126 SkMatrix fPtsToUnit; // set by subclass
127 SkMatrix fDstToIndex;
128 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 TileMode fTileMode;
130 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000131 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132 uint8_t fDstToIndexClass;
133 uint8_t fFlags;
134
135 struct Rec {
136 SkFixed fPos; // 0...1
137 uint32_t fScale; // (1 << 24) / range
138 };
139 Rec* fRecs;
140
141 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000142 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000144 kCache16Mask = kCache16Count - 1,
145 kCache16Shift = 16 - kCache16Bits,
146
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 kCache32Bits = 8, // pretty much should always be 8
148 kCache32Count = 1 << kCache32Bits
149 };
150 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000151 const uint16_t* getCache16() const;
152 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153
reed@google.com7c2f27d2011-03-07 19:29:00 +0000154 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000155 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000156
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157private:
158 enum {
159 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
160
reed@android.com1c12abe2009-07-02 15:01:02 +0000161 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 };
163 SkColor fStorage[(kStorageSize + 3) >> 2];
164 SkColor* fOrigColors;
165
reed@google.com7c2f27d2011-03-07 19:29:00 +0000166 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
167 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168
reed@google.com7c2f27d2011-03-07 19:29:00 +0000169 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
170 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000171 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 +0000172
reed@android.com512a8762009-12-14 15:25:36 +0000173 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000174 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
175 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000176 void setCacheAlpha(U8CPU alpha) const;
reed@android.com512a8762009-12-14 15:25:36 +0000177
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178 typedef SkShader INHERITED;
179};
180
reed@android.com41bccf52009-04-03 13:33:51 +0000181static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182 SkASSERT(x >= 0 && x <= SK_Scalar1);
183
184#ifdef SK_SCALAR_IS_FLOAT
185 return (unsigned)(x * 0xFFFF);
186#else
187 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
188#endif
189}
190
reed@android.com41bccf52009-04-03 13:33:51 +0000191Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
192 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 SkASSERT(colorCount > 1);
194
195 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
196
197 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000198 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000199
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
201 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
202 fTileMode = mode;
203 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000204
reed@android.com41bccf52009-04-03 13:33:51 +0000205 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000206 fCache32 = NULL;
207 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208
reed@android.com41bccf52009-04-03 13:33:51 +0000209 /* Note: we let the caller skip the first and/or last position.
210 i.e. pos[0] = 0.3, pos[1] = 0.7
211 In these cases, we insert dummy entries to ensure that the final data
212 will be bracketed by [0, 1].
213 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
214
215 Thus colorCount (the caller's value, and fColorCount (our value) may
216 differ by up to 2. In the above example:
217 colorCount = 2
218 fColorCount = 4
219 */
220 fColorCount = colorCount;
221 // check if we need to add in dummy start and/or end position/colors
222 bool dummyFirst = false;
223 bool dummyLast = false;
224 if (pos) {
225 dummyFirst = pos[0] != 0;
226 dummyLast = pos[colorCount - 1] != SK_Scalar1;
227 fColorCount += dummyFirst + dummyLast;
228 }
229
230 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000231 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000232 fOrigColors = reinterpret_cast<SkColor*>(
233 sk_malloc_throw(size * fColorCount));
234 }
235 else {
236 fOrigColors = fStorage;
237 }
238
239 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 {
reed@android.com41bccf52009-04-03 13:33:51 +0000241 SkColor* origColors = fOrigColors;
242 if (dummyFirst) {
243 *origColors++ = colors[0];
244 }
245 memcpy(origColors, colors, colorCount * sizeof(SkColor));
246 if (dummyLast) {
247 origColors += colorCount;
248 *origColors = colors[colorCount - 1];
249 }
250 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000251
reed@android.com1c12abe2009-07-02 15:01:02 +0000252 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000253 if (fColorCount > 2) {
254 Rec* recs = fRecs;
255 recs->fPos = 0;
256 // recs->fScale = 0; // unused;
257 recs += 1;
258 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000259 /* We need to convert the user's array of relative positions into
260 fixed-point positions and scale factors. We need these results
261 to be strictly monotonic (no two values equal or out of order).
262 Hence this complex loop that just jams a zero for the scale
263 value if it sees a segment out of order, and it assures that
264 we start at 0 and end at 1.0
265 */
266 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000267 int startIndex = dummyFirst ? 0 : 1;
268 int count = colorCount + dummyLast;
269 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000270 // force the last value to be 1.0
271 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000272 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000273 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000274 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000275 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000276 }
reed@android.com41bccf52009-04-03 13:33:51 +0000277 // pin curr withing range
278 if (curr < 0) {
279 curr = 0;
280 } else if (curr > SK_Fixed1) {
281 curr = SK_Fixed1;
282 }
283 recs->fPos = curr;
284 if (curr > prev) {
285 recs->fScale = (1 << 24) / (curr - prev);
286 } else {
287 recs->fScale = 0; // ignore this segment
288 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289 // get ready for the next value
290 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000291 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000292 }
reed@android.com41bccf52009-04-03 13:33:51 +0000293 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000294 SkFixed dp = SK_Fixed1 / (colorCount - 1);
295 SkFixed p = dp;
296 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000297 for (int i = 1; i < colorCount; i++) {
298 recs->fPos = p;
299 recs->fScale = scale;
300 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 p += dp;
302 }
303 }
304 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000305 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306}
307
308Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000309 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 fCacheAlpha = 256;
311
312 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
313
314 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000315 fCache32 = NULL;
316 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317
reed@android.com41bccf52009-04-03 13:33:51 +0000318 int colorCount = fColorCount = buffer.readU32();
319 if (colorCount > kColorStorageCount) {
320 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
321 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
322 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000324 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326
327 fTileMode = (TileMode)buffer.readU8();
328 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000329 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330 if (colorCount > 2) {
331 Rec* recs = fRecs;
332 recs[0].fPos = 0;
333 for (int i = 1; i < colorCount; i++) {
334 recs[i].fPos = buffer.readS32();
335 recs[i].fScale = buffer.readU32();
336 }
337 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000338 SkReadMatrix(&buffer, &fPtsToUnit);
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000339 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340}
341
reed@android.com41bccf52009-04-03 13:33:51 +0000342Gradient_Shader::~Gradient_Shader() {
343 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000345 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000346 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000347 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000348 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000349 }
reed@google.com82065d62011-02-07 15:30:46 +0000350 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351}
352
reed@android.com41bccf52009-04-03 13:33:51 +0000353void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354 this->INHERITED::flatten(buffer);
355 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000356 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000357 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
358 buffer.write8(fTileMode);
359 if (fColorCount > 2) {
360 Rec* recs = fRecs;
361 for (int i = 1; i < fColorCount; i++) {
362 buffer.write32(recs[i].fPos);
363 buffer.write32(recs[i].fScale);
364 }
365 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000366 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367}
368
369bool Gradient_Shader::setContext(const SkBitmap& device,
370 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000371 const SkMatrix& matrix) {
372 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000374 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000375
376 const SkMatrix& inverse = this->getTotalInverse();
377
378 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
379 return false;
380 }
381
382 fDstToIndexProc = fDstToIndex.getMapXYProc();
383 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
384
385 // now convert our colors in to PMColors
386 unsigned paintAlpha = this->getPaintAlpha();
387 unsigned colorAlpha = 0xFF;
388
reed@android.com3d06a8c2009-07-07 18:19:59 +0000389 // FIXME: record colorAlpha in constructor, since this is not affected
390 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000391 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000392 SkColor src = fOrigColors[i];
393 unsigned sa = SkColorGetA(src);
394 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000395 }
396
397 fFlags = this->INHERITED::getFlags();
398 if ((colorAlpha & paintAlpha) == 0xFF) {
399 fFlags |= kOpaqueAlpha_Flag;
400 }
401 // we can do span16 as long as our individual colors are opaque,
402 // regardless of the paint's alpha
403 if (0xFF == colorAlpha) {
404 fFlags |= kHasSpan16_Flag;
405 }
406
reed@google.com95eed982011-07-05 17:01:56 +0000407 this->setCacheAlpha(paintAlpha);
408 return true;
409}
410
411void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412 // if the new alpha differs from the previous time we were called, inval our cache
413 // this will trigger the cache to be rebuilt.
414 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000415 if (fCacheAlpha != alpha) {
416 fCache16 = NULL; // inval the cache
417 fCache32 = NULL; // inval the cache
418 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000419 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000420 if (fCache32PixelRef) {
421 fCache32PixelRef->notifyPixelsChanged();
422 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000423 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424}
425
reed@android.com41bccf52009-04-03 13:33:51 +0000426static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000427 SkASSERT(a == SkToU8(a));
428 SkASSERT(b == SkToU8(b));
429 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 return a + ((b - a) * scale >> 8);
431}
432
reed@android.com41bccf52009-04-03 13:33:51 +0000433static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
434 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435#if 0
436 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
437 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
438 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
439 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
440
441 return SkPackARGB32(a, r, g, b);
442#else
443 int otherBlend = 256 - blend;
444
445#if 0
446 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
447 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
448 SkASSERT((t0 & t1) == 0);
449 return t0 | t1;
450#else
451 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
452 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
453#endif
454
455#endif
456}
457
458#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
459
reed@android.com41bccf52009-04-03 13:33:51 +0000460/** We take the original colors, not our premultiplied PMColors, since we can
461 build a 16bit table as long as the original colors are opaque, even if the
462 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000463*/
reed@android.com512a8762009-12-14 15:25:36 +0000464void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
465 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000466 SkASSERT(count > 1);
467 SkASSERT(SkColorGetA(c0) == 0xFF);
468 SkASSERT(SkColorGetA(c1) == 0xFF);
469
470 SkFixed r = SkColorGetR(c0);
471 SkFixed g = SkColorGetG(c0);
472 SkFixed b = SkColorGetB(c0);
473
474 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
475 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
476 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
477
478 r = SkIntToFixed(r) + 0x8000;
479 g = SkIntToFixed(g) + 0x8000;
480 b = SkIntToFixed(b) + 0x8000;
481
482 do {
483 unsigned rr = r >> 16;
484 unsigned gg = g >> 16;
485 unsigned bb = b >> 16;
486 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000487 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000488 cache += 1;
489 r += dr;
490 g += dg;
491 b += db;
492 } while (--count != 0);
493}
494
reed@google.com55b8e8c2011-01-13 16:22:35 +0000495/*
496 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
497 * semantics of how we 2x2 dither 32->16
498 */
499static inline U8CPU dither_fixed_to_8(SkFixed n) {
500 n >>= 8;
501 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
502}
503
504/*
505 * For dithering with premultiply, we want to ceiling the alpha component,
506 * to ensure that it is always >= any color component.
507 */
508static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
509 n >>= 8;
510 return ((n << 1) - (n | (n >> 8))) >> 8;
511}
512
513void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
514 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000515 SkASSERT(count > 1);
516
reed@android.com1c12abe2009-07-02 15:01:02 +0000517 // need to apply paintAlpha to our two endpoints
518 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
519 SkFixed da;
520 {
521 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
522 da = SkIntToFixed(tmp - a) / (count - 1);
523 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000524
reed@android.com1c12abe2009-07-02 15:01:02 +0000525 SkFixed r = SkColorGetR(c0);
526 SkFixed g = SkColorGetG(c0);
527 SkFixed b = SkColorGetB(c0);
528 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
529 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
530 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000531
532 a = SkIntToFixed(a) + 0x8000;
533 r = SkIntToFixed(r) + 0x8000;
534 g = SkIntToFixed(g) + 0x8000;
535 b = SkIntToFixed(b) + 0x8000;
536
537 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000538 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
539 cache[kCache32Count] = SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
540 dither_fixed_to_8(r),
541 dither_fixed_to_8(g),
542 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000543 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000544 a += da;
545 r += dr;
546 g += dg;
547 b += db;
548 } while (--count != 0);
549}
550
reed@android.com41bccf52009-04-03 13:33:51 +0000551static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000552 SkASSERT((unsigned)x <= SK_Fixed1);
553 return x - (x >> 16);
554}
555
reed@android.com200645d2009-12-14 16:41:57 +0000556static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000557 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000558 if (6 == bits) {
559 return (x << 10) | (x << 4) | (x >> 2);
560 }
561 if (8 == bits) {
562 return (x << 8) | x;
563 }
564 sk_throw();
565 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000566}
567
reed@google.com7c2f27d2011-03-07 19:29:00 +0000568const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000569 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000570 // double the count for dither entries
571 const int entryCount = kCache16Count * 2;
572 const size_t allocSize = sizeof(uint16_t) * entryCount;
573
reed@android.com3c9b2a42009-08-27 19:28:37 +0000574 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000575 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000576 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000578 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000579 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000580 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581 Rec* rec = fRecs;
582 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000583 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000584 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585 SkASSERT(nextIndex < kCache16Count);
586
587 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000588 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589 prevIndex = nextIndex;
590 }
591 SkASSERT(prevIndex == kCache16Count - 1);
592 }
593
reed@android.com41bccf52009-04-03 13:33:51 +0000594 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000595 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 uint16_t* linear = fCache16; // just computed linear data
597 uint16_t* mapped = fCache16Storage; // storage for mapped data
598 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000599 for (int i = 0; i < kCache16Count; i++) {
600 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000602 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000603 }
604 sk_free(fCache16);
605 fCache16 = fCache16Storage;
606 }
607 }
608 return fCache16;
609}
610
reed@google.com7c2f27d2011-03-07 19:29:00 +0000611const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000612 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000613 // double the count for dither entries
614 const int entryCount = kCache32Count * 2;
615 const size_t allocSize = sizeof(SkPMColor) * entryCount;
616
reed@google.comdc731fd2010-12-23 15:19:47 +0000617 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000618 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
619 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000620 }
621 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000622 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000623 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
624 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000625 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626 Rec* rec = fRecs;
627 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000628 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
630 SkASSERT(nextIndex < kCache32Count);
631
632 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000633 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
634 fOrigColors[i],
635 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 prevIndex = nextIndex;
637 }
638 SkASSERT(prevIndex == kCache32Count - 1);
639 }
640
reed@android.com41bccf52009-04-03 13:33:51 +0000641 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000642 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000643 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000645 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000647 for (int i = 0; i < kCache32Count; i++) {
648 int index = map->mapUnit16((i << 8) | i) >> 8;
649 mapped[i] = linear[index];
650 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000651 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000652 fCache32PixelRef->unref();
653 fCache32PixelRef = newPR;
654 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655 }
656 }
657 return fCache32;
658}
659
reed@google.comdc731fd2010-12-23 15:19:47 +0000660/*
661 * Because our caller might rebuild the same (logically the same) gradient
662 * over and over, we'd like to return exactly the same "bitmap" if possible,
663 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
664 * To do that, we maintain a private cache of built-bitmaps, based on our
665 * colors and positions. Note: we don't try to flatten the fMapper, so if one
666 * is present, we skip the cache for now.
667 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000668void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000669 // our caller assumes no external alpha, so we ensure that our cache is
670 // built with 0xFF
671 this->setCacheAlpha(0xFF);
672
reed@google.comdc731fd2010-12-23 15:19:47 +0000673 // don't have a way to put the mapper into our cache-key yet
674 if (fMapper) {
675 // force our cahce32pixelref to be built
676 (void)this->getCache32();
677 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
678 bitmap->setPixelRef(fCache32PixelRef);
679 return;
680 }
681
682 // build our key: [numColors + colors[] + {positions[]} ]
683 int count = 1 + fColorCount;
684 if (fColorCount > 2) {
685 count += fColorCount - 1; // fRecs[].fPos
686 }
687
688 SkAutoSTMalloc<16, int32_t> storage(count);
689 int32_t* buffer = storage.get();
690
691 *buffer++ = fColorCount;
692 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
693 buffer += fColorCount;
694 if (fColorCount > 2) {
695 for (int i = 1; i < fColorCount; i++) {
696 *buffer++ = fRecs[i].fPos;
697 }
698 }
699 SkASSERT(buffer - storage.get() == count);
700
701 ///////////////////////////////////
702
703 static SkMutex gMutex;
704 static SkBitmapCache* gCache;
705 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
706 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
707 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000708
reed@google.comdc731fd2010-12-23 15:19:47 +0000709 if (NULL == gCache) {
710 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
711 }
712 size_t size = count * sizeof(int32_t);
713
714 if (!gCache->find(storage.get(), size, bitmap)) {
715 // force our cahce32pixelref to be built
716 (void)this->getCache32();
717 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
718 bitmap->setPixelRef(fCache32PixelRef);
719
720 gCache->add(storage.get(), size, *bitmap);
721 }
722}
723
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000724void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
725 if (info) {
726 if (info->fColorCount >= fColorCount) {
727 if (info->fColors) {
728 memcpy(info->fColors, fOrigColors,
729 fColorCount * sizeof(SkColor));
730 }
731 if (info->fColorOffsets) {
732 if (fColorCount == 2) {
733 info->fColorOffsets[0] = 0;
734 info->fColorOffsets[1] = SK_Scalar1;
735 } else if (fColorCount > 2) {
736 for (int i = 0; i < fColorCount; i++)
737 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
738 }
739 }
740 }
741 info->fColorCount = fColorCount;
742 info->fTileMode = fTileMode;
743 }
744}
745
reed@google.com61eb0402011-04-15 12:11:12 +0000746///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747
reed@android.com41bccf52009-04-03 13:33:51 +0000748static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000749 SkVector vec = pts[1] - pts[0];
750 SkScalar mag = vec.length();
751 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
752
753 vec.scale(inv);
754 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
755 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
756 matrix->postScale(inv, inv);
757}
758
759///////////////////////////////////////////////////////////////////////////////
760
761class Linear_Gradient : public Gradient_Shader {
762public:
763 Linear_Gradient(const SkPoint pts[2],
764 const SkColor colors[], const SkScalar pos[], int colorCount,
765 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000766 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
767 fStart(pts[0]),
768 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769 {
770 pts_to_unit_matrix(pts, &fPtsToUnit);
771 }
reed@android.com9b46e772009-06-05 12:24:41 +0000772
reed@android.com5119bdb2009-06-12 21:27:03 +0000773 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
775 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000776 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*,
reed@google.com7c2f27d2011-03-07 19:29:00 +0000777 TileMode*, SkScalar* twoPointRadialParams) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000778 virtual GradientType asAGradient(GradientInfo* info) const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779
reed@google.com55b8e8c2011-01-13 16:22:35 +0000780 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781 return SkNEW_ARGS(Linear_Gradient, (buffer));
782 }
783
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000784 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
785 this->INHERITED::flatten(buffer);
786 buffer.writeScalar(fStart.fX);
787 buffer.writeScalar(fStart.fY);
788 buffer.writeScalar(fEnd.fX);
789 buffer.writeScalar(fEnd.fY);
790 }
791
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000793 Linear_Gradient(SkFlattenableReadBuffer& buffer)
794 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000795 fStart(unflatten_point(buffer)),
796 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000797 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 virtual Factory getFactory() { return CreateProc; }
799
800private:
801 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000802 const SkPoint fStart;
803 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804};
805
reed@android.com5119bdb2009-06-12 21:27:03 +0000806bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
807 const SkMatrix& matrix) {
808 if (!this->INHERITED::setContext(device, paint, matrix)) {
809 return false;
810 }
811
812 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
813 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000814 fFlags |= SkShader::kConstInY32_Flag;
815 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
816 // only claim this if we do have a 16bit mode (i.e. none of our
817 // colors have alpha), and if we are not dithering (which obviously
818 // is not const in Y).
819 fFlags |= SkShader::kConstInY16_Flag;
820 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000821 }
822 return true;
823}
824
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@google.com61eb0402011-04-15 12:11:12 +0000826static inline bool no_need_for_clamp(int fx, int dx, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000827 SkASSERT(count > 0);
828 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
829}
830
reed@google.com5eb158d2011-04-15 15:50:34 +0000831#include "SkClampRange.h"
832
833#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000834 do { \
835 unsigned fi = fx >> 8; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000836 SkASSERT(fi <= 0xFF); \
837 fx += dx; \
838 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000839 toggle ^= TOGGLE_MASK; \
840 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000841
842
reed@google.comdd5bd672011-09-20 15:56:13 +0000843void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844 SkASSERT(count > 0);
845
846 SkPoint srcPt;
847 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
848 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +0000849 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000850#ifdef USE_DITHER_32BIT_GRADIENT
851 int toggle = ((x ^ y) & 1) << kCache32Bits;
852 const int TOGGLE_MASK = (1 << kCache32Bits);
853#else
854 int toggle = 0;
855 const int TOGGLE_MASK = 0;
856#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857
reed@android.comc552a432009-06-12 20:02:50 +0000858 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000859 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
860 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
862
reed@android.comc552a432009-06-12 20:02:50 +0000863 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864 SkFixed dxStorage[1];
865 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
866 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000867 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
869 dx = SkScalarToFixed(fDstToIndex.getScaleX());
870 }
871
reed@android.comc552a432009-06-12 20:02:50 +0000872 if (SkFixedNearlyZero(dx)) {
873 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874 unsigned fi = proc(fx);
875 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000876 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000878 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000879 SkClampRange range;
880 range.init(fx, dx, count, 0, 0xFF);
881
882 if ((count = range.fCount0) > 0) {
883 sk_memset32_dither(dstC,
884 cache[toggle + range.fV0],
885 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
886 count);
887 dstC += count;
888 }
889 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +0000890 int unroll = count >> 3;
891 fx = range.fFx1;
892 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000893 NO_CHECK_ITER; NO_CHECK_ITER;
894 NO_CHECK_ITER; NO_CHECK_ITER;
895 NO_CHECK_ITER; NO_CHECK_ITER;
896 NO_CHECK_ITER; NO_CHECK_ITER;
897 }
898 if ((count &= 7) > 0) {
899 do {
900 NO_CHECK_ITER;
901 } while (--count != 0);
902 }
903 }
904 if ((count = range.fCount2) > 0) {
905 sk_memset32_dither(dstC,
906 cache[toggle + range.fV1],
907 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
908 count);
909 }
reed@android.comc552a432009-06-12 20:02:50 +0000910 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000911 do {
912 unsigned fi = mirror_8bits(fx >> 8);
913 SkASSERT(fi <= 0xFF);
914 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000915 *dstC++ = cache[toggle + fi];
916 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000917 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000918 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000919 SkASSERT(proc == repeat_tileproc);
920 do {
921 unsigned fi = repeat_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);
927 }
reed@android.comc552a432009-06-12 20:02:50 +0000928 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000929 SkScalar dstX = SkIntToScalar(x);
930 SkScalar dstY = SkIntToScalar(y);
931 do {
932 dstProc(fDstToIndex, dstX, dstY, &srcPt);
933 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
934 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000935 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
936 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000937 dstX += SK_Scalar1;
938 } while (--count != 0);
939 }
940}
941
reed@google.com55b8e8c2011-01-13 16:22:35 +0000942SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000943 SkMatrix* matrix,
944 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000945 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000947 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000948 }
949 if (matrix) {
950 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
951 matrix->preConcat(fPtsToUnit);
952 }
953 if (xy) {
954 xy[0] = fTileMode;
955 xy[1] = kClamp_TileMode;
956 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000957 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000958}
959
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000960SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
961 if (info) {
962 commonAsAGradient(info);
963 info->fPoint[0] = fStart;
964 info->fPoint[1] = fEnd;
965 }
966 return kLinear_GradientType;
967}
968
reed@android.com3c9b2a42009-08-27 19:28:37 +0000969static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
970 int count) {
971 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 *dst++ = value;
973 count -= 1;
974 SkTSwap(value, other);
975 }
976
977 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000978
reed@android.com3c9b2a42009-08-27 19:28:37 +0000979 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000981 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000982}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000983
reed@google.com5eb158d2011-04-15 15:50:34 +0000984#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +0000985 do { \
986 unsigned fi = fx >> kCache16Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000987 SkASSERT(fi <= kCache16Mask); \
988 fx += dx; \
989 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000990 toggle ^= TOGGLE_MASK; \
991 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000992
993
reed@google.comdd5bd672011-09-20 15:56:13 +0000994void Linear_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995 SkASSERT(count > 0);
996
997 SkPoint srcPt;
998 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
999 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001000 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@google.com5eb158d2011-04-15 15:50:34 +00001002 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001003
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001004 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001005 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1006 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1008
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001009 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010 SkFixed dxStorage[1];
1011 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1012 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001013 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1015 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1016 }
1017
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001018 if (SkFixedNearlyZero(dx)) {
1019 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +00001020 unsigned fi = proc(fx) >> kCache16Shift;
1021 SkASSERT(fi <= kCache16Mask);
reed@google.com5eb158d2011-04-15 15:50:34 +00001022 dither_memset16(dstC, cache[toggle + fi],
1023 cache[(toggle ^ TOGGLE_MASK) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001024 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001025 SkClampRange range;
1026 range.init(fx, dx, count, 0, kCache16Mask);
1027
1028 if ((count = range.fCount0) > 0) {
1029 dither_memset16(dstC,
1030 cache[toggle + range.fV0],
1031 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
1032 count);
1033 dstC += count;
1034 }
1035 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +00001036 int unroll = count >> 3;
1037 fx = range.fFx1;
1038 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001039 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1040 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1041 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1042 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1043 }
1044 if ((count &= 7) > 0) {
1045 do {
1046 NO_CHECK_ITER_16;
1047 } while (--count != 0);
1048 }
1049 }
1050 if ((count = range.fCount2) > 0) {
1051 dither_memset16(dstC,
1052 cache[toggle + range.fV1],
1053 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
1054 count);
1055 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001056 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001057 do {
reed@android.com200645d2009-12-14 16:41:57 +00001058 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001059 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001062 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001064 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 SkASSERT(proc == repeat_tileproc);
1066 do {
reed@android.com200645d2009-12-14 16:41:57 +00001067 unsigned fi = repeat_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);
1073 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001074 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 SkScalar dstX = SkIntToScalar(x);
1076 SkScalar dstY = SkIntToScalar(y);
1077 do {
1078 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1079 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1080 SkASSERT(fi <= 0xFFFF);
1081
reed@android.com512a8762009-12-14 15:25:36 +00001082 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001083 *dstC++ = cache[toggle + index];
reed@google.com5eb158d2011-04-15 15:50:34 +00001084 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085
1086 dstX += SK_Scalar1;
1087 } while (--count != 0);
1088 }
1089}
1090
1091///////////////////////////////////////////////////////////////////////////////
1092
1093#define kSQRT_TABLE_BITS 11
1094#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1095
1096#include "SkRadialGradient_Table.h"
1097
1098#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1099
1100#include <stdio.h>
1101
reed@google.com61eb0402011-04-15 12:11:12 +00001102void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001103 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1104
1105 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1106 SkASSERT(file);
1107 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1108
reed@google.com61eb0402011-04-15 12:11:12 +00001109 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1110 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001112 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113
1114 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1115
1116 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001117 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001119 }
1120 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001121 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001122 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123 }
1124 ::fprintf(file, "};\n");
1125 ::fclose(file);
1126}
1127
1128#endif
1129
1130
reed@google.com61eb0402011-04-15 12:11:12 +00001131static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1132 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001133 SkScalar inv = SkScalarInvert(radius);
1134
1135 matrix->setTranslate(-center.fX, -center.fY);
1136 matrix->postScale(inv, inv);
1137}
1138
1139class Radial_Gradient : public Gradient_Shader {
1140public:
1141 Radial_Gradient(const SkPoint& center, SkScalar radius,
1142 const SkColor colors[], const SkScalar pos[], int colorCount,
1143 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001144 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1145 fCenter(center),
1146 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001147 {
1148 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1149 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1150
1151 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1152 }
reed@google.com61eb0402011-04-15 12:11:12 +00001153
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001154 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count);
reed@google.comdd5bd672011-09-20 15:56:13 +00001155 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001156 SkASSERT(count > 0);
1157
1158 SkPoint srcPt;
1159 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1160 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001161 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001163
reed@android.com3c9b2a42009-08-27 19:28:37 +00001164 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001165 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1166 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001167 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1168 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1169
reed@android.com3c9b2a42009-08-27 19:28:37 +00001170 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001171 SkFixed storage[2];
1172 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1173 dx = storage[0];
1174 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001175 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1177 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1178 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1179 }
1180
reed@android.com3c9b2a42009-08-27 19:28:37 +00001181 if (proc == clamp_tileproc) {
reed@google.comdd5bd672011-09-20 15:56:13 +00001182 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001183
1184 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1185 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1186 precision, but that appears to be visually OK. If we decide this is OK for
1187 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1188 to avoid having to do these extra shifts each time.
1189 */
1190 fx >>= 1;
1191 dx >>= 1;
1192 fy >>= 1;
1193 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001194 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 +00001195 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1196 fy *= fy;
1197 do {
1198 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1199 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1200 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1201 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1203 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001205 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206 do {
1207 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1208 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1209 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1210 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1211 fx += dx;
1212 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1214 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215 } while (--count != 0);
1216 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001217 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001218 do {
1219 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1220 unsigned fi = mirror_tileproc(dist);
1221 SkASSERT(fi <= 0xFFFF);
1222 fx += dx;
1223 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1225 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001226 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001227 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228 SkASSERT(proc == repeat_tileproc);
1229 do {
1230 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1231 unsigned fi = repeat_tileproc(dist);
1232 SkASSERT(fi <= 0xFFFF);
1233 fx += dx;
1234 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1236 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237 } while (--count != 0);
1238 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001239 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 SkScalar dstX = SkIntToScalar(x);
1241 SkScalar dstY = SkIntToScalar(y);
1242 do {
1243 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1244 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1245 SkASSERT(fi <= 0xFFFF);
1246
1247 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 *dstC++ = cache[toggle + index];
1249 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250
1251 dstX += SK_Scalar1;
1252 } while (--count != 0);
1253 }
1254 }
1255
reed@google.com55b8e8c2011-01-13 16:22:35 +00001256 virtual BitmapType asABitmap(SkBitmap* bitmap,
1257 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001258 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001259 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001260 if (bitmap) {
1261 this->commonAsABitmap(bitmap);
1262 }
1263 if (matrix) {
1264 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1265 matrix->preConcat(fPtsToUnit);
1266 }
1267 if (xy) {
1268 xy[0] = fTileMode;
1269 xy[1] = kClamp_TileMode;
1270 }
1271 return kRadial_BitmapType;
1272 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001273 virtual GradientType asAGradient(GradientInfo* info) const {
1274 if (info) {
1275 commonAsAGradient(info);
1276 info->fPoint[0] = fCenter;
1277 info->fRadius[0] = fRadius;
1278 }
1279 return kRadial_GradientType;
1280 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001281
1282 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 return SkNEW_ARGS(Radial_Gradient, (buffer));
1284 }
1285
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001286 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1287 this->INHERITED::flatten(buffer);
1288 buffer.writeScalar(fCenter.fX);
1289 buffer.writeScalar(fCenter.fY);
1290 buffer.writeScalar(fRadius);
1291 }
1292
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001294 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1295 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001296 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001297 fRadius(buffer.readScalar()) {
1298 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 virtual Factory getFactory() { return CreateProc; }
1300
1301private:
1302 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001303 const SkPoint fCenter;
1304 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305};
1306
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001307static inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
1308 // fast, overly-conservative test: checks unit square instead
1309 // of unit circle
1310 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1311 (fx <= -SK_FixedHalf && dx <= 0);
1312 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1313 (fy <= -SK_FixedHalf && dy <= 0);
1314
1315 return xClamped || yClamped;
1316}
1317
1318// Return true if (fx * fy) is always inside the unit circle
1319// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1320// so it shouldn't be run if count is small.
1321static inline bool no_need_for_radial_pin(int fx, int dx,
1322 int fy, int dy, int count) {
1323 SkASSERT(count > 0);
1324 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1325 return false;
1326 }
1327 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1328 return false;
1329 }
1330 fx += (count - 1) * dx;
1331 fy += (count - 1) * dy;
1332 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1333 return false;
1334 }
1335 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1336}
1337
1338#define UNPINNED_RADIAL_STEP \
1339 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
1340 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)]; \
1341 fx += dx; \
1342 fy += dy;
1343
1344// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
1345static void radial_clamp(SkFixed fx, SkFixed fy, SkFixed dx, SkFixed dy,
1346 SkPMColor* dstC, int count, const SkPMColor* cache,
1347 const int kCache32Bits, const int kCache32Count) {
1348 // Floating point seems to be slower than fixed point,
1349 // even when we have float hardware.
1350 const uint8_t* sqrt_table = gSqrt8Table;
1351 fx >>= 1;
1352 dx >>= 1;
1353 fy >>= 1;
1354 dy >>= 1;
1355 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
1356 sk_memset32(dstC, cache[kCache32Count - 1], count);
1357 } else if ((count > 4) &&
1358 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1359 unsigned fi;
1360 // 4x unroll appears to be no faster than 2x unroll on Linux
1361 while (count > 1) {
1362 UNPINNED_RADIAL_STEP;
1363 UNPINNED_RADIAL_STEP;
1364 count -= 2;
1365 }
1366 if (count) {
1367 UNPINNED_RADIAL_STEP;
1368 }
1369 }
1370 else {
1371 do {
1372 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1373 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1374 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1375 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1376 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1377 fx += dx;
1378 fy += dy;
1379 } while (--count != 0);
1380 }
1381}
1382
1383void Radial_Gradient::shadeSpan(int x, int y,
1384 SkPMColor* SK_RESTRICT dstC, int count) {
1385 SkASSERT(count > 0);
1386
1387 SkPoint srcPt;
1388 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1389 TileProc proc = fTileProc;
1390 const SkPMColor* cache = this->getCache32();
1391
1392 if (fDstToIndexClass != kPerspective_MatrixClass) {
1393 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1394 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1395 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1396 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1397#ifdef SK_USE_FLOAT_SQRT
1398 float fdx, fdy;
1399#endif
1400
1401 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1402 SkFixed storage[2];
1403 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1404 dx = storage[0];
1405 dy = storage[1];
1406#ifdef SK_USE_FLOAT_SQRT
1407 fdx = SkFixedToFloat(storage[0]);
1408 fdy = SkFixedToFloat(storage[1]);
1409#endif
1410 } else {
1411 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1412 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1413 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1414#ifdef SK_USE_FLOAT_SQRT
1415 fdx = fDstToIndex.getScaleX();
1416 fdy = fDstToIndex.getSkewY();
1417#endif
1418 }
1419
1420 if (proc == clamp_tileproc) {
1421 radial_clamp(fx, fy, dx, dy, dstC, count, cache,
1422 kCache32Bits, kCache32Count);
1423 } else if (proc == mirror_tileproc) {
1424#ifdef SK_USE_FLOAT_SQRT
1425 float ffx = srcPt.fX;
1426 float ffy = srcPt.fY;
1427 do {
1428 float fdist = sk_float_sqrt(ffx*ffx + ffy*ffy);
1429 unsigned fi = mirror_tileproc(SkFloatToFixed(fdist));
1430 SkASSERT(fi <= 0xFFFF);
1431 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1432 ffx += fdx;
1433 ffy += fdy;
1434 } while (--count != 0);
1435#else
1436 do {
1437 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1438 SkFixedSquare(fy);
1439 if (magnitudeSquared < 0) // Overflow.
1440 magnitudeSquared = SK_FixedMax;
1441 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1442 unsigned fi = mirror_tileproc(dist);
1443 SkASSERT(fi <= 0xFFFF);
1444 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1445 fx += dx;
1446 fy += dy;
1447 } while (--count != 0);
1448#endif
1449 } else {
1450 SkASSERT(proc == repeat_tileproc);
1451 do {
1452 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1453 SkFixedSquare(fy);
1454 if (magnitudeSquared < 0) // Overflow.
1455 magnitudeSquared = SK_FixedMax;
1456 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1457 unsigned fi = repeat_tileproc(dist);
1458 SkASSERT(fi <= 0xFFFF);
1459 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1460 fx += dx;
1461 fy += dy;
1462 } while (--count != 0);
1463 }
1464 } else { // perspective case
1465 SkScalar dstX = SkIntToScalar(x);
1466 SkScalar dstY = SkIntToScalar(y);
1467 do {
1468 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1469 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1470 SkASSERT(fi <= 0xFFFF);
1471 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1472 dstX += SK_Scalar1;
1473 } while (--count != 0);
1474 }
1475}
1476
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001477/* Two-point radial gradients are specified by two circles, each with a center
1478 point and radius. The gradient can be considered to be a series of
1479 concentric circles, with the color interpolated from the start circle
1480 (at t=0) to the end circle (at t=1).
1481
1482 For each point (x, y) in the span, we want to find the
1483 interpolated circle that intersects that point. The center
1484 of the desired circle (Cx, Cy) falls at some distance t
1485 along the line segment between the start point (Sx, Sy) and
1486 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001487
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001488 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1489 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001490
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001491 The radius of the desired circle (r) is also a linear interpolation t
1492 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001493
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001494 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001495
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001496 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001497
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001498 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001499
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001500 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001501
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001502 (x - ((1 - t) * Sx + t * Ex))^2
1503 + (y - ((1 - t) * Sy + t * Ey))^2
1504 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001505
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001506 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001507
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001508 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1509 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1510 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001511
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001512 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1513
1514 [Dx^2 + Dy^2 - Dr^2)] * t^2
1515 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1516 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001517
1518 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001519 possible circles on which the point may fall. Solving for t yields
1520 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001521
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001522 If a<0, the start circle is entirely contained in the
1523 end circle, and one of the roots will be <0 or >1 (off the line
1524 segment). If a>0, the start circle falls at least partially
1525 outside the end circle (or vice versa), and the gradient
1526 defines a "tube" where a point may be on one circle (on the
1527 inside of the tube) or the other (outside of the tube). We choose
1528 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001529
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001530 In order to keep the math to within the limits of fixed point,
1531 we divide the entire quadratic by Dr^2, and replace
1532 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001533
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001534 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1535 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1536 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001537
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001538 (x' and y' are computed by appending the subtract and scale to the
1539 fDstToIndex matrix in the constructor).
1540
1541 Since the 'A' component of the quadratic is independent of x' and y', it
1542 is precomputed in the constructor. Since the 'B' component is linear in
1543 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001544 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001545 a perspective projection), it must be computed in the loop.
1546
1547*/
1548
reed@google.com84e9c082011-04-13 17:44:24 +00001549static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1550 SkScalar sr2d2, SkScalar foura,
1551 SkScalar oneOverTwoA, bool posRoot) {
1552 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001553 if (0 == foura) {
1554 return SkScalarToFixed(SkScalarDiv(-c, b));
1555 }
1556
reed@google.com84e9c082011-04-13 17:44:24 +00001557 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001558 if (discrim < 0) {
1559 discrim = -discrim;
1560 }
reed@google.com84e9c082011-04-13 17:44:24 +00001561 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1562 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001563 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001564 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001565 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001566 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001567 }
reed@google.com84e9c082011-04-13 17:44:24 +00001568 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001569}
1570
1571class Two_Point_Radial_Gradient : public Gradient_Shader {
1572public:
1573 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1574 const SkPoint& end, SkScalar endRadius,
1575 const SkColor colors[], const SkScalar pos[],
1576 int colorCount, SkShader::TileMode mode,
1577 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001578 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1579 fCenter1(start),
1580 fCenter2(end),
1581 fRadius1(startRadius),
1582 fRadius2(endRadius) {
1583 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001584 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001585
1586 virtual BitmapType asABitmap(SkBitmap* bitmap,
1587 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001588 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001589 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001590 if (bitmap) {
1591 this->commonAsABitmap(bitmap);
1592 }
1593 SkScalar diffL = 0; // just to avoid gcc warning
1594 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001595 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001596 SkScalarSquare(fDiff.fY));
1597 }
1598 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001599 if (diffL) {
1600 SkScalar invDiffL = SkScalarInvert(diffL);
1601 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1602 SkScalarMul(invDiffL, fDiff.fX));
1603 } else {
1604 matrix->reset();
1605 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001606 matrix->preConcat(fPtsToUnit);
1607 }
1608 if (xy) {
1609 xy[0] = fTileMode;
1610 xy[1] = kClamp_TileMode;
1611 }
1612 if (NULL != twoPointRadialParams) {
1613 twoPointRadialParams[0] = diffL;
1614 twoPointRadialParams[1] = fStartRadius;
1615 twoPointRadialParams[2] = fDiffRadius;
1616 }
1617 return kTwoPointRadial_BitmapType;
1618 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001619
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001620 virtual GradientType asAGradient(GradientInfo* info) const {
1621 if (info) {
1622 commonAsAGradient(info);
1623 info->fPoint[0] = fCenter1;
1624 info->fPoint[1] = fCenter2;
1625 info->fRadius[0] = fRadius1;
1626 info->fRadius[1] = fRadius2;
1627 }
1628 return kRadial2_GradientType;
1629 }
1630
reed@google.comdd5bd672011-09-20 15:56:13 +00001631 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001632 SkASSERT(count > 0);
1633
1634 // Zero difference between radii: fill with transparent black.
1635 if (fDiffRadius == 0) {
1636 sk_bzero(dstC, count * sizeof(*dstC));
1637 return;
1638 }
1639 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1640 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001641 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001642
1643 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001644 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001645 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001646 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001647 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1648 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001649 SkScalar dx, fx = srcPt.fX;
1650 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001651
reed@google.com61eb0402011-04-15 12:11:12 +00001652 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001653 SkFixed fixedX, fixedY;
1654 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1655 dx = SkFixedToScalar(fixedX);
1656 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001657 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001658 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001659 dx = fDstToIndex.getScaleX();
1660 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001661 }
reed@google.com84e9c082011-04-13 17:44:24 +00001662 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1663 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1664 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1665 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001666 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001667 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001668 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001669 SkFixed index = SkClampMax(t, 0xFFFF);
1670 SkASSERT(index <= 0xFFFF);
1671 *dstC++ = cache[index >> (16 - kCache32Bits)];
1672 fx += dx;
1673 fy += dy;
1674 b += db;
1675 }
reed@google.com61eb0402011-04-15 12:11:12 +00001676 } else if (proc == mirror_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 = mirror_tileproc(t);
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 {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001687 SkASSERT(proc == repeat_tileproc);
1688 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001689 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001690 SkFixed index = repeat_tileproc(t);
1691 SkASSERT(index <= 0xFFFF);
1692 *dstC++ = cache[index >> (16 - kCache32Bits)];
1693 fx += dx;
1694 fy += dy;
1695 b += db;
1696 }
1697 }
reed@google.com61eb0402011-04-15 12:11:12 +00001698 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001699 SkScalar dstX = SkIntToScalar(x);
1700 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001701 for (; count > 0; --count) {
1702 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001703 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001704 SkScalar fx = srcPt.fX;
1705 SkScalar fy = srcPt.fY;
1706 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1707 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1708 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001709 SkFixed index = proc(t);
1710 SkASSERT(index <= 0xFFFF);
1711 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001712 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001713 }
1714 }
1715 }
1716
reed@android.com6c59a172009-09-22 20:24:05 +00001717 virtual bool setContext(const SkBitmap& device,
1718 const SkPaint& paint,
1719 const SkMatrix& matrix) {
1720 if (!this->INHERITED::setContext(device, paint, matrix)) {
1721 return false;
1722 }
1723
1724 // we don't have a span16 proc
1725 fFlags &= ~kHasSpan16_Flag;
1726 return true;
1727 }
1728
reed@google.com55b8e8c2011-01-13 16:22:35 +00001729 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001730 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1731 }
1732
reed@android.combcfc7332009-11-10 16:19:39 +00001733 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1734 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001735 buffer.writeScalar(fCenter1.fX);
1736 buffer.writeScalar(fCenter1.fY);
1737 buffer.writeScalar(fCenter2.fX);
1738 buffer.writeScalar(fCenter2.fY);
1739 buffer.writeScalar(fRadius1);
1740 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001741 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001742
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001743protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001744 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001745 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001746 fCenter1(unflatten_point(buffer)),
1747 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001748 fRadius1(buffer.readScalar()),
1749 fRadius2(buffer.readScalar()) {
1750 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001751 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001752 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001753
1754private:
1755 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001756 const SkPoint fCenter1;
1757 const SkPoint fCenter2;
1758 const SkScalar fRadius1;
1759 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001760 SkPoint fDiff;
1761 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001762
1763 void init() {
1764 fDiff = fCenter1 - fCenter2;
1765 fDiffRadius = fRadius2 - fRadius1;
1766 SkScalar inv = SkScalarInvert(fDiffRadius);
1767 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1768 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1769 fStartRadius = SkScalarMul(fRadius1, inv);
1770 fSr2D2 = SkScalarSquare(fStartRadius);
1771 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00001772 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001773
1774 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1775 fPtsToUnit.postScale(inv, inv);
1776 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001777};
1778
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779///////////////////////////////////////////////////////////////////////////////
1780
1781class Sweep_Gradient : public Gradient_Shader {
1782public:
1783 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1784 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001785 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1786 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 {
1788 fPtsToUnit.setTranslate(-cx, -cy);
1789 }
1790 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1791 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001792
1793 virtual BitmapType asABitmap(SkBitmap* bitmap,
1794 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001795 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001796 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001797 if (bitmap) {
1798 this->commonAsABitmap(bitmap);
1799 }
1800 if (matrix) {
1801 *matrix = fPtsToUnit;
1802 }
1803 if (xy) {
1804 xy[0] = fTileMode;
1805 xy[1] = kClamp_TileMode;
1806 }
1807 return kSweep_BitmapType;
1808 }
1809
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001810 virtual GradientType asAGradient(GradientInfo* info) const {
1811 if (info) {
1812 commonAsAGradient(info);
1813 info->fPoint[0] = fCenter;
1814 }
1815 return kSweep_GradientType;
1816 }
1817
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1819 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1820 }
1821
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001822 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1823 this->INHERITED::flatten(buffer);
1824 buffer.writeScalar(fCenter.fX);
1825 buffer.writeScalar(fCenter.fY);
1826 }
1827
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001829 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1830 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001831 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001832 }
1833
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834 virtual Factory getFactory() { return CreateProc; }
1835
1836private:
1837 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001838 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839};
1840
1841#ifdef COMPUTE_SWEEP_TABLE
1842#define PI 3.14159265
1843static bool gSweepTableReady;
1844static uint8_t gSweepTable[65];
1845
1846/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1847 We scale the results to [0..32]
1848*/
reed@google.com61eb0402011-04-15 12:11:12 +00001849static const uint8_t* build_sweep_table() {
1850 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 const int N = 65;
1852 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001853
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 for (int i = 0; i < N; i++)
1855 {
1856 double arg = i / DENOM;
1857 double v = atan(arg);
1858 int iv = (int)round(v * DENOM * 2 / PI);
1859// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1860 printf("%d, ", iv);
1861 gSweepTable[i] = iv;
1862 }
1863 gSweepTableReady = true;
1864 }
1865 return gSweepTable;
1866}
1867#else
1868static const uint8_t gSweepTable[] = {
1869 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1870 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1871 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1872 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1873 32
1874};
1875static const uint8_t* build_sweep_table() { return gSweepTable; }
1876#endif
1877
1878// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1879// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1880// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1881
1882//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001883static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884 SkASSERT(numer <= denom);
1885 SkASSERT(numer > 0);
1886 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001887
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888 int nbits = SkCLZ(numer);
1889 int dbits = SkCLZ(denom);
1890 int bits = 6 - nbits + dbits;
1891 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001892
reed@google.com61eb0402011-04-15 12:11:12 +00001893 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001895 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896
1897 denom <<= dbits - 1;
1898 numer <<= nbits - 1;
1899
1900 unsigned result = 0;
1901
1902 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001903 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001905 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001906 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001907 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001908
reed@android.com8a1c16f2008-12-17 15:59:43 +00001909 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001910 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001911 // make room for the rest of the answer bits
1912 result <<= bits;
1913 switch (bits) {
1914 case 6:
1915 if ((numer = (numer << 1) - denom) >= 0)
1916 result |= 32;
1917 else
1918 numer += denom;
1919 case 5:
1920 if ((numer = (numer << 1) - denom) >= 0)
1921 result |= 16;
1922 else
1923 numer += denom;
1924 case 4:
1925 if ((numer = (numer << 1) - denom) >= 0)
1926 result |= 8;
1927 else
1928 numer += denom;
1929 case 3:
1930 if ((numer = (numer << 1) - denom) >= 0)
1931 result |= 4;
1932 else
1933 numer += denom;
1934 case 2:
1935 if ((numer = (numer << 1) - denom) >= 0)
1936 result |= 2;
1937 else
1938 numer += denom;
1939 case 1:
1940 default: // not strictly need, but makes GCC make better ARM code
1941 if ((numer = (numer << 1) - denom) >= 0)
1942 result |= 1;
1943 else
1944 numer += denom;
1945 }
1946 }
1947 return result;
1948}
1949
1950// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001951static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952#ifdef SK_DEBUG
1953 {
1954 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001955 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956 gOnce = true;
1957 SkASSERT(div_64(55, 55) == 64);
1958 SkASSERT(div_64(128, 256) == 32);
1959 SkASSERT(div_64(2326528, 4685824) == 31);
1960 SkASSERT(div_64(753664, 5210112) == 9);
1961 SkASSERT(div_64(229376, 4882432) == 3);
1962 SkASSERT(div_64(2, 64) == 2);
1963 SkASSERT(div_64(1, 64) == 1);
1964 // test that we handle underflow correctly
1965 SkASSERT(div_64(12345, 0x54321234) == 0);
1966 }
1967 }
1968#endif
1969
1970 SkASSERT(y > 0 && x > 0);
1971 const uint8_t* table = build_sweep_table();
1972
1973 unsigned result;
1974 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00001975 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001976 // first part of the atan(v) = PI/2 - atan(1/v) identity
1977 // since our div_64 and table want v <= 1, where v = y/x
1978 SkTSwap<SkFixed>(x, y);
1979 }
1980
1981 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001982
reed@android.com8a1c16f2008-12-17 15:59:43 +00001983#ifdef SK_DEBUG
1984 {
1985 unsigned result2 = SkDivBits(y, x, 6);
1986 SkASSERT(result2 == result ||
1987 (result == 1 && result2 == 0));
1988 }
1989#endif
1990
1991 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1992 result = table[result];
1993
reed@google.com61eb0402011-04-15 12:11:12 +00001994 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001995 // complete the atan(v) = PI/2 - atan(1/v) identity
1996 result = 64 - result;
1997 // pin to 63
1998 result -= result >> 6;
1999 }
2000
2001 SkASSERT(result <= 63);
2002 return result;
2003}
2004
2005// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002006#ifdef SK_SCALAR_IS_FLOAT
2007static unsigned SkATan2_255(float y, float x) {
2008 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2009 static const float g255Over2PI = 40.584510488433314f;
2010
2011 float result = sk_float_atan2(y, x);
2012 if (result < 0) {
2013 result += 2 * SK_ScalarPI;
2014 }
2015 SkASSERT(result >= 0);
2016 // since our value is always >= 0, we can cast to int, which is faster than
2017 // calling floorf()
2018 int ir = (int)(result * g255Over2PI);
2019 SkASSERT(ir >= 0 && ir <= 255);
2020 return ir;
2021}
2022#else
reed@google.com61eb0402011-04-15 12:11:12 +00002023static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2024 if (x == 0) {
2025 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002027 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002028 return y < 0 ? 192 : 64;
2029 }
reed@google.com61eb0402011-04-15 12:11:12 +00002030 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002032 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002033
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 /* Find the right quadrant for x,y
2035 Since atan_0_90 only handles the first quadrant, we rotate x,y
2036 appropriately before calling it, and then add the right amount
2037 to account for the real quadrant.
2038 quadrant 0 : add 0 | x > 0 && y > 0
2039 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2040 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2041 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002042
reed@android.com8a1c16f2008-12-17 15:59:43 +00002043 map x<0 to (1 << 6)
2044 map y<0 to (3 << 6)
2045 add = map_x ^ map_y
2046 */
2047 int xsign = x >> 31;
2048 int ysign = y >> 31;
2049 int add = ((-xsign) ^ (ysign & 3)) << 6;
2050
2051#ifdef SK_DEBUG
2052 if (0 == add)
2053 SkASSERT(x > 0 && y > 0);
2054 else if (64 == add)
2055 SkASSERT(x < 0 && y > 0);
2056 else if (128 == add)
2057 SkASSERT(x < 0 && y < 0);
2058 else if (192 == add)
2059 SkASSERT(x > 0 && y < 0);
2060 else
2061 SkASSERT(!"bad value for add");
2062#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002063
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2065 where we need to rotate x,y by 90 or -90
2066 */
2067 x = (x ^ xsign) - xsign;
2068 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002069 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002071 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072
2073 unsigned result = add + atan_0_90(y, x);
2074 SkASSERT(result < 256);
2075 return result;
2076}
reed@google.com51baf5a2011-09-21 13:38:36 +00002077#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002078
reed@google.comdd5bd672011-09-20 15:56:13 +00002079void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002080 SkMatrix::MapXYProc proc = fDstToIndexProc;
2081 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002082 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002083 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002084
reed@google.com61eb0402011-04-15 12:11:12 +00002085 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002086 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2087 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002088 SkScalar dx, fx = srcPt.fX;
2089 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002090
reed@google.com61eb0402011-04-15 12:11:12 +00002091 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002092 SkFixed storage[2];
2093 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2094 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002095 dx = SkFixedToScalar(storage[0]);
2096 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002097 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002098 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002099 dx = matrix.getScaleX();
2100 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002101 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002102
reed@google.com61eb0402011-04-15 12:11:12 +00002103 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002104 *dstC++ = cache[SkATan2_255(fy, fx)];
2105 fx += dx;
2106 fy += dy;
2107 }
reed@google.com61eb0402011-04-15 12:11:12 +00002108 } else { // perspective case
2109 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002110 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002111 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2112 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002113 }
2114 }
2115}
2116
reed@google.comdd5bd672011-09-20 15:56:13 +00002117void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 SkMatrix::MapXYProc proc = fDstToIndexProc;
2119 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002120 const uint16_t* SK_RESTRICT cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121 int toggle = ((x ^ y) & 1) << kCache16Bits;
2122 SkPoint srcPt;
2123
reed@google.com61eb0402011-04-15 12:11:12 +00002124 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002125 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2126 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002127 SkScalar dx, fx = srcPt.fX;
2128 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002129
reed@google.com61eb0402011-04-15 12:11:12 +00002130 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 SkFixed storage[2];
2132 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2133 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002134 dx = SkFixedToScalar(storage[0]);
2135 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002136 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002138 dx = matrix.getScaleX();
2139 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002140 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002141
reed@google.com61eb0402011-04-15 12:11:12 +00002142 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2144 *dstC++ = cache[toggle + index];
2145 toggle ^= (1 << kCache16Bits);
2146 fx += dx;
2147 fy += dy;
2148 }
reed@google.com61eb0402011-04-15 12:11:12 +00002149 } else { // perspective case
2150 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2152 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002153
reed@google.com51baf5a2011-09-21 13:38:36 +00002154 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002155 index >>= (8 - kCache16Bits);
2156 *dstC++ = cache[toggle + index];
2157 toggle ^= (1 << kCache16Bits);
2158 }
2159 }
2160}
2161
reed@google.com61eb0402011-04-15 12:11:12 +00002162///////////////////////////////////////////////////////////////////////////////
2163///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002164
2165// assumes colors is SkColor* and pos is SkScalar*
2166#define EXPAND_1_COLOR(count) \
2167 SkColor tmp[2]; \
2168 do { \
2169 if (1 == count) { \
2170 tmp[0] = tmp[1] = colors[0]; \
2171 colors = tmp; \
2172 pos = NULL; \
2173 count = 2; \
2174 } \
2175 } while (0)
2176
reed@google.com61eb0402011-04-15 12:11:12 +00002177SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2178 const SkColor colors[],
2179 const SkScalar pos[], int colorCount,
2180 SkShader::TileMode mode,
2181 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002182 if (NULL == pts || NULL == colors || colorCount < 1) {
2183 return NULL;
2184 }
2185 EXPAND_1_COLOR(colorCount);
2186
reed@android.comab840b82009-07-01 17:00:03 +00002187 return SkNEW_ARGS(Linear_Gradient,
2188 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002189}
2190
reed@google.com61eb0402011-04-15 12:11:12 +00002191SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2192 const SkColor colors[],
2193 const SkScalar pos[], int colorCount,
2194 SkShader::TileMode mode,
2195 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002196 if (radius <= 0 || NULL == colors || colorCount < 1) {
2197 return NULL;
2198 }
2199 EXPAND_1_COLOR(colorCount);
2200
reed@android.comab840b82009-07-01 17:00:03 +00002201 return SkNEW_ARGS(Radial_Gradient,
2202 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002203}
2204
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002205SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2206 SkScalar startRadius,
2207 const SkPoint& end,
2208 SkScalar endRadius,
2209 const SkColor colors[],
2210 const SkScalar pos[],
2211 int colorCount,
2212 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002213 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002214 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2215 return NULL;
2216 }
2217 EXPAND_1_COLOR(colorCount);
2218
2219 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002220 (start, startRadius, end, endRadius, colors, pos,
2221 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002222}
2223
reed@android.com8a1c16f2008-12-17 15:59:43 +00002224SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2225 const SkColor colors[],
2226 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002227 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002228 if (NULL == colors || count < 1) {
2229 return NULL;
2230 }
2231 EXPAND_1_COLOR(count);
2232
2233 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2234}
2235
2236static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2237 Linear_Gradient::CreateProc);
2238
2239static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2240 Radial_Gradient::CreateProc);
2241
2242static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2243 Sweep_Gradient::CreateProc);
tomhudson@google.com938d6042011-04-27 13:57:03 +00002244
2245static SkFlattenable::Registrar
2246 gTwoPointRadialGradientReg("Two_Point_Radial_Gradient",
2247 Two_Point_Radial_Gradient::CreateProc);