blob: b86c4e2e8a70615fad76d965c879773c4272acd1 [file] [log] [blame]
reed@google.com0e734bd2011-12-08 17:24:44 +00001
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkGradientShader.h"
tomhudson@google.come8c984d2012-01-09 13:45:36 +000011#include "SkClampRange.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000013#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000014#include "SkUnitMapper.h"
15#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000016#include "SkTemplates.h"
17#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000018
reed@google.com0e734bd2011-12-08 17:24:44 +000019#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
20 #define USE_DITHER_32BIT_GRADIENT
21#endif
22
reed@google.com5eb158d2011-04-15 15:50:34 +000023static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
24 int count) {
25 if (count > 0) {
26 if (v0 == v1) {
27 sk_memset32(dst, v0, count);
28 } else {
29 int pairs = count >> 1;
30 for (int i = 0; i < pairs; i++) {
31 *dst++ = v0;
32 *dst++ = v1;
33 }
34 if (count & 1) {
35 *dst = v0;
36 }
37 }
38 }
39}
40
reed@google.comc98a0aa2012-02-02 19:33:08 +000041// Clamp
reed@android.com8a1c16f2008-12-17 15:59:43 +000042
reed@android.com41bccf52009-04-03 13:33:51 +000043static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000044 return SkClampMax(x, 0xFFFF);
45}
46
reed@google.comc98a0aa2012-02-02 19:33:08 +000047// Repeat
48
reed@android.com41bccf52009-04-03 13:33:51 +000049static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000050 return x & 0xFFFF;
51}
52
reed@google.comc98a0aa2012-02-02 19:33:08 +000053static inline int repeat_bits(int x, const int bits) {
54 return x & ((1 << bits) - 1);
55}
56
57static inline int repeat_8bits(int x) {
58 return x & 0xFF;
59}
60
61// Mirror
62
epoger@google.com5468c902012-02-02 20:41:45 +000063// Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly.
64// See http://code.google.com/p/skia/issues/detail?id=472
65#if defined(_MSC_VER) && (_MSC_VER >= 1600)
66#pragma optimize("", off)
67#endif
68
reed@android.com41bccf52009-04-03 13:33:51 +000069static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000070 int s = x << 15 >> 31;
71 return (x ^ s) & 0xFFFF;
72}
73
reed@android.com200645d2009-12-14 16:41:57 +000074static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000075#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000076 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000078 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000079#else
reed@android.com200645d2009-12-14 16:41:57 +000080 int s = x << (31 - bits) >> 31;
81 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082#endif
83}
84
reed@android.com41bccf52009-04-03 13:33:51 +000085static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000086#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000087 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +000089 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000090 return x & 255;
91#else
92 int s = x << 23 >> 31;
93 return (x ^ s) & 0xFF;
94#endif
95}
96
epoger@google.com5468c902012-02-02 20:41:45 +000097#if defined(_MSC_VER) && (_MSC_VER >= 1600)
98#pragma optimize("", on)
99#endif
100
reed@google.com61eb0402011-04-15 12:11:12 +0000101///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102
reed@google.comc98a0aa2012-02-02 19:33:08 +0000103typedef SkFixed (*TileProc)(SkFixed);
104
105static const TileProc gTileProcs[] = {
106 clamp_tileproc,
107 repeat_tileproc,
108 mirror_tileproc
109};
110
111///////////////////////////////////////////////////////////////////////////////
112///////////////////////////////////////////////////////////////////////////////
113
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114class Gradient_Shader : public SkShader {
115public:
116 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000117 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118 virtual ~Gradient_Shader();
119
120 // overrides
reed@google.com7716afb2011-12-07 15:17:50 +0000121 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
122 virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000123 virtual bool isOpaque() const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000124
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000125 enum {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000126 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
127 /// it, use a larger cache.
128 kCache16Bits = 8,
129 kGradient16Length = (1 << kCache16Bits),
130 /// Each cache gets 1 extra entry at the end so we don't have to
131 /// test for end-of-cache in lerps. This is also the value used
132 /// to stride *writes* into the dither cache; it must not be zero.
133 /// Total space for a cache is 2x kCache16Count entries: one
134 /// regular cache, one for dithering.
135 kCache16Count = kGradient16Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000136 kCache16Shift = 16 - kCache16Bits,
137 kSqrt16Shift = 8 - kCache16Bits,
138
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000139 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
140 /// it, use a larger cache.
141 kCache32Bits = 8,
142 kGradient32Length = (1 << kCache32Bits),
143 /// Each cache gets 1 extra entry at the end so we don't have to
144 /// test for end-of-cache in lerps. This is also the value used
145 /// to stride *writes* into the dither cache; it must not be zero.
146 /// Total space for a cache is 2x kCache32Count entries: one
147 /// regular cache, one for dithering.
148 kCache32Count = kGradient32Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000149 kCache32Shift = 16 - kCache32Bits,
150 kSqrt32Shift = 8 - kCache32Bits,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000151
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000152 /// This value is used to *read* the dither cache; it may be 0
153 /// if dithering is disabled.
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000154#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000155 kDitherStride32 = kCache32Count,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000156#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000157 kDitherStride32 = 0,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000158#endif
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000159 kDitherStride16 = kCache16Count,
160 kLerpRemainderMask32 = (1 << (16 - kCache32Bits)) - 1
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000161 };
162
163
reed@android.com8a1c16f2008-12-17 15:59:43 +0000164protected:
165 Gradient_Shader(SkFlattenableReadBuffer& );
djsollen@google.com54924242012-03-29 15:18:04 +0000166 virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
167
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168 SkUnitMapper* fMapper;
169 SkMatrix fPtsToUnit; // set by subclass
170 SkMatrix fDstToIndex;
171 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 TileMode fTileMode;
173 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000174 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175 uint8_t fDstToIndexClass;
176 uint8_t fFlags;
177
178 struct Rec {
179 SkFixed fPos; // 0...1
180 uint32_t fScale; // (1 << 24) / range
181 };
182 Rec* fRecs;
183
reed@google.com7c2f27d2011-03-07 19:29:00 +0000184 const uint16_t* getCache16() const;
185 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186
reed@google.com7c2f27d2011-03-07 19:29:00 +0000187 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000188 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000189
reed@android.com8a1c16f2008-12-17 15:59:43 +0000190private:
191 enum {
192 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
193
reed@android.com1c12abe2009-07-02 15:01:02 +0000194 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 };
196 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000197 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
198 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000199
reed@google.com7c2f27d2011-03-07 19:29:00 +0000200 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
201 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202
reed@google.com7c2f27d2011-03-07 19:29:00 +0000203 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
204 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000205 mutable unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
reed@android.com8a1c16f2008-12-17 15:59:43 +0000206
reed@android.com512a8762009-12-14 15:25:36 +0000207 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000208 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
209 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000210 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000211 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000212
reed@android.com8a1c16f2008-12-17 15:59:43 +0000213 typedef SkShader INHERITED;
214};
215
reed@android.com41bccf52009-04-03 13:33:51 +0000216Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
217 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000218 SkASSERT(colorCount > 1);
219
220 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
221
222 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000223 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000224
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
226 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
227 fTileMode = mode;
228 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000229
reed@android.com41bccf52009-04-03 13:33:51 +0000230 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000231 fCache32 = NULL;
232 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000233
reed@android.com41bccf52009-04-03 13:33:51 +0000234 /* Note: we let the caller skip the first and/or last position.
235 i.e. pos[0] = 0.3, pos[1] = 0.7
236 In these cases, we insert dummy entries to ensure that the final data
237 will be bracketed by [0, 1].
238 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
239
240 Thus colorCount (the caller's value, and fColorCount (our value) may
241 differ by up to 2. In the above example:
242 colorCount = 2
243 fColorCount = 4
244 */
245 fColorCount = colorCount;
246 // check if we need to add in dummy start and/or end position/colors
247 bool dummyFirst = false;
248 bool dummyLast = false;
249 if (pos) {
250 dummyFirst = pos[0] != 0;
251 dummyLast = pos[colorCount - 1] != SK_Scalar1;
252 fColorCount += dummyFirst + dummyLast;
253 }
254
255 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000256 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000257 fOrigColors = reinterpret_cast<SkColor*>(
258 sk_malloc_throw(size * fColorCount));
259 }
260 else {
261 fOrigColors = fStorage;
262 }
263
264 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265 {
reed@android.com41bccf52009-04-03 13:33:51 +0000266 SkColor* origColors = fOrigColors;
267 if (dummyFirst) {
268 *origColors++ = colors[0];
269 }
270 memcpy(origColors, colors, colorCount * sizeof(SkColor));
271 if (dummyLast) {
272 origColors += colorCount;
273 *origColors = colors[colorCount - 1];
274 }
275 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000276
reed@android.com1c12abe2009-07-02 15:01:02 +0000277 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000278 if (fColorCount > 2) {
279 Rec* recs = fRecs;
280 recs->fPos = 0;
281 // recs->fScale = 0; // unused;
282 recs += 1;
283 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000284 /* We need to convert the user's array of relative positions into
285 fixed-point positions and scale factors. We need these results
286 to be strictly monotonic (no two values equal or out of order).
287 Hence this complex loop that just jams a zero for the scale
288 value if it sees a segment out of order, and it assures that
289 we start at 0 and end at 1.0
290 */
291 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000292 int startIndex = dummyFirst ? 0 : 1;
293 int count = colorCount + dummyLast;
294 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 // force the last value to be 1.0
296 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000297 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000298 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000299 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000300 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 }
reed@android.com41bccf52009-04-03 13:33:51 +0000302 // pin curr withing range
303 if (curr < 0) {
304 curr = 0;
305 } else if (curr > SK_Fixed1) {
306 curr = SK_Fixed1;
307 }
308 recs->fPos = curr;
309 if (curr > prev) {
310 recs->fScale = (1 << 24) / (curr - prev);
311 } else {
312 recs->fScale = 0; // ignore this segment
313 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000314 // get ready for the next value
315 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000316 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317 }
reed@android.com41bccf52009-04-03 13:33:51 +0000318 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319 SkFixed dp = SK_Fixed1 / (colorCount - 1);
320 SkFixed p = dp;
321 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000322 for (int i = 1; i < colorCount; i++) {
323 recs->fPos = p;
324 recs->fScale = scale;
325 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 p += dp;
327 }
328 }
329 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000330 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331}
332
333Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000334 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335 fCacheAlpha = 256;
336
337 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
338
339 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000340 fCache32 = NULL;
341 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342
reed@android.com41bccf52009-04-03 13:33:51 +0000343 int colorCount = fColorCount = buffer.readU32();
344 if (colorCount > kColorStorageCount) {
345 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
346 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
347 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000348 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000349 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351
352 fTileMode = (TileMode)buffer.readU8();
353 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000354 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 if (colorCount > 2) {
356 Rec* recs = fRecs;
357 recs[0].fPos = 0;
358 for (int i = 1; i < colorCount; i++) {
359 recs[i].fPos = buffer.readS32();
360 recs[i].fScale = buffer.readU32();
361 }
362 }
djsollen@google.com2b2ede32012-04-12 13:24:04 +0000363 buffer.readMatrix(&fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000364 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365}
366
reed@android.com41bccf52009-04-03 13:33:51 +0000367Gradient_Shader::~Gradient_Shader() {
368 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000370 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000371 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000372 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000374 }
reed@google.com82065d62011-02-07 15:30:46 +0000375 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000376}
377
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000378void Gradient_Shader::initCommon() {
379 fFlags = 0;
380 unsigned colorAlpha = 0xFF;
381 for (int i = 0; i < fColorCount; i++) {
382 colorAlpha &= SkColorGetA(fOrigColors[i]);
383 }
384 fColorsAreOpaque = colorAlpha == 0xFF;
385}
386
djsollen@google.com54924242012-03-29 15:18:04 +0000387void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000388 this->INHERITED::flatten(buffer);
389 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000390 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
392 buffer.write8(fTileMode);
393 if (fColorCount > 2) {
394 Rec* recs = fRecs;
395 for (int i = 1; i < fColorCount; i++) {
396 buffer.write32(recs[i].fPos);
397 buffer.write32(recs[i].fScale);
398 }
399 }
djsollen@google.com2b2ede32012-04-12 13:24:04 +0000400 buffer.writeMatrix(fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000401}
402
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000403bool Gradient_Shader::isOpaque() const {
404 return fColorsAreOpaque;
405}
406
reed@android.com8a1c16f2008-12-17 15:59:43 +0000407bool Gradient_Shader::setContext(const SkBitmap& device,
408 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000409 const SkMatrix& matrix) {
410 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000412 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000413
414 const SkMatrix& inverse = this->getTotalInverse();
415
416 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
417 return false;
418 }
419
420 fDstToIndexProc = fDstToIndex.getMapXYProc();
421 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
422
423 // now convert our colors in to PMColors
424 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425
426 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000427 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428 fFlags |= kOpaqueAlpha_Flag;
429 }
430 // we can do span16 as long as our individual colors are opaque,
431 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000432 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433 fFlags |= kHasSpan16_Flag;
434 }
435
reed@google.com95eed982011-07-05 17:01:56 +0000436 this->setCacheAlpha(paintAlpha);
437 return true;
438}
439
440void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000441 // if the new alpha differs from the previous time we were called, inval our cache
442 // this will trigger the cache to be rebuilt.
443 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000444 if (fCacheAlpha != alpha) {
445 fCache16 = NULL; // inval the cache
446 fCache32 = NULL; // inval the cache
447 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000448 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000449 if (fCache32PixelRef) {
450 fCache32PixelRef->notifyPixelsChanged();
451 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000452 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000453}
454
reed@android.com8a1c16f2008-12-17 15:59:43 +0000455#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
456
reed@android.com41bccf52009-04-03 13:33:51 +0000457/** We take the original colors, not our premultiplied PMColors, since we can
458 build a 16bit table as long as the original colors are opaque, even if the
459 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000460*/
reed@android.com512a8762009-12-14 15:25:36 +0000461void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
462 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000463 SkASSERT(count > 1);
464 SkASSERT(SkColorGetA(c0) == 0xFF);
465 SkASSERT(SkColorGetA(c1) == 0xFF);
466
467 SkFixed r = SkColorGetR(c0);
468 SkFixed g = SkColorGetG(c0);
469 SkFixed b = SkColorGetB(c0);
470
471 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
472 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
473 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
474
475 r = SkIntToFixed(r) + 0x8000;
476 g = SkIntToFixed(g) + 0x8000;
477 b = SkIntToFixed(b) + 0x8000;
478
479 do {
480 unsigned rr = r >> 16;
481 unsigned gg = g >> 16;
482 unsigned bb = b >> 16;
483 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000484 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000485 cache += 1;
486 r += dr;
487 g += dg;
488 b += db;
489 } while (--count != 0);
490}
491
reed@google.com55b8e8c2011-01-13 16:22:35 +0000492/*
493 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
494 * semantics of how we 2x2 dither 32->16
495 */
496static inline U8CPU dither_fixed_to_8(SkFixed n) {
497 n >>= 8;
498 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
499}
500
501/*
502 * For dithering with premultiply, we want to ceiling the alpha component,
503 * to ensure that it is always >= any color component.
504 */
505static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
506 n >>= 8;
507 return ((n << 1) - (n | (n >> 8))) >> 8;
508}
509
510void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
511 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000512 SkASSERT(count > 1);
513
reed@android.com1c12abe2009-07-02 15:01:02 +0000514 // need to apply paintAlpha to our two endpoints
515 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
516 SkFixed da;
517 {
518 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
519 da = SkIntToFixed(tmp - a) / (count - 1);
520 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000521
reed@android.com1c12abe2009-07-02 15:01:02 +0000522 SkFixed r = SkColorGetR(c0);
523 SkFixed g = SkColorGetG(c0);
524 SkFixed b = SkColorGetB(c0);
525 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
526 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
527 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000528
529 a = SkIntToFixed(a) + 0x8000;
530 r = SkIntToFixed(r) + 0x8000;
531 g = SkIntToFixed(g) + 0x8000;
532 b = SkIntToFixed(b) + 0x8000;
533
534 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000535 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000536 cache[kCache32Count] =
537 SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
538 dither_fixed_to_8(r),
539 dither_fixed_to_8(g),
540 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000541 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000542 a += da;
543 r += dr;
544 g += dg;
545 b += db;
546 } while (--count != 0);
547}
548
reed@android.com41bccf52009-04-03 13:33:51 +0000549static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000550 SkASSERT((unsigned)x <= SK_Fixed1);
551 return x - (x >> 16);
552}
553
reed@android.com200645d2009-12-14 16:41:57 +0000554static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000555 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000556 if (6 == bits) {
557 return (x << 10) | (x << 4) | (x >> 2);
558 }
559 if (8 == bits) {
560 return (x << 8) | x;
561 }
562 sk_throw();
563 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000564}
565
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000566/** We duplicate the last value in each half of the cache so that
567 interpolation doesn't have to special-case being at the last point.
568*/
569static void complete_16bit_cache(uint16_t* cache, int stride) {
570 cache[stride - 1] = cache[stride - 2];
571 cache[2 * stride - 1] = cache[2 * stride - 2];
572}
573
reed@google.com7c2f27d2011-03-07 19:29:00 +0000574const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000575 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000576 // double the count for dither entries
577 const int entryCount = kCache16Count * 2;
578 const size_t allocSize = sizeof(uint16_t) * entryCount;
579
reed@android.com3c9b2a42009-08-27 19:28:37 +0000580 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000581 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000582 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000583 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000584 if (fColorCount == 2) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000585 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
586 kGradient16Length);
reed@android.com41bccf52009-04-03 13:33:51 +0000587 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588 Rec* rec = fRecs;
589 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000590 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000591 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000592 SkASSERT(nextIndex < kCache16Count);
593
594 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000595 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 prevIndex = nextIndex;
597 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000598 // one extra space left over at the end for complete_16bit_cache()
599 SkASSERT(prevIndex == kGradient16Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000600 }
601
reed@android.com41bccf52009-04-03 13:33:51 +0000602 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000603 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000604 uint16_t* linear = fCache16; // just computed linear data
605 uint16_t* mapped = fCache16Storage; // storage for mapped data
606 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000607 for (int i = 0; i < kGradient16Length; i++) {
reed@android.com200645d2009-12-14 16:41:57 +0000608 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000609 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000610 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000611 }
612 sk_free(fCache16);
613 fCache16 = fCache16Storage;
614 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000615 complete_16bit_cache(fCache16, kCache16Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616 }
617 return fCache16;
618}
619
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000620/** We duplicate the last value in each half of the cache so that
621 interpolation doesn't have to special-case being at the last point.
622*/
623static void complete_32bit_cache(SkPMColor* cache, int stride) {
624 cache[stride - 1] = cache[stride - 2];
625 cache[2 * stride - 1] = cache[2 * stride - 2];
626}
627
reed@google.com7c2f27d2011-03-07 19:29:00 +0000628const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000629 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000630 // double the count for dither entries
631 const int entryCount = kCache32Count * 2;
632 const size_t allocSize = sizeof(SkPMColor) * entryCount;
633
reed@google.comdc731fd2010-12-23 15:19:47 +0000634 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000635 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
636 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000637 }
638 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000639 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000640 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000641 kGradient32Length, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000642 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643 Rec* rec = fRecs;
644 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000645 for (int i = 1; i < fColorCount; i++) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000646 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000647 SkASSERT(nextIndex < kGradient32Length);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648
649 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000650 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
651 fOrigColors[i],
652 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 prevIndex = nextIndex;
654 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000655 SkASSERT(prevIndex == kGradient32Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 }
657
reed@android.com41bccf52009-04-03 13:33:51 +0000658 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000659 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000660 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000662 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000664 for (int i = 0; i < kGradient32Length; i++) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000665 int index = map->mapUnit16((i << 8) | i) >> 8;
666 mapped[i] = linear[index];
667 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000668 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000669 fCache32PixelRef->unref();
670 fCache32PixelRef = newPR;
671 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000673 complete_32bit_cache(fCache32, kCache32Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674 }
675 return fCache32;
676}
677
reed@google.comdc731fd2010-12-23 15:19:47 +0000678/*
679 * Because our caller might rebuild the same (logically the same) gradient
680 * over and over, we'd like to return exactly the same "bitmap" if possible,
681 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
682 * To do that, we maintain a private cache of built-bitmaps, based on our
683 * colors and positions. Note: we don't try to flatten the fMapper, so if one
684 * is present, we skip the cache for now.
685 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000686void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000687 // our caller assumes no external alpha, so we ensure that our cache is
688 // built with 0xFF
689 this->setCacheAlpha(0xFF);
690
reed@google.comdc731fd2010-12-23 15:19:47 +0000691 // don't have a way to put the mapper into our cache-key yet
692 if (fMapper) {
693 // force our cahce32pixelref to be built
694 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000695 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000696 bitmap->setPixelRef(fCache32PixelRef);
697 return;
698 }
699
700 // build our key: [numColors + colors[] + {positions[]} ]
701 int count = 1 + fColorCount;
702 if (fColorCount > 2) {
703 count += fColorCount - 1; // fRecs[].fPos
704 }
705
706 SkAutoSTMalloc<16, int32_t> storage(count);
707 int32_t* buffer = storage.get();
708
709 *buffer++ = fColorCount;
710 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
711 buffer += fColorCount;
712 if (fColorCount > 2) {
713 for (int i = 1; i < fColorCount; i++) {
714 *buffer++ = fRecs[i].fPos;
715 }
716 }
717 SkASSERT(buffer - storage.get() == count);
718
719 ///////////////////////////////////
720
digit@google.com1771cbf2012-01-26 21:26:40 +0000721 SK_DECLARE_STATIC_MUTEX(gMutex);
reed@google.comdc731fd2010-12-23 15:19:47 +0000722 static SkBitmapCache* gCache;
723 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
724 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
725 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000726
reed@google.comdc731fd2010-12-23 15:19:47 +0000727 if (NULL == gCache) {
728 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
729 }
730 size_t size = count * sizeof(int32_t);
731
732 if (!gCache->find(storage.get(), size, bitmap)) {
733 // force our cahce32pixelref to be built
734 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000735 // Only expose the linear section of the cache; don't let the caller
736 // know about the padding at the end to make interpolation faster.
737 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000738 bitmap->setPixelRef(fCache32PixelRef);
739
740 gCache->add(storage.get(), size, *bitmap);
741 }
742}
743
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000744void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
745 if (info) {
746 if (info->fColorCount >= fColorCount) {
747 if (info->fColors) {
748 memcpy(info->fColors, fOrigColors,
749 fColorCount * sizeof(SkColor));
750 }
751 if (info->fColorOffsets) {
752 if (fColorCount == 2) {
753 info->fColorOffsets[0] = 0;
754 info->fColorOffsets[1] = SK_Scalar1;
755 } else if (fColorCount > 2) {
756 for (int i = 0; i < fColorCount; i++)
757 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
758 }
759 }
760 }
761 info->fColorCount = fColorCount;
762 info->fTileMode = fTileMode;
763 }
764}
765
reed@google.com61eb0402011-04-15 12:11:12 +0000766///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767
reed@android.com41bccf52009-04-03 13:33:51 +0000768static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769 SkVector vec = pts[1] - pts[0];
770 SkScalar mag = vec.length();
771 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
772
773 vec.scale(inv);
774 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
775 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
776 matrix->postScale(inv, inv);
777}
778
779///////////////////////////////////////////////////////////////////////////////
780
781class Linear_Gradient : public Gradient_Shader {
782public:
783 Linear_Gradient(const SkPoint pts[2],
784 const SkColor colors[], const SkScalar pos[], int colorCount,
785 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000786 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
787 fStart(pts[0]),
788 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 {
790 pts_to_unit_matrix(pts, &fPtsToUnit);
791 }
reed@android.com9b46e772009-06-05 12:24:41 +0000792
reed@google.com7716afb2011-12-07 15:17:50 +0000793 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
794 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
795 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
796 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
797 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
798 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799
djsollen@google.com54924242012-03-29 15:18:04 +0000800 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Linear_Gradient)
801
802protected:
803 Linear_Gradient(SkFlattenableReadBuffer& buffer)
804 : INHERITED(buffer),
djsollen@google.com2b2ede32012-04-12 13:24:04 +0000805 fStart(buffer.readPoint()),
806 fEnd(buffer.readPoint()) {
djsollen@google.com54924242012-03-29 15:18:04 +0000807 }
808 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000809 this->INHERITED::flatten(buffer);
djsollen@google.com2b2ede32012-04-12 13:24:04 +0000810 buffer.writePoint(fStart);
811 buffer.writePoint(fEnd);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000812 }
813
reed@android.com8a1c16f2008-12-17 15:59:43 +0000814private:
815 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000816 const SkPoint fStart;
817 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000818};
819
reed@android.com5119bdb2009-06-12 21:27:03 +0000820bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
821 const SkMatrix& matrix) {
822 if (!this->INHERITED::setContext(device, paint, matrix)) {
823 return false;
824 }
825
826 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
827 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000828 fFlags |= SkShader::kConstInY32_Flag;
829 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
830 // only claim this if we do have a 16bit mode (i.e. none of our
831 // colors have alpha), and if we are not dithering (which obviously
832 // is not const in Y).
833 fFlags |= SkShader::kConstInY16_Flag;
834 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000835 }
836 return true;
837}
838
reed@google.com5eb158d2011-04-15 15:50:34 +0000839#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000840 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000841 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000842 SkASSERT(fi <= 0xFF); \
843 fx += dx; \
844 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000845 toggle ^= Gradient_Shader::kDitherStride32; \
reed@google.com13659f12011-04-18 19:59:38 +0000846 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000847
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000848namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000849
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000850typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000851 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000852 int toggle, int count);
853
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000854// This function is deprecated, and will be replaced by
855// shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it.
856void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
857 SkPMColor* SK_RESTRICT dstC,
858 const SkPMColor* SK_RESTRICT cache,
859 int toggle, int count) {
860 // We're a vertical gradient, so no change in a span.
861 // If colors change sharply across the gradient, dithering is
862 // insufficient (it subsamples the color space) and we need to lerp.
863 unsigned fullIndex = proc(fx);
864 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
865 sk_memset32_dither(dstC,
866 cache[toggle + fi],
867 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
868 count);
869}
870
871// Linear interpolation (lerp) is unnecessary if there are no sharp
872// discontinuities in the gradient - which must be true if there are
873// only 2 colors - but it's cheap.
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000874void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
875 SkPMColor* SK_RESTRICT dstC,
876 const SkPMColor* SK_RESTRICT cache,
877 int toggle, int count) {
878 // We're a vertical gradient, so no change in a span.
879 // If colors change sharply across the gradient, dithering is
880 // insufficient (it subsamples the color space) and we need to lerp.
881 unsigned fullIndex = proc(fx);
882 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
883 unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32;
884 SkPMColor lerp =
885 SkFastFourByteInterp(
886 cache[toggle + fi + 1],
887 cache[toggle + fi], remainder);
888 SkPMColor dlerp =
889 SkFastFourByteInterp(
890 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1],
891 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], remainder);
892 sk_memset32_dither(dstC, lerp, dlerp, count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000893}
894
895void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
896 SkPMColor* SK_RESTRICT dstC,
897 const SkPMColor* SK_RESTRICT cache,
898 int toggle, int count) {
899 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000900 range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000901
902 if ((count = range.fCount0) > 0) {
903 sk_memset32_dither(dstC,
904 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000905 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000906 count);
907 dstC += count;
908 }
909 if ((count = range.fCount1) > 0) {
910 int unroll = count >> 3;
911 fx = range.fFx1;
912 for (int i = 0; i < unroll; i++) {
913 NO_CHECK_ITER; NO_CHECK_ITER;
914 NO_CHECK_ITER; NO_CHECK_ITER;
915 NO_CHECK_ITER; NO_CHECK_ITER;
916 NO_CHECK_ITER; NO_CHECK_ITER;
917 }
918 if ((count &= 7) > 0) {
919 do {
920 NO_CHECK_ITER;
921 } while (--count != 0);
922 }
923 }
924 if ((count = range.fCount2) > 0) {
925 sk_memset32_dither(dstC,
926 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000927 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000928 count);
929 }
930}
931
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000932void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
933 SkPMColor* SK_RESTRICT dstC,
934 const SkPMColor* SK_RESTRICT cache,
935 int toggle, int count) {
936 do {
937 unsigned fi = mirror_8bits(fx >> 8);
938 SkASSERT(fi <= 0xFF);
939 fx += dx;
940 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000941 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000942 } while (--count != 0);
943}
944
945void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
946 SkPMColor* SK_RESTRICT dstC,
947 const SkPMColor* SK_RESTRICT cache,
948 int toggle, int count) {
949 do {
950 unsigned fi = repeat_8bits(fx >> 8);
951 SkASSERT(fi <= 0xFF);
952 fx += dx;
953 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000954 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000955 } while (--count != 0);
956}
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000957
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000958}
959
960void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
961 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962 SkASSERT(count > 0);
963
964 SkPoint srcPt;
965 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
966 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +0000967 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +0000968#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000969 int toggle = ((x ^ y) & 1) * kDitherStride32;
reed@google.com0e734bd2011-12-08 17:24:44 +0000970#else
971 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +0000972#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000973
reed@android.comc552a432009-06-12 20:02:50 +0000974 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000975 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
976 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
978
reed@android.comc552a432009-06-12 20:02:50 +0000979 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980 SkFixed dxStorage[1];
981 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
982 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000983 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
985 dx = SkScalarToFixed(fDstToIndex.getScaleX());
986 }
987
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000988 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +0000989 if (SkFixedNearlyZero(dx)) {
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000990#ifdef SK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS
991 if (fColorCount > 2) {
992 shadeProc = shadeSpan_linear_vertical_lerp;
993 } else {
994 shadeProc = shadeSpan_linear_vertical;
995 }
996#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000997 shadeProc = shadeSpan_linear_vertical_lerp;
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000998#endif
reed@android.comc552a432009-06-12 20:02:50 +0000999 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001000 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +00001001 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001002 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +00001003 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001006 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +00001007 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008 SkScalar dstX = SkIntToScalar(x);
1009 SkScalar dstY = SkIntToScalar(y);
1010 do {
1011 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1012 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1013 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001014 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001015 toggle ^= Gradient_Shader::kDitherStride32;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001016 dstX += SK_Scalar1;
1017 } while (--count != 0);
1018 }
1019}
1020
reed@google.com55b8e8c2011-01-13 16:22:35 +00001021SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +00001022 SkMatrix* matrix,
1023 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +00001024 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001025 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001026 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001027 }
1028 if (matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001029 matrix->preConcat(fPtsToUnit);
1030 }
1031 if (xy) {
1032 xy[0] = fTileMode;
1033 xy[1] = kClamp_TileMode;
1034 }
rileya@google.com22e57f92012-07-19 15:16:19 +00001035 return kLinear_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001036}
1037
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001038SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1039 if (info) {
1040 commonAsAGradient(info);
1041 info->fPoint[0] = fStart;
1042 info->fPoint[1] = fEnd;
1043 }
1044 return kLinear_GradientType;
1045}
1046
reed@android.com3c9b2a42009-08-27 19:28:37 +00001047static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1048 int count) {
1049 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001050 *dst++ = value;
1051 count -= 1;
1052 SkTSwap(value, other);
1053 }
1054
1055 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001056
reed@android.com3c9b2a42009-08-27 19:28:37 +00001057 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001058 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001059 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061
reed@google.com5eb158d2011-04-15 15:50:34 +00001062#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001063 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001064 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001065 SkASSERT(fi < Gradient_Shader::kCache16Count); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001066 fx += dx; \
1067 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001068 toggle ^= Gradient_Shader::kDitherStride16; \
reed@google.com13659f12011-04-18 19:59:38 +00001069 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001070
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001071namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001072
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001073typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001074 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001075 int toggle, int count);
1076
1077void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1078 uint16_t* SK_RESTRICT dstC,
1079 const uint16_t* SK_RESTRICT cache,
1080 int toggle, int count) {
1081 // we're a vertical gradient, so no change in a span
1082 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001083 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001084 dither_memset16(dstC, cache[toggle + fi],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001085 cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001086
1087}
1088
1089void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1090 uint16_t* SK_RESTRICT dstC,
1091 const uint16_t* SK_RESTRICT cache,
1092 int toggle, int count) {
1093 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001094 range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001095
1096 if ((count = range.fCount0) > 0) {
1097 dither_memset16(dstC,
1098 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001099 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001100 count);
1101 dstC += count;
1102 }
1103 if ((count = range.fCount1) > 0) {
1104 int unroll = count >> 3;
1105 fx = range.fFx1;
1106 for (int i = 0; i < unroll; i++) {
1107 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1108 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1109 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1110 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1111 }
1112 if ((count &= 7) > 0) {
1113 do {
1114 NO_CHECK_ITER_16;
1115 } while (--count != 0);
1116 }
1117 }
1118 if ((count = range.fCount2) > 0) {
1119 dither_memset16(dstC,
1120 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001121 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001122 count);
1123 }
1124}
1125
1126void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1127 uint16_t* SK_RESTRICT dstC,
1128 const uint16_t* SK_RESTRICT cache,
1129 int toggle, int count) {
1130 do {
1131 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1132 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001133 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001134 fx += dx;
1135 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001136 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001137 } while (--count != 0);
1138}
1139
1140void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1141 uint16_t* SK_RESTRICT dstC,
1142 const uint16_t* SK_RESTRICT cache,
1143 int toggle, int count) {
1144 SkASSERT(proc == repeat_tileproc);
1145 do {
1146 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1147 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001148 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001149 fx += dx;
1150 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001151 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001152 } while (--count != 0);
1153}
1154}
1155
1156void Linear_Gradient::shadeSpan16(int x, int y,
1157 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001158 SkASSERT(count > 0);
1159
1160 SkPoint srcPt;
1161 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1162 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001163 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001164 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001165
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001166 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001167 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1168 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001169 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1170
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001171 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172 SkFixed dxStorage[1];
1173 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1174 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001175 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1177 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1178 }
1179
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001180 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001181 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001182 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001183 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001184 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001185 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001186 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001187 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001190 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001191 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001192 SkScalar dstX = SkIntToScalar(x);
1193 SkScalar dstY = SkIntToScalar(y);
1194 do {
1195 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1196 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1197 SkASSERT(fi <= 0xFFFF);
1198
reed@android.com512a8762009-12-14 15:25:36 +00001199 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001200 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001201 toggle ^= Gradient_Shader::kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202
1203 dstX += SK_Scalar1;
1204 } while (--count != 0);
1205 }
1206}
1207
1208///////////////////////////////////////////////////////////////////////////////
1209
1210#define kSQRT_TABLE_BITS 11
1211#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1212
1213#include "SkRadialGradient_Table.h"
1214
1215#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1216
1217#include <stdio.h>
1218
reed@google.com61eb0402011-04-15 12:11:12 +00001219void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1221
1222 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1223 SkASSERT(file);
1224 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1225
reed@google.com61eb0402011-04-15 12:11:12 +00001226 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1227 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001229 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001230
1231 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1232
1233 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001234 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001236 }
1237 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001239 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 }
1241 ::fprintf(file, "};\n");
1242 ::fclose(file);
1243}
1244
1245#endif
1246
1247
reed@google.com61eb0402011-04-15 12:11:12 +00001248static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1249 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001250 SkScalar inv = SkScalarInvert(radius);
1251
1252 matrix->setTranslate(-center.fX, -center.fY);
1253 matrix->postScale(inv, inv);
1254}
1255
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001256
1257namespace {
1258
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001259typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
1260 SkScalar sfy, SkScalar sdy,
1261 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001262 int toggle, int count);
1263
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001264void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
1265 SkScalar sfy, SkScalar sdy,
1266 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001267 int toggle, int count) {
1268 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1269
1270 /* knock these down so we can pin against +- 0x7FFF, which is an
1271 immediate load, rather than 0xFFFF which is slower. This is a
1272 compromise, since it reduces our precision, but that appears
1273 to be visually OK. If we decide this is OK for all of our cases,
1274 we could (it seems) put this scale-down into fDstToIndex,
1275 to avoid having to do these extra shifts each time.
1276 */
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001277 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1278 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1279 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1280 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001281 // might perform this check for the other modes,
1282 // but the win will be a smaller % of the total
1283 if (dy == 0) {
1284 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1285 fy *= fy;
1286 do {
1287 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1288 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1289 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1290 fx += dx;
1291 *dstC++ = cache[toggle +
1292 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001293 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001294 } while (--count != 0);
1295 } else {
1296 do {
1297 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1298 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1299 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1300 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1301 fx += dx;
1302 fy += dy;
1303 *dstC++ = cache[toggle +
1304 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001305 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001306 } while (--count != 0);
1307 }
1308}
1309
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001310void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
1311 SkScalar sfy, SkScalar sdy,
1312 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001313 int toggle, int count) {
1314 do {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001315#ifdef SK_SCALAR_IS_FLOAT
1316 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1317 SkFixed dist = SkFloatToFixed(fdist);
1318#else
1319 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1320 SkFixedSquare(sfy);
1321 if (magnitudeSquared < 0) // Overflow.
1322 magnitudeSquared = SK_FixedMax;
1323 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1324#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001325 unsigned fi = mirror_tileproc(dist);
1326 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001327 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001328 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001329 sfx += sdx;
1330 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001331 } while (--count != 0);
1332}
1333
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001334void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
1335 SkScalar sfy, SkScalar sdy,
1336 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001337 int toggle, int count) {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001338 SkFixed fx = SkScalarToFixed(sfx);
1339 SkFixed dx = SkScalarToFixed(sdx);
1340 SkFixed fy = SkScalarToFixed(sfy);
1341 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001342 do {
1343 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1344 unsigned fi = repeat_tileproc(dist);
1345 SkASSERT(fi <= 0xFFFF);
1346 fx += dx;
1347 fy += dy;
1348 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001349 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001350 } while (--count != 0);
1351}
1352
1353}
1354
reed@android.com8a1c16f2008-12-17 15:59:43 +00001355class Radial_Gradient : public Gradient_Shader {
1356public:
1357 Radial_Gradient(const SkPoint& center, SkScalar radius,
1358 const SkColor colors[], const SkScalar pos[], int colorCount,
1359 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001360 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1361 fCenter(center),
1362 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 {
1364 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1365 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1366
1367 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1368 }
reed@google.com61eb0402011-04-15 12:11:12 +00001369
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001370 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
1371 SK_OVERRIDE;
reed@google.com7096dc62012-05-11 18:01:50 +00001372 virtual void shadeSpan16(int x, int y, uint16_t* dstCParam,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001373 int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001374 SkASSERT(count > 0);
1375
reed@google.com7096dc62012-05-11 18:01:50 +00001376 uint16_t* SK_RESTRICT dstC = dstCParam;
1377
reed@android.com8a1c16f2008-12-17 15:59:43 +00001378 SkPoint srcPt;
1379 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1380 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001381 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001382 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001383
reed@android.com3c9b2a42009-08-27 19:28:37 +00001384 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001385 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1386 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001387
1388 SkScalar sdx = fDstToIndex.getScaleX();
1389 SkScalar sdy = fDstToIndex.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001390
reed@android.com3c9b2a42009-08-27 19:28:37 +00001391 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001392 SkFixed storage[2];
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001393 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1394 &storage[0], &storage[1]);
1395 sdx = SkFixedToScalar(storage[0]);
1396 sdy = SkFixedToScalar(storage[1]);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001397 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 }
1400
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001401 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001402 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001403 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001404 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001405 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001406 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001408 }
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001409 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
1410 cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001411 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412 SkScalar dstX = SkIntToScalar(x);
1413 SkScalar dstY = SkIntToScalar(y);
1414 do {
1415 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1416 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1417 SkASSERT(fi <= 0xFFFF);
1418
1419 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001420 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001421 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422
1423 dstX += SK_Scalar1;
1424 } while (--count != 0);
1425 }
1426 }
1427
reed@google.com55b8e8c2011-01-13 16:22:35 +00001428 virtual BitmapType asABitmap(SkBitmap* bitmap,
1429 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001430 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001431 SkScalar* twoPointRadialParams)
1432 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001433 if (bitmap) {
1434 this->commonAsABitmap(bitmap);
1435 }
1436 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001437 matrix->setScale(SkIntToScalar(kGradient32Length),
1438 SkIntToScalar(kGradient32Length));
reed@google.comdc731fd2010-12-23 15:19:47 +00001439 matrix->preConcat(fPtsToUnit);
1440 }
1441 if (xy) {
1442 xy[0] = fTileMode;
1443 xy[1] = kClamp_TileMode;
1444 }
1445 return kRadial_BitmapType;
1446 }
reed@google.com7716afb2011-12-07 15:17:50 +00001447 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001448 if (info) {
1449 commonAsAGradient(info);
1450 info->fPoint[0] = fCenter;
1451 info->fRadius[0] = fRadius;
1452 }
1453 return kRadial_GradientType;
1454 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001455
djsollen@google.comba28d032012-03-26 17:57:35 +00001456 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Radial_Gradient)
1457
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001459 Radial_Gradient(SkFlattenableReadBuffer& buffer)
djsollen@google.com54924242012-03-29 15:18:04 +00001460 : INHERITED(buffer),
djsollen@google.com2b2ede32012-04-12 13:24:04 +00001461 fCenter(buffer.readPoint()),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001462 fRadius(buffer.readScalar()) {
1463 }
djsollen@google.com54924242012-03-29 15:18:04 +00001464 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
1465 this->INHERITED::flatten(buffer);
djsollen@google.com2b2ede32012-04-12 13:24:04 +00001466 buffer.writePoint(fCenter);
djsollen@google.com54924242012-03-29 15:18:04 +00001467 buffer.writeScalar(fRadius);
1468 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469
1470private:
1471 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001472 const SkPoint fCenter;
1473 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474};
1475
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001476namespace {
1477
1478inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001479 // fast, overly-conservative test: checks unit square instead
1480 // of unit circle
1481 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1482 (fx <= -SK_FixedHalf && dx <= 0);
1483 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1484 (fy <= -SK_FixedHalf && dy <= 0);
1485
1486 return xClamped || yClamped;
1487}
1488
1489// Return true if (fx * fy) is always inside the unit circle
1490// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1491// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001492inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001493 int fy, int dy, int count) {
1494 SkASSERT(count > 0);
1495 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1496 return false;
1497 }
1498 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1499 return false;
1500 }
1501 fx += (count - 1) * dx;
1502 fy += (count - 1) * dy;
1503 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1504 return false;
1505 }
1506 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1507}
1508
1509#define UNPINNED_RADIAL_STEP \
1510 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001511 *dstC++ = cache[toggle + \
1512 (sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift)]; \
1513 toggle ^= Gradient_Shader::kDitherStride32; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001514 fx += dx; \
1515 fy += dy;
1516
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001517typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1518 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001519 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001520 int count, int toggle);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001521
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001522// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001523void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
1524 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001525 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001526 int count, int toggle) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001527 // Floating point seems to be slower than fixed point,
1528 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001529 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001530 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1531 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1532 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1533 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001534 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001535 unsigned fi = Gradient_Shader::kGradient32Length;
1536 sk_memset32_dither(dstC,
1537 cache[toggle + fi],
1538 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
1539 count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001540 } else if ((count > 4) &&
1541 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1542 unsigned fi;
1543 // 4x unroll appears to be no faster than 2x unroll on Linux
1544 while (count > 1) {
1545 UNPINNED_RADIAL_STEP;
1546 UNPINNED_RADIAL_STEP;
1547 count -= 2;
1548 }
1549 if (count) {
1550 UNPINNED_RADIAL_STEP;
1551 }
1552 }
1553 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001554 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1555 if (dy == 0) {
1556 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1557 yy *= yy;
1558 do {
1559 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1560 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1561 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001562 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1563 Gradient_Shader::kSqrt32Shift)];
1564 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001565 fx += dx;
1566 } while (--count != 0);
1567 } else {
1568 do {
1569 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1570 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1571 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1572 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001573 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1574 Gradient_Shader::kSqrt32Shift)];
1575 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001576 fx += dx;
1577 fy += dy;
1578 } while (--count != 0);
1579 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001580 }
1581}
1582
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001583// Unrolling this loop doesn't seem to help (when float); we're stalling to
1584// get the results of the sqrt (?), and don't have enough extra registers to
1585// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001586void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1587 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001588 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001589 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001590 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001591#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001592 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1593 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001594#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001595 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1596 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001597 if (magnitudeSquared < 0) // Overflow.
1598 magnitudeSquared = SK_FixedMax;
1599 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001600#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001601 unsigned fi = mirror_tileproc(dist);
1602 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001603 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1604 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001605 sfx += sdx;
1606 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001607 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001608}
1609
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001610void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1611 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001612 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001613 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001614 SkFixed fx = SkScalarToFixed(sfx);
1615 SkFixed dx = SkScalarToFixed(sdx);
1616 SkFixed fy = SkScalarToFixed(sfy);
1617 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001618 do {
1619 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1620 SkFixedSquare(fy);
1621 if (magnitudeSquared < 0) // Overflow.
1622 magnitudeSquared = SK_FixedMax;
1623 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1624 unsigned fi = repeat_tileproc(dist);
1625 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001626 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1627 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001628 fx += dx;
1629 fy += dy;
1630 } while (--count != 0);
1631}
1632}
1633
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001634void Radial_Gradient::shadeSpan(int x, int y,
1635 SkPMColor* SK_RESTRICT dstC, int count) {
1636 SkASSERT(count > 0);
1637
1638 SkPoint srcPt;
1639 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1640 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001641 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001642#ifdef USE_DITHER_32BIT_GRADIENT
1643 int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32;
1644#else
1645 int toggle = 0;
1646#endif
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001647
1648 if (fDstToIndexClass != kPerspective_MatrixClass) {
1649 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1650 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001651 SkScalar sdx = fDstToIndex.getScaleX();
1652 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001653
1654 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1655 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001656 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1657 &storage[0], &storage[1]);
1658 sdx = SkFixedToScalar(storage[0]);
1659 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001660 } else {
1661 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001662 }
1663
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001664 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001665 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001666 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001667 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001668 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001669 } else {
1670 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001671 }
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001672 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001673 } else { // perspective case
1674 SkScalar dstX = SkIntToScalar(x);
1675 SkScalar dstY = SkIntToScalar(y);
1676 do {
1677 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1678 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1679 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001680 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001681 dstX += SK_Scalar1;
1682 } while (--count != 0);
1683 }
1684}
1685
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001686/* Two-point radial gradients are specified by two circles, each with a center
1687 point and radius. The gradient can be considered to be a series of
1688 concentric circles, with the color interpolated from the start circle
1689 (at t=0) to the end circle (at t=1).
1690
1691 For each point (x, y) in the span, we want to find the
1692 interpolated circle that intersects that point. The center
1693 of the desired circle (Cx, Cy) falls at some distance t
1694 along the line segment between the start point (Sx, Sy) and
1695 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001696
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001697 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1698 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001699
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001700 The radius of the desired circle (r) is also a linear interpolation t
1701 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001702
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001703 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001704
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001705 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001706
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001707 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001708
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001709 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001710
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001711 (x - ((1 - t) * Sx + t * Ex))^2
1712 + (y - ((1 - t) * Sy + t * Ey))^2
1713 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001714
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001715 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001716
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001717 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1718 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1719 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001720
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001721 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1722
1723 [Dx^2 + Dy^2 - Dr^2)] * t^2
1724 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1725 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001726
1727 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001728 possible circles on which the point may fall. Solving for t yields
1729 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001730
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001731 If a<0, the start circle is entirely contained in the
1732 end circle, and one of the roots will be <0 or >1 (off the line
1733 segment). If a>0, the start circle falls at least partially
1734 outside the end circle (or vice versa), and the gradient
1735 defines a "tube" where a point may be on one circle (on the
1736 inside of the tube) or the other (outside of the tube). We choose
1737 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001738
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001739 In order to keep the math to within the limits of fixed point,
1740 we divide the entire quadratic by Dr^2, and replace
1741 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001742
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001743 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1744 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1745 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001746
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001747 (x' and y' are computed by appending the subtract and scale to the
1748 fDstToIndex matrix in the constructor).
1749
1750 Since the 'A' component of the quadratic is independent of x' and y', it
1751 is precomputed in the constructor. Since the 'B' component is linear in
1752 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001753 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001754 a perspective projection), it must be computed in the loop.
1755
1756*/
1757
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001758namespace {
1759
1760inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1761 SkScalar sr2d2, SkScalar foura,
1762 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001763 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001764 if (0 == foura) {
1765 return SkScalarToFixed(SkScalarDiv(-c, b));
1766 }
1767
reed@google.com84e9c082011-04-13 17:44:24 +00001768 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001769 if (discrim < 0) {
1770 discrim = -discrim;
1771 }
reed@google.com84e9c082011-04-13 17:44:24 +00001772 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1773 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001774 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001775 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001776 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001777 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001778 }
reed@google.com84e9c082011-04-13 17:44:24 +00001779 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001780}
1781
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001782typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1783 SkScalar fy, SkScalar dy,
1784 SkScalar b, SkScalar db,
1785 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001786 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001787 int count);
1788
1789void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1790 SkScalar fy, SkScalar dy,
1791 SkScalar b, SkScalar db,
1792 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001793 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001794 int count) {
1795 for (; count > 0; --count) {
1796 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1797 fOneOverTwoA, posRoot);
1798 SkFixed index = SkClampMax(t, 0xFFFF);
1799 SkASSERT(index <= 0xFFFF);
1800 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1801 fx += dx;
1802 fy += dy;
1803 b += db;
1804 }
1805}
1806void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1807 SkScalar fy, SkScalar dy,
1808 SkScalar b, SkScalar db,
1809 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001810 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001811 int count) {
1812 for (; count > 0; --count) {
1813 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1814 fOneOverTwoA, posRoot);
1815 SkFixed index = mirror_tileproc(t);
1816 SkASSERT(index <= 0xFFFF);
1817 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1818 fx += dx;
1819 fy += dy;
1820 b += db;
1821 }
1822}
1823
1824void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1825 SkScalar fy, SkScalar dy,
1826 SkScalar b, SkScalar db,
1827 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001828 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001829 int count) {
1830 for (; count > 0; --count) {
1831 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1832 fOneOverTwoA, posRoot);
1833 SkFixed index = repeat_tileproc(t);
1834 SkASSERT(index <= 0xFFFF);
1835 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1836 fx += dx;
1837 fy += dy;
1838 b += db;
1839 }
1840}
1841
1842
1843
1844}
1845
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001846class Two_Point_Radial_Gradient : public Gradient_Shader {
1847public:
1848 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1849 const SkPoint& end, SkScalar endRadius,
1850 const SkColor colors[], const SkScalar pos[],
1851 int colorCount, SkShader::TileMode mode,
1852 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001853 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1854 fCenter1(start),
1855 fCenter2(end),
1856 fRadius1(startRadius),
1857 fRadius2(endRadius) {
1858 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001859 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001860
1861 virtual BitmapType asABitmap(SkBitmap* bitmap,
1862 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001863 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001864 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001865 if (bitmap) {
1866 this->commonAsABitmap(bitmap);
1867 }
1868 SkScalar diffL = 0; // just to avoid gcc warning
1869 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001870 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001871 SkScalarSquare(fDiff.fY));
1872 }
1873 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001874 if (diffL) {
1875 SkScalar invDiffL = SkScalarInvert(diffL);
1876 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1877 SkScalarMul(invDiffL, fDiff.fX));
1878 } else {
1879 matrix->reset();
1880 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001881 matrix->preConcat(fPtsToUnit);
1882 }
1883 if (xy) {
1884 xy[0] = fTileMode;
1885 xy[1] = kClamp_TileMode;
1886 }
1887 if (NULL != twoPointRadialParams) {
1888 twoPointRadialParams[0] = diffL;
1889 twoPointRadialParams[1] = fStartRadius;
1890 twoPointRadialParams[2] = fDiffRadius;
1891 }
1892 return kTwoPointRadial_BitmapType;
1893 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001894
reed@google.com8e6d9142011-12-07 15:30:34 +00001895 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001896 if (info) {
1897 commonAsAGradient(info);
1898 info->fPoint[0] = fCenter1;
1899 info->fPoint[1] = fCenter2;
1900 info->fRadius[0] = fRadius1;
1901 info->fRadius[1] = fRadius2;
1902 }
1903 return kRadial2_GradientType;
1904 }
1905
reed@google.com7096dc62012-05-11 18:01:50 +00001906 virtual void shadeSpan(int x, int y, SkPMColor* dstCParam,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001907 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001908 SkASSERT(count > 0);
1909
reed@google.com7096dc62012-05-11 18:01:50 +00001910 SkPMColor* SK_RESTRICT dstC = dstCParam;
1911
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001912 // Zero difference between radii: fill with transparent black.
1913 if (fDiffRadius == 0) {
1914 sk_bzero(dstC, count * sizeof(*dstC));
1915 return;
1916 }
1917 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1918 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001919 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001920
1921 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001922 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001923 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001924 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001925 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1926 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001927 SkScalar dx, fx = srcPt.fX;
1928 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001929
reed@google.com61eb0402011-04-15 12:11:12 +00001930 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001931 SkFixed fixedX, fixedY;
1932 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1933 dx = SkFixedToScalar(fixedX);
1934 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001935 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001936 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001937 dx = fDstToIndex.getScaleX();
1938 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001939 }
reed@google.com84e9c082011-04-13 17:44:24 +00001940 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1941 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1942 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1943 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001944
1945 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00001946 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001947 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00001948 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001949 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00001950 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001951 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001952 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001953 (*shadeProc)(fx, dx, fy, dy, b, db,
1954 fSr2D2, foura, fOneOverTwoA, posRoot,
1955 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00001956 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001957 SkScalar dstX = SkIntToScalar(x);
1958 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001959 for (; count > 0; --count) {
1960 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001961 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001962 SkScalar fx = srcPt.fX;
1963 SkScalar fy = srcPt.fY;
1964 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1965 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001966 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1967 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001968 SkFixed index = proc(t);
1969 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001970 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00001971 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001972 }
1973 }
1974 }
1975
reed@android.com6c59a172009-09-22 20:24:05 +00001976 virtual bool setContext(const SkBitmap& device,
1977 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00001978 const SkMatrix& matrix) SK_OVERRIDE {
1979 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00001980 return false;
1981 }
1982
reed@google.com070a8002012-06-05 17:15:30 +00001983 // For now, we might have divided by zero, so detect that
1984 if (0 == fDiffRadius) {
1985 return false;
1986 }
1987
reed@android.com6c59a172009-09-22 20:24:05 +00001988 // we don't have a span16 proc
1989 fFlags &= ~kHasSpan16_Flag;
1990 return true;
1991 }
1992
djsollen@google.com54924242012-03-29 15:18:04 +00001993 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Two_Point_Radial_Gradient)
1994
1995protected:
1996 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
1997 : INHERITED(buffer),
djsollen@google.com2b2ede32012-04-12 13:24:04 +00001998 fCenter1(buffer.readPoint()),
1999 fCenter2(buffer.readPoint()),
djsollen@google.com54924242012-03-29 15:18:04 +00002000 fRadius1(buffer.readScalar()),
2001 fRadius2(buffer.readScalar()) {
2002 init();
2003 };
2004
2005 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00002006 this->INHERITED::flatten(buffer);
djsollen@google.com2b2ede32012-04-12 13:24:04 +00002007 buffer.writePoint(fCenter1);
2008 buffer.writePoint(fCenter2);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002009 buffer.writeScalar(fRadius1);
2010 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002011 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002012
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002013private:
2014 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002015 const SkPoint fCenter1;
2016 const SkPoint fCenter2;
2017 const SkScalar fRadius1;
2018 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002019 SkPoint fDiff;
2020 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002021
2022 void init() {
2023 fDiff = fCenter1 - fCenter2;
2024 fDiffRadius = fRadius2 - fRadius1;
reed@google.comb83f7972012-06-05 17:39:22 +00002025 // hack to avoid zero-divide for now
2026 SkScalar inv = fDiffRadius ? SkScalarInvert(fDiffRadius) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002027 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2028 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2029 fStartRadius = SkScalarMul(fRadius1, inv);
2030 fSr2D2 = SkScalarSquare(fStartRadius);
2031 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002032 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002033
2034 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2035 fPtsToUnit.postScale(inv, inv);
2036 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002037};
2038
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039///////////////////////////////////////////////////////////////////////////////
2040
reed@google.comcb7be692012-06-06 20:31:56 +00002041static int valid_divide(float numer, float denom, float* ratio) {
2042 SkASSERT(ratio);
2043 if (0 == denom) {
2044 return 0;
2045 }
2046 *ratio = numer / denom;
2047 return 1;
2048}
2049
2050// Return the number of distinct real roots, and write them into roots[] in
2051// ascending order
2052static int find_quad_roots(float A, float B, float C, float roots[2]) {
2053 SkASSERT(roots);
2054
2055 if (A == 0) {
2056 return valid_divide(-C, B, roots);
2057 }
2058
reed@google.comcb7be692012-06-06 20:31:56 +00002059 float R = B*B - 4*A*C;
reed@google.comaca29ae2012-06-07 14:20:52 +00002060 if (R < 0) {
reed@google.comcb7be692012-06-06 20:31:56 +00002061 return 0;
2062 }
2063 R = sk_float_sqrt(R);
reed@google.comaca29ae2012-06-07 14:20:52 +00002064
2065#if 1
2066 float Q = B;
2067 if (Q < 0) {
2068 Q -= R;
2069 } else {
2070 Q += R;
reed@google.comcb7be692012-06-06 20:31:56 +00002071 }
reed@google.comaca29ae2012-06-07 14:20:52 +00002072#else
2073 // on 10.6 this was much slower than the above branch :(
2074 float Q = B + copysignf(R, B);
2075#endif
2076 Q *= -0.5f;
2077 if (0 == Q) {
2078 roots[0] = 0;
2079 return 1;
2080 }
2081
2082 float r0 = Q / A;
2083 float r1 = C / Q;
2084 roots[0] = r0 < r1 ? r0 : r1;
2085 roots[1] = r0 > r1 ? r0 : r1;
2086 return 2;
reed@google.comcb7be692012-06-06 20:31:56 +00002087}
2088
2089static float lerp(float x, float dx, float t) {
2090 return x + t * dx;
2091}
2092
2093static float sqr(float x) { return x * x; }
2094
2095struct TwoPtRadial {
2096 enum {
2097 kDontDrawT = 0x80000000
2098 };
2099
2100 float fCenterX, fCenterY;
2101 float fDCenterX, fDCenterY;
2102 float fRadius;
2103 float fDRadius;
2104 float fA;
2105 float fRadius2;
2106 float fRDR;
2107
2108 void init(const SkPoint& center0, SkScalar rad0,
2109 const SkPoint& center1, SkScalar rad1) {
2110 fCenterX = SkScalarToFloat(center0.fX);
2111 fCenterY = SkScalarToFloat(center0.fY);
2112 fDCenterX = SkScalarToFloat(center1.fX) - fCenterX;
2113 fDCenterY = SkScalarToFloat(center1.fY) - fCenterY;
2114 fRadius = SkScalarToFloat(rad0);
2115 fDRadius = SkScalarToFloat(rad1) - fRadius;
2116
mike@reedtribe.org23113dd2012-06-09 02:03:40 +00002117 fA = sqr(fDCenterX) + sqr(fDCenterY) - sqr(fDRadius);
reed@google.comcb7be692012-06-06 20:31:56 +00002118 fRadius2 = sqr(fRadius);
2119 fRDR = fRadius * fDRadius;
2120 }
2121
2122 // used by setup and nextT
2123 float fRelX, fRelY, fIncX, fIncY;
2124 float fB, fDB;
2125
2126 void setup(SkScalar fx, SkScalar fy, SkScalar dfx, SkScalar dfy) {
2127 fRelX = SkScalarToFloat(fx) - fCenterX;
2128 fRelY = SkScalarToFloat(fy) - fCenterY;
2129 fIncX = SkScalarToFloat(dfx);
2130 fIncY = SkScalarToFloat(dfy);
2131 fB = -2 * (fDCenterX * fRelX + fDCenterY * fRelY + fRDR);
2132 fDB = -2 * (fDCenterX * fIncX + fDCenterY * fIncY);
2133 }
2134
2135 SkFixed nextT() {
2136 float roots[2];
2137
reed@google.comaca29ae2012-06-07 14:20:52 +00002138 float C = sqr(fRelX) + sqr(fRelY) - fRadius2;
reed@google.comcb7be692012-06-06 20:31:56 +00002139 int countRoots = find_quad_roots(fA, fB, C, roots);
2140
2141 fRelX += fIncX;
2142 fRelY += fIncY;
2143 fB += fDB;
2144
2145 if (0 == countRoots) {
2146 return kDontDrawT;
2147 }
2148
2149 // Prefer the bigger t value if both give a radius(t) > 0
2150 // find_quad_roots returns the values sorted, so we start with the last
2151 float t = roots[countRoots - 1];
2152 float r = lerp(fRadius, fDRadius, t);
2153 if (r <= 0) {
2154 t = roots[0]; // might be the same as roots[countRoots-1]
2155 r = lerp(fRadius, fDRadius, t);
2156 if (r <= 0) {
2157 return kDontDrawT;
2158 }
2159 }
2160 return SkFloatToFixed(t);
2161 }
2162
2163 static bool DontDrawT(SkFixed t) {
reed@google.comaca29ae2012-06-07 14:20:52 +00002164 return kDontDrawT == (uint32_t)t;
reed@google.comcb7be692012-06-06 20:31:56 +00002165 }
2166};
2167
2168typedef void (*TwoPointRadialProc)(TwoPtRadial* rec, SkPMColor* dstC,
2169 const SkPMColor* cache, int count);
2170
reed@google.comaca29ae2012-06-07 14:20:52 +00002171static void twopoint_clamp(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
2172 const SkPMColor* SK_RESTRICT cache, int count) {
reed@google.comcb7be692012-06-06 20:31:56 +00002173 for (; count > 0; --count) {
2174 SkFixed t = rec->nextT();
2175 if (TwoPtRadial::DontDrawT(t)) {
2176 *dstC++ = 0;
2177 } else {
2178 SkFixed index = SkClampMax(t, 0xFFFF);
2179 SkASSERT(index <= 0xFFFF);
2180 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
2181 }
2182 }
2183}
2184
reed@google.comaca29ae2012-06-07 14:20:52 +00002185static void twopoint_repeat(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
2186 const SkPMColor* SK_RESTRICT cache, int count) {
reed@google.comcb7be692012-06-06 20:31:56 +00002187 for (; count > 0; --count) {
2188 SkFixed t = rec->nextT();
2189 if (TwoPtRadial::DontDrawT(t)) {
2190 *dstC++ = 0;
2191 } else {
2192 SkFixed index = repeat_tileproc(t);
2193 SkASSERT(index <= 0xFFFF);
2194 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
2195 }
2196 }
2197}
2198
reed@google.comaca29ae2012-06-07 14:20:52 +00002199static void twopoint_mirror(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
2200 const SkPMColor* SK_RESTRICT cache, int count) {
reed@google.comcb7be692012-06-06 20:31:56 +00002201 for (; count > 0; --count) {
2202 SkFixed t = rec->nextT();
2203 if (TwoPtRadial::DontDrawT(t)) {
2204 *dstC++ = 0;
2205 } else {
2206 SkFixed index = mirror_tileproc(t);
2207 SkASSERT(index <= 0xFFFF);
2208 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
2209 }
2210 }
2211}
2212
2213class Two_Point_Conical_Gradient : public Gradient_Shader {
2214 TwoPtRadial fRec;
2215
2216 void init() {
2217 fRec.init(fCenter1, fRadius1, fCenter2, fRadius2);
2218 fPtsToUnit.reset();
2219 }
2220public:
2221 Two_Point_Conical_Gradient(const SkPoint& start, SkScalar startRadius,
2222 const SkPoint& end, SkScalar endRadius,
2223 const SkColor colors[], const SkScalar pos[],
2224 int colorCount, SkShader::TileMode mode,
2225 SkUnitMapper* mapper)
2226 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
2227 fCenter1(start),
2228 fCenter2(end),
2229 fRadius1(startRadius),
2230 fRadius2(endRadius) {
2231 // this is degenerate, and should be caught by our caller
2232 SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
2233 this->init();
2234 }
2235
2236 virtual void shadeSpan(int x, int y, SkPMColor* dstCParam,
2237 int count) SK_OVERRIDE {
2238 SkASSERT(count > 0);
2239
2240 SkPMColor* SK_RESTRICT dstC = dstCParam;
2241
2242 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
2243 TileProc proc = fTileProc;
2244 const SkPMColor* SK_RESTRICT cache = this->getCache32();
2245
2246 TwoPointRadialProc shadeProc = twopoint_repeat;
2247 if (proc == clamp_tileproc) {
2248 shadeProc = twopoint_clamp;
2249 } else if (proc == mirror_tileproc) {
2250 shadeProc = twopoint_mirror;
2251 } else {
2252 SkASSERT(proc == repeat_tileproc);
2253 }
2254
2255 if (fDstToIndexClass != kPerspective_MatrixClass) {
2256 SkPoint srcPt;
2257 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
2258 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2259 SkScalar dx, fx = srcPt.fX;
2260 SkScalar dy, fy = srcPt.fY;
2261
2262 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
2263 SkFixed fixedX, fixedY;
2264 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
2265 dx = SkFixedToScalar(fixedX);
2266 dy = SkFixedToScalar(fixedY);
2267 } else {
2268 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2269 dx = fDstToIndex.getScaleX();
2270 dy = fDstToIndex.getSkewY();
2271 }
2272
2273 fRec.setup(fx, fy, dx, dy);
2274 (*shadeProc)(&fRec, dstC, cache, count);
2275 } else { // perspective case
2276 SkScalar dstX = SkIntToScalar(x);
2277 SkScalar dstY = SkIntToScalar(y);
2278 for (; count > 0; --count) {
2279 SkPoint srcPt;
2280 dstProc(fDstToIndex, dstX, dstY, &srcPt);
2281 dstX += SK_Scalar1;
2282
2283 fRec.setup(srcPt.fX, srcPt.fY, 0, 0);
2284 (*shadeProc)(&fRec, dstC, cache, 1);
2285 }
2286 }
2287 }
2288
2289 virtual bool setContext(const SkBitmap& device,
2290 const SkPaint& paint,
2291 const SkMatrix& matrix) SK_OVERRIDE {
2292 if (!this->INHERITED::setContext(device, paint, matrix)) {
2293 return false;
2294 }
2295
2296 // we don't have a span16 proc
2297 fFlags &= ~kHasSpan16_Flag;
2298
2299 // in general, we might discard based on computed-radius, so clear
2300 // this flag (todo: sometimes we can detect that we never discard...)
2301 fFlags &= ~kOpaqueAlpha_Flag;
2302
2303 return true;
2304 }
reed@google.com83226972012-06-07 20:26:47 +00002305
rileya@google.com3e332582012-07-03 13:43:35 +00002306 virtual BitmapType asABitmap(SkBitmap* bitmap,
2307 SkMatrix* matrix,
2308 TileMode* xy,
2309 SkScalar* twoPointRadialParams) const {
2310
2311 SkPoint diff = fCenter2 - fCenter1;
2312 SkScalar diffRadius = fRadius2 - fRadius1;
2313 SkScalar startRadius = fRadius1;
2314 SkScalar diffLen = 0;
2315
2316 if (bitmap) {
2317 this->commonAsABitmap(bitmap);
2318 }
2319 if (matrix || twoPointRadialParams) {
2320 diffLen = diff.length();
2321 }
2322 if (matrix) {
2323 if (diffLen) {
2324 SkScalar invDiffLen = SkScalarInvert(diffLen);
2325 // rotate to align circle centers with the x-axis
2326 matrix->setSinCos(-SkScalarMul(invDiffLen, diff.fY),
2327 SkScalarMul(invDiffLen, diff.fX));
2328 } else {
2329 matrix->reset();
2330 }
2331 matrix->preTranslate(-fCenter1.fX, -fCenter1.fY);
2332 }
2333 if (xy) {
2334 xy[0] = fTileMode;
2335 xy[1] = kClamp_TileMode;
2336 }
2337 if (NULL != twoPointRadialParams) {
2338 twoPointRadialParams[0] = diffLen;
2339 twoPointRadialParams[1] = startRadius;
2340 twoPointRadialParams[2] = diffRadius;
2341 }
2342
2343 return kTwoPointConical_BitmapType;
2344 }
2345
2346
reed@google.com83226972012-06-07 20:26:47 +00002347 SkShader::GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
2348 if (info) {
2349 commonAsAGradient(info);
2350 info->fPoint[0] = fCenter1;
2351 info->fPoint[1] = fCenter2;
2352 info->fRadius[0] = fRadius1;
2353 info->fRadius[1] = fRadius2;
2354 }
2355 return kConical_GradientType;
2356 }
2357
reed@google.comcb7be692012-06-06 20:31:56 +00002358 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Two_Point_Conical_Gradient)
2359
2360protected:
2361 Two_Point_Conical_Gradient(SkFlattenableReadBuffer& buffer)
2362 : INHERITED(buffer),
2363 fCenter1(buffer.readPoint()),
2364 fCenter2(buffer.readPoint()),
2365 fRadius1(buffer.readScalar()),
2366 fRadius2(buffer.readScalar()) {
2367 this->init();
2368 };
2369
2370 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
2371 this->INHERITED::flatten(buffer);
2372 buffer.writePoint(fCenter1);
2373 buffer.writePoint(fCenter2);
2374 buffer.writeScalar(fRadius1);
2375 buffer.writeScalar(fRadius2);
2376 }
2377
2378private:
2379 typedef Gradient_Shader INHERITED;
2380 const SkPoint fCenter1;
2381 const SkPoint fCenter2;
2382 const SkScalar fRadius1;
2383 const SkScalar fRadius2;
2384};
2385
2386///////////////////////////////////////////////////////////////////////////////
2387
reed@android.com8a1c16f2008-12-17 15:59:43 +00002388class Sweep_Gradient : public Gradient_Shader {
2389public:
2390 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2391 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002392 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2393 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002394 {
2395 fPtsToUnit.setTranslate(-cx, -cy);
2396 }
reed@google.com7716afb2011-12-07 15:17:50 +00002397 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2398 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002399
2400 virtual BitmapType asABitmap(SkBitmap* bitmap,
2401 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002402 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002403 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002404 if (bitmap) {
2405 this->commonAsABitmap(bitmap);
2406 }
2407 if (matrix) {
2408 *matrix = fPtsToUnit;
2409 }
2410 if (xy) {
2411 xy[0] = fTileMode;
2412 xy[1] = kClamp_TileMode;
2413 }
2414 return kSweep_BitmapType;
2415 }
2416
reed@google.com7716afb2011-12-07 15:17:50 +00002417 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002418 if (info) {
2419 commonAsAGradient(info);
2420 info->fPoint[0] = fCenter;
2421 }
2422 return kSweep_GradientType;
2423 }
2424
djsollen@google.comba28d032012-03-26 17:57:35 +00002425 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Sweep_Gradient)
2426
reed@android.com8a1c16f2008-12-17 15:59:43 +00002427protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002428 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
djsollen@google.com54924242012-03-29 15:18:04 +00002429 : INHERITED(buffer),
djsollen@google.com2b2ede32012-04-12 13:24:04 +00002430 fCenter(buffer.readPoint()) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002431 }
djsollen@google.com54924242012-03-29 15:18:04 +00002432 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
2433 this->INHERITED::flatten(buffer);
djsollen@google.com2b2ede32012-04-12 13:24:04 +00002434 buffer.writePoint(fCenter);
djsollen@google.com54924242012-03-29 15:18:04 +00002435 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002436
reed@android.com8a1c16f2008-12-17 15:59:43 +00002437private:
2438 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002439 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002440};
2441
caryclark@google.com383d5d42012-06-06 12:09:18 +00002442#ifndef SK_SCALAR_IS_FLOAT
reed@android.com8a1c16f2008-12-17 15:59:43 +00002443#ifdef COMPUTE_SWEEP_TABLE
2444#define PI 3.14159265
2445static bool gSweepTableReady;
2446static uint8_t gSweepTable[65];
2447
2448/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2449 We scale the results to [0..32]
2450*/
reed@google.com61eb0402011-04-15 12:11:12 +00002451static const uint8_t* build_sweep_table() {
2452 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002453 const int N = 65;
2454 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002455
reed@android.com8a1c16f2008-12-17 15:59:43 +00002456 for (int i = 0; i < N; i++)
2457 {
2458 double arg = i / DENOM;
2459 double v = atan(arg);
2460 int iv = (int)round(v * DENOM * 2 / PI);
2461// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2462 printf("%d, ", iv);
2463 gSweepTable[i] = iv;
2464 }
2465 gSweepTableReady = true;
2466 }
2467 return gSweepTable;
2468}
2469#else
2470static const uint8_t gSweepTable[] = {
2471 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2472 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2473 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2474 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2475 32
2476};
2477static const uint8_t* build_sweep_table() { return gSweepTable; }
2478#endif
caryclark@google.com383d5d42012-06-06 12:09:18 +00002479#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002480
2481// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2482// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2483// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2484
2485//unsigned div_64(int numer, int denom);
caryclark@google.com383d5d42012-06-06 12:09:18 +00002486#ifndef SK_SCALAR_IS_FLOAT
reed@google.com61eb0402011-04-15 12:11:12 +00002487static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002488 SkASSERT(numer <= denom);
2489 SkASSERT(numer > 0);
2490 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002491
reed@android.com8a1c16f2008-12-17 15:59:43 +00002492 int nbits = SkCLZ(numer);
2493 int dbits = SkCLZ(denom);
2494 int bits = 6 - nbits + dbits;
2495 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002496
reed@google.com61eb0402011-04-15 12:11:12 +00002497 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002498 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002499 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002500
2501 denom <<= dbits - 1;
2502 numer <<= nbits - 1;
2503
2504 unsigned result = 0;
2505
2506 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002507 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002508 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002509 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002510 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002511 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002512
reed@android.com8a1c16f2008-12-17 15:59:43 +00002513 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002514 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002515 // make room for the rest of the answer bits
2516 result <<= bits;
2517 switch (bits) {
2518 case 6:
2519 if ((numer = (numer << 1) - denom) >= 0)
2520 result |= 32;
2521 else
2522 numer += denom;
2523 case 5:
2524 if ((numer = (numer << 1) - denom) >= 0)
2525 result |= 16;
2526 else
2527 numer += denom;
2528 case 4:
2529 if ((numer = (numer << 1) - denom) >= 0)
2530 result |= 8;
2531 else
2532 numer += denom;
2533 case 3:
2534 if ((numer = (numer << 1) - denom) >= 0)
2535 result |= 4;
2536 else
2537 numer += denom;
2538 case 2:
2539 if ((numer = (numer << 1) - denom) >= 0)
2540 result |= 2;
2541 else
2542 numer += denom;
2543 case 1:
2544 default: // not strictly need, but makes GCC make better ARM code
2545 if ((numer = (numer << 1) - denom) >= 0)
2546 result |= 1;
2547 else
2548 numer += denom;
2549 }
2550 }
2551 return result;
2552}
caryclark@google.com383d5d42012-06-06 12:09:18 +00002553#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002554
2555// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
caryclark@google.com383d5d42012-06-06 12:09:18 +00002556#ifndef SK_SCALAR_IS_FLOAT
reed@google.com61eb0402011-04-15 12:11:12 +00002557static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002558#ifdef SK_DEBUG
2559 {
2560 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002561 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002562 gOnce = true;
2563 SkASSERT(div_64(55, 55) == 64);
2564 SkASSERT(div_64(128, 256) == 32);
2565 SkASSERT(div_64(2326528, 4685824) == 31);
2566 SkASSERT(div_64(753664, 5210112) == 9);
2567 SkASSERT(div_64(229376, 4882432) == 3);
2568 SkASSERT(div_64(2, 64) == 2);
2569 SkASSERT(div_64(1, 64) == 1);
2570 // test that we handle underflow correctly
2571 SkASSERT(div_64(12345, 0x54321234) == 0);
2572 }
2573 }
2574#endif
2575
2576 SkASSERT(y > 0 && x > 0);
2577 const uint8_t* table = build_sweep_table();
2578
2579 unsigned result;
2580 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002581 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002582 // first part of the atan(v) = PI/2 - atan(1/v) identity
2583 // since our div_64 and table want v <= 1, where v = y/x
2584 SkTSwap<SkFixed>(x, y);
2585 }
2586
2587 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002588
reed@android.com8a1c16f2008-12-17 15:59:43 +00002589#ifdef SK_DEBUG
2590 {
2591 unsigned result2 = SkDivBits(y, x, 6);
2592 SkASSERT(result2 == result ||
2593 (result == 1 && result2 == 0));
2594 }
2595#endif
2596
2597 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2598 result = table[result];
2599
reed@google.com61eb0402011-04-15 12:11:12 +00002600 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002601 // complete the atan(v) = PI/2 - atan(1/v) identity
2602 result = 64 - result;
2603 // pin to 63
2604 result -= result >> 6;
2605 }
2606
2607 SkASSERT(result <= 63);
2608 return result;
2609}
caryclark@google.com383d5d42012-06-06 12:09:18 +00002610#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002611
2612// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002613#ifdef SK_SCALAR_IS_FLOAT
2614static unsigned SkATan2_255(float y, float x) {
2615 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2616 static const float g255Over2PI = 40.584510488433314f;
2617
2618 float result = sk_float_atan2(y, x);
2619 if (result < 0) {
2620 result += 2 * SK_ScalarPI;
2621 }
2622 SkASSERT(result >= 0);
2623 // since our value is always >= 0, we can cast to int, which is faster than
2624 // calling floorf()
2625 int ir = (int)(result * g255Over2PI);
2626 SkASSERT(ir >= 0 && ir <= 255);
2627 return ir;
2628}
2629#else
reed@google.com61eb0402011-04-15 12:11:12 +00002630static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2631 if (x == 0) {
2632 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002633 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002634 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002635 return y < 0 ? 192 : 64;
2636 }
reed@google.com61eb0402011-04-15 12:11:12 +00002637 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002638 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002639 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002640
reed@android.com8a1c16f2008-12-17 15:59:43 +00002641 /* Find the right quadrant for x,y
2642 Since atan_0_90 only handles the first quadrant, we rotate x,y
2643 appropriately before calling it, and then add the right amount
2644 to account for the real quadrant.
2645 quadrant 0 : add 0 | x > 0 && y > 0
2646 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2647 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2648 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002649
reed@android.com8a1c16f2008-12-17 15:59:43 +00002650 map x<0 to (1 << 6)
2651 map y<0 to (3 << 6)
2652 add = map_x ^ map_y
2653 */
2654 int xsign = x >> 31;
2655 int ysign = y >> 31;
2656 int add = ((-xsign) ^ (ysign & 3)) << 6;
2657
2658#ifdef SK_DEBUG
2659 if (0 == add)
2660 SkASSERT(x > 0 && y > 0);
2661 else if (64 == add)
2662 SkASSERT(x < 0 && y > 0);
2663 else if (128 == add)
2664 SkASSERT(x < 0 && y < 0);
2665 else if (192 == add)
2666 SkASSERT(x > 0 && y < 0);
2667 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002668 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002669#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002670
reed@android.com8a1c16f2008-12-17 15:59:43 +00002671 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2672 where we need to rotate x,y by 90 or -90
2673 */
2674 x = (x ^ xsign) - xsign;
2675 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002676 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002677 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002678 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002679
2680 unsigned result = add + atan_0_90(y, x);
2681 SkASSERT(result < 256);
2682 return result;
2683}
reed@google.com51baf5a2011-09-21 13:38:36 +00002684#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002685
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002686void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2687 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002688 SkMatrix::MapXYProc proc = fDstToIndexProc;
2689 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002690 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002691 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002692
reed@google.com61eb0402011-04-15 12:11:12 +00002693 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002694 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2695 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002696 SkScalar dx, fx = srcPt.fX;
2697 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002698
reed@google.com61eb0402011-04-15 12:11:12 +00002699 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002700 SkFixed storage[2];
2701 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2702 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002703 dx = SkFixedToScalar(storage[0]);
2704 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002705 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002706 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002707 dx = matrix.getScaleX();
2708 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002709 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002710
reed@google.com61eb0402011-04-15 12:11:12 +00002711 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002712 *dstC++ = cache[SkATan2_255(fy, fx)];
2713 fx += dx;
2714 fy += dy;
2715 }
reed@google.com61eb0402011-04-15 12:11:12 +00002716 } else { // perspective case
2717 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002718 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002719 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2720 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002721 }
2722 }
2723}
2724
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002725void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2726 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002727 SkMatrix::MapXYProc proc = fDstToIndexProc;
2728 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002729 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002730 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002731 SkPoint srcPt;
2732
reed@google.com61eb0402011-04-15 12:11:12 +00002733 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002734 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2735 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002736 SkScalar dx, fx = srcPt.fX;
2737 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002738
reed@google.com61eb0402011-04-15 12:11:12 +00002739 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002740 SkFixed storage[2];
2741 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2742 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002743 dx = SkFixedToScalar(storage[0]);
2744 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002745 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002746 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002747 dx = matrix.getScaleX();
2748 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002749 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002750
reed@google.com61eb0402011-04-15 12:11:12 +00002751 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002752 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2753 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002754 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002755 fx += dx;
2756 fy += dy;
2757 }
reed@google.com61eb0402011-04-15 12:11:12 +00002758 } else { // perspective case
2759 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002760 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2761 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002762
reed@google.com51baf5a2011-09-21 13:38:36 +00002763 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002764 index >>= (8 - kCache16Bits);
2765 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002766 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002767 }
2768 }
2769}
2770
reed@google.com61eb0402011-04-15 12:11:12 +00002771///////////////////////////////////////////////////////////////////////////////
2772///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002773
reed@google.comcb7be692012-06-06 20:31:56 +00002774#include "SkEmptyShader.h"
2775
reed@android.com8a1c16f2008-12-17 15:59:43 +00002776// assumes colors is SkColor* and pos is SkScalar*
2777#define EXPAND_1_COLOR(count) \
2778 SkColor tmp[2]; \
2779 do { \
2780 if (1 == count) { \
2781 tmp[0] = tmp[1] = colors[0]; \
2782 colors = tmp; \
2783 pos = NULL; \
2784 count = 2; \
2785 } \
2786 } while (0)
2787
reed@google.com61eb0402011-04-15 12:11:12 +00002788SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2789 const SkColor colors[],
2790 const SkScalar pos[], int colorCount,
2791 SkShader::TileMode mode,
2792 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002793 if (NULL == pts || NULL == colors || colorCount < 1) {
2794 return NULL;
2795 }
2796 EXPAND_1_COLOR(colorCount);
2797
reed@android.comab840b82009-07-01 17:00:03 +00002798 return SkNEW_ARGS(Linear_Gradient,
2799 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002800}
2801
reed@google.com61eb0402011-04-15 12:11:12 +00002802SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2803 const SkColor colors[],
2804 const SkScalar pos[], int colorCount,
2805 SkShader::TileMode mode,
2806 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002807 if (radius <= 0 || NULL == colors || colorCount < 1) {
2808 return NULL;
2809 }
2810 EXPAND_1_COLOR(colorCount);
2811
reed@android.comab840b82009-07-01 17:00:03 +00002812 return SkNEW_ARGS(Radial_Gradient,
2813 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002814}
2815
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002816SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2817 SkScalar startRadius,
2818 const SkPoint& end,
2819 SkScalar endRadius,
2820 const SkColor colors[],
2821 const SkScalar pos[],
2822 int colorCount,
2823 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002824 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002825 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2826 return NULL;
2827 }
2828 EXPAND_1_COLOR(colorCount);
reed@google.comcb7be692012-06-06 20:31:56 +00002829
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002830 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002831 (start, startRadius, end, endRadius, colors, pos,
2832 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002833}
2834
reed@google.comcb7be692012-06-06 20:31:56 +00002835SkShader* SkGradientShader::CreateTwoPointConical(const SkPoint& start,
2836 SkScalar startRadius,
2837 const SkPoint& end,
2838 SkScalar endRadius,
2839 const SkColor colors[],
2840 const SkScalar pos[],
2841 int colorCount,
2842 SkShader::TileMode mode,
2843 SkUnitMapper* mapper) {
2844 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2845 return NULL;
2846 }
2847 if (start == end && startRadius == endRadius) {
2848 return SkNEW(SkEmptyShader);
2849 }
2850
2851 return SkNEW_ARGS(Two_Point_Conical_Gradient,
2852 (start, startRadius, end, endRadius, colors, pos,
2853 colorCount, mode, mapper));
2854}
2855
reed@android.com8a1c16f2008-12-17 15:59:43 +00002856SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2857 const SkColor colors[],
2858 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002859 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002860 if (NULL == colors || count < 1) {
2861 return NULL;
2862 }
2863 EXPAND_1_COLOR(count);
2864
2865 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2866}
2867
caryclark@google.comd26147a2011-12-15 14:16:43 +00002868SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2869 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2870 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002871 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002872 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
scroggo@google.comdb15a422012-06-11 16:51:45 +00002873 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Conical_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002874SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END