blob: 8b78984d784458b916e15da7124ba0a2f5598e88 [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) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001029 matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001030 matrix->preConcat(fPtsToUnit);
1031 }
1032 if (xy) {
1033 xy[0] = fTileMode;
1034 xy[1] = kClamp_TileMode;
1035 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001036 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001037}
1038
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001039SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1040 if (info) {
1041 commonAsAGradient(info);
1042 info->fPoint[0] = fStart;
1043 info->fPoint[1] = fEnd;
1044 }
1045 return kLinear_GradientType;
1046}
1047
reed@android.com3c9b2a42009-08-27 19:28:37 +00001048static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1049 int count) {
1050 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001051 *dst++ = value;
1052 count -= 1;
1053 SkTSwap(value, other);
1054 }
1055
1056 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001057
reed@android.com3c9b2a42009-08-27 19:28:37 +00001058 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001059 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001060 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001062
reed@google.com5eb158d2011-04-15 15:50:34 +00001063#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001064 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001065 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001066 SkASSERT(fi < Gradient_Shader::kCache16Count); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001067 fx += dx; \
1068 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001069 toggle ^= Gradient_Shader::kDitherStride16; \
reed@google.com13659f12011-04-18 19:59:38 +00001070 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001071
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001072namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001073
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001074typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001075 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001076 int toggle, int count);
1077
1078void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1079 uint16_t* SK_RESTRICT dstC,
1080 const uint16_t* SK_RESTRICT cache,
1081 int toggle, int count) {
1082 // we're a vertical gradient, so no change in a span
1083 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001084 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001085 dither_memset16(dstC, cache[toggle + fi],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001086 cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001087
1088}
1089
1090void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1091 uint16_t* SK_RESTRICT dstC,
1092 const uint16_t* SK_RESTRICT cache,
1093 int toggle, int count) {
1094 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001095 range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001096
1097 if ((count = range.fCount0) > 0) {
1098 dither_memset16(dstC,
1099 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001100 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001101 count);
1102 dstC += count;
1103 }
1104 if ((count = range.fCount1) > 0) {
1105 int unroll = count >> 3;
1106 fx = range.fFx1;
1107 for (int i = 0; i < unroll; i++) {
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 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1112 }
1113 if ((count &= 7) > 0) {
1114 do {
1115 NO_CHECK_ITER_16;
1116 } while (--count != 0);
1117 }
1118 }
1119 if ((count = range.fCount2) > 0) {
1120 dither_memset16(dstC,
1121 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001122 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001123 count);
1124 }
1125}
1126
1127void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1128 uint16_t* SK_RESTRICT dstC,
1129 const uint16_t* SK_RESTRICT cache,
1130 int toggle, int count) {
1131 do {
1132 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1133 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001134 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001135 fx += dx;
1136 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001137 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001138 } while (--count != 0);
1139}
1140
1141void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1142 uint16_t* SK_RESTRICT dstC,
1143 const uint16_t* SK_RESTRICT cache,
1144 int toggle, int count) {
1145 SkASSERT(proc == repeat_tileproc);
1146 do {
1147 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1148 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001149 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001150 fx += dx;
1151 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001152 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001153 } while (--count != 0);
1154}
1155}
1156
1157void Linear_Gradient::shadeSpan16(int x, int y,
1158 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001159 SkASSERT(count > 0);
1160
1161 SkPoint srcPt;
1162 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1163 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001164 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001165 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001166
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001167 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001168 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1169 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001170 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1171
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001172 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 SkFixed dxStorage[1];
1174 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1175 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001176 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001177 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1178 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1179 }
1180
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001181 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001182 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001183 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001184 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001185 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001186 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001187 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001188 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001190 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001191 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001192 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001193 SkScalar dstX = SkIntToScalar(x);
1194 SkScalar dstY = SkIntToScalar(y);
1195 do {
1196 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1197 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1198 SkASSERT(fi <= 0xFFFF);
1199
reed@android.com512a8762009-12-14 15:25:36 +00001200 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001201 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001202 toggle ^= Gradient_Shader::kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203
1204 dstX += SK_Scalar1;
1205 } while (--count != 0);
1206 }
1207}
1208
1209///////////////////////////////////////////////////////////////////////////////
1210
1211#define kSQRT_TABLE_BITS 11
1212#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1213
1214#include "SkRadialGradient_Table.h"
1215
1216#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1217
1218#include <stdio.h>
1219
reed@google.com61eb0402011-04-15 12:11:12 +00001220void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1222
1223 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1224 SkASSERT(file);
1225 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1226
reed@google.com61eb0402011-04-15 12:11:12 +00001227 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1228 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001230 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231
1232 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1233
1234 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001235 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001236 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001237 }
1238 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001239 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001240 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241 }
1242 ::fprintf(file, "};\n");
1243 ::fclose(file);
1244}
1245
1246#endif
1247
1248
reed@google.com61eb0402011-04-15 12:11:12 +00001249static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1250 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001251 SkScalar inv = SkScalarInvert(radius);
1252
1253 matrix->setTranslate(-center.fX, -center.fY);
1254 matrix->postScale(inv, inv);
1255}
1256
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001257
1258namespace {
1259
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001260typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
1261 SkScalar sfy, SkScalar sdy,
1262 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001263 int toggle, int count);
1264
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001265void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
1266 SkScalar sfy, SkScalar sdy,
1267 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001268 int toggle, int count) {
1269 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1270
1271 /* knock these down so we can pin against +- 0x7FFF, which is an
1272 immediate load, rather than 0xFFFF which is slower. This is a
1273 compromise, since it reduces our precision, but that appears
1274 to be visually OK. If we decide this is OK for all of our cases,
1275 we could (it seems) put this scale-down into fDstToIndex,
1276 to avoid having to do these extra shifts each time.
1277 */
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001278 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1279 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1280 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1281 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001282 // might perform this check for the other modes,
1283 // but the win will be a smaller % of the total
1284 if (dy == 0) {
1285 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1286 fy *= fy;
1287 do {
1288 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1289 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1290 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1291 fx += dx;
1292 *dstC++ = cache[toggle +
1293 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001294 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001295 } while (--count != 0);
1296 } else {
1297 do {
1298 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1299 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1300 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1301 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1302 fx += dx;
1303 fy += dy;
1304 *dstC++ = cache[toggle +
1305 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001306 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001307 } while (--count != 0);
1308 }
1309}
1310
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001311void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
1312 SkScalar sfy, SkScalar sdy,
1313 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001314 int toggle, int count) {
1315 do {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001316#ifdef SK_SCALAR_IS_FLOAT
1317 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1318 SkFixed dist = SkFloatToFixed(fdist);
1319#else
1320 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1321 SkFixedSquare(sfy);
1322 if (magnitudeSquared < 0) // Overflow.
1323 magnitudeSquared = SK_FixedMax;
1324 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1325#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001326 unsigned fi = mirror_tileproc(dist);
1327 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001328 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001329 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001330 sfx += sdx;
1331 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001332 } while (--count != 0);
1333}
1334
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001335void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
1336 SkScalar sfy, SkScalar sdy,
1337 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001338 int toggle, int count) {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001339 SkFixed fx = SkScalarToFixed(sfx);
1340 SkFixed dx = SkScalarToFixed(sdx);
1341 SkFixed fy = SkScalarToFixed(sfy);
1342 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001343 do {
1344 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1345 unsigned fi = repeat_tileproc(dist);
1346 SkASSERT(fi <= 0xFFFF);
1347 fx += dx;
1348 fy += dy;
1349 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001350 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001351 } while (--count != 0);
1352}
1353
1354}
1355
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356class Radial_Gradient : public Gradient_Shader {
1357public:
1358 Radial_Gradient(const SkPoint& center, SkScalar radius,
1359 const SkColor colors[], const SkScalar pos[], int colorCount,
1360 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001361 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1362 fCenter(center),
1363 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001364 {
1365 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1366 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1367
1368 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1369 }
reed@google.com61eb0402011-04-15 12:11:12 +00001370
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001371 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
1372 SK_OVERRIDE;
1373 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
1374 int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001375 SkASSERT(count > 0);
1376
1377 SkPoint srcPt;
1378 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1379 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001380 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001381 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382
reed@android.com3c9b2a42009-08-27 19:28:37 +00001383 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001384 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1385 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001386
1387 SkScalar sdx = fDstToIndex.getScaleX();
1388 SkScalar sdy = fDstToIndex.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001389
reed@android.com3c9b2a42009-08-27 19:28:37 +00001390 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001391 SkFixed storage[2];
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001392 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1393 &storage[0], &storage[1]);
1394 sdx = SkFixedToScalar(storage[0]);
1395 sdy = SkFixedToScalar(storage[1]);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001396 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001397 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001398 }
1399
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001400 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001401 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001402 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001403 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001404 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001405 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001406 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 }
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001408 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
1409 cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001410 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 SkScalar dstX = SkIntToScalar(x);
1412 SkScalar dstY = SkIntToScalar(y);
1413 do {
1414 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1415 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1416 SkASSERT(fi <= 0xFFFF);
1417
1418 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001419 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001420 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421
1422 dstX += SK_Scalar1;
1423 } while (--count != 0);
1424 }
1425 }
1426
reed@google.com55b8e8c2011-01-13 16:22:35 +00001427 virtual BitmapType asABitmap(SkBitmap* bitmap,
1428 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001429 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001430 SkScalar* twoPointRadialParams)
1431 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001432 if (bitmap) {
1433 this->commonAsABitmap(bitmap);
1434 }
1435 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001436 matrix->setScale(SkIntToScalar(kGradient32Length),
1437 SkIntToScalar(kGradient32Length));
reed@google.comdc731fd2010-12-23 15:19:47 +00001438 matrix->preConcat(fPtsToUnit);
1439 }
1440 if (xy) {
1441 xy[0] = fTileMode;
1442 xy[1] = kClamp_TileMode;
1443 }
1444 return kRadial_BitmapType;
1445 }
reed@google.com7716afb2011-12-07 15:17:50 +00001446 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001447 if (info) {
1448 commonAsAGradient(info);
1449 info->fPoint[0] = fCenter;
1450 info->fRadius[0] = fRadius;
1451 }
1452 return kRadial_GradientType;
1453 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001454
djsollen@google.comba28d032012-03-26 17:57:35 +00001455 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Radial_Gradient)
1456
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001458 Radial_Gradient(SkFlattenableReadBuffer& buffer)
djsollen@google.com54924242012-03-29 15:18:04 +00001459 : INHERITED(buffer),
djsollen@google.com2b2ede32012-04-12 13:24:04 +00001460 fCenter(buffer.readPoint()),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001461 fRadius(buffer.readScalar()) {
1462 }
djsollen@google.com54924242012-03-29 15:18:04 +00001463 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
1464 this->INHERITED::flatten(buffer);
djsollen@google.com2b2ede32012-04-12 13:24:04 +00001465 buffer.writePoint(fCenter);
djsollen@google.com54924242012-03-29 15:18:04 +00001466 buffer.writeScalar(fRadius);
1467 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468
1469private:
1470 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001471 const SkPoint fCenter;
1472 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473};
1474
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001475namespace {
1476
1477inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001478 // fast, overly-conservative test: checks unit square instead
1479 // of unit circle
1480 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1481 (fx <= -SK_FixedHalf && dx <= 0);
1482 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1483 (fy <= -SK_FixedHalf && dy <= 0);
1484
1485 return xClamped || yClamped;
1486}
1487
1488// Return true if (fx * fy) is always inside the unit circle
1489// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1490// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001491inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001492 int fy, int dy, int count) {
1493 SkASSERT(count > 0);
1494 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1495 return false;
1496 }
1497 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1498 return false;
1499 }
1500 fx += (count - 1) * dx;
1501 fy += (count - 1) * dy;
1502 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1503 return false;
1504 }
1505 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1506}
1507
1508#define UNPINNED_RADIAL_STEP \
1509 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001510 *dstC++ = cache[toggle + \
1511 (sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift)]; \
1512 toggle ^= Gradient_Shader::kDitherStride32; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001513 fx += dx; \
1514 fy += dy;
1515
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001516typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1517 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001518 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001519 int count, int toggle);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001520
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001521// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001522void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
1523 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001524 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001525 int count, int toggle) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001526 // Floating point seems to be slower than fixed point,
1527 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001528 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001529 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1530 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1531 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1532 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001533 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001534 unsigned fi = Gradient_Shader::kGradient32Length;
1535 sk_memset32_dither(dstC,
1536 cache[toggle + fi],
1537 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
1538 count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001539 } else if ((count > 4) &&
1540 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1541 unsigned fi;
1542 // 4x unroll appears to be no faster than 2x unroll on Linux
1543 while (count > 1) {
1544 UNPINNED_RADIAL_STEP;
1545 UNPINNED_RADIAL_STEP;
1546 count -= 2;
1547 }
1548 if (count) {
1549 UNPINNED_RADIAL_STEP;
1550 }
1551 }
1552 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001553 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1554 if (dy == 0) {
1555 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1556 yy *= yy;
1557 do {
1558 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1559 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1560 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001561 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1562 Gradient_Shader::kSqrt32Shift)];
1563 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001564 fx += dx;
1565 } while (--count != 0);
1566 } else {
1567 do {
1568 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1569 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1570 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1571 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001572 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1573 Gradient_Shader::kSqrt32Shift)];
1574 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001575 fx += dx;
1576 fy += dy;
1577 } while (--count != 0);
1578 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001579 }
1580}
1581
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001582// Unrolling this loop doesn't seem to help (when float); we're stalling to
1583// get the results of the sqrt (?), and don't have enough extra registers to
1584// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001585void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1586 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001587 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001588 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001589 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001590#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001591 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1592 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001593#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001594 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1595 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001596 if (magnitudeSquared < 0) // Overflow.
1597 magnitudeSquared = SK_FixedMax;
1598 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001599#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001600 unsigned fi = mirror_tileproc(dist);
1601 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001602 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1603 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001604 sfx += sdx;
1605 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001606 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001607}
1608
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001609void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1610 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001611 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001612 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001613 SkFixed fx = SkScalarToFixed(sfx);
1614 SkFixed dx = SkScalarToFixed(sdx);
1615 SkFixed fy = SkScalarToFixed(sfy);
1616 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001617 do {
1618 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1619 SkFixedSquare(fy);
1620 if (magnitudeSquared < 0) // Overflow.
1621 magnitudeSquared = SK_FixedMax;
1622 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1623 unsigned fi = repeat_tileproc(dist);
1624 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001625 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1626 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001627 fx += dx;
1628 fy += dy;
1629 } while (--count != 0);
1630}
1631}
1632
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001633void Radial_Gradient::shadeSpan(int x, int y,
1634 SkPMColor* SK_RESTRICT dstC, int count) {
1635 SkASSERT(count > 0);
1636
1637 SkPoint srcPt;
1638 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1639 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001640 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001641#ifdef USE_DITHER_32BIT_GRADIENT
1642 int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32;
1643#else
1644 int toggle = 0;
1645#endif
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001646
1647 if (fDstToIndexClass != kPerspective_MatrixClass) {
1648 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1649 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001650 SkScalar sdx = fDstToIndex.getScaleX();
1651 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001652
1653 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1654 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001655 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1656 &storage[0], &storage[1]);
1657 sdx = SkFixedToScalar(storage[0]);
1658 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001659 } else {
1660 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001661 }
1662
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001663 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001664 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001665 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001666 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001667 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001668 } else {
1669 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001670 }
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001671 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001672 } else { // perspective case
1673 SkScalar dstX = SkIntToScalar(x);
1674 SkScalar dstY = SkIntToScalar(y);
1675 do {
1676 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1677 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1678 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001679 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001680 dstX += SK_Scalar1;
1681 } while (--count != 0);
1682 }
1683}
1684
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001685/* Two-point radial gradients are specified by two circles, each with a center
1686 point and radius. The gradient can be considered to be a series of
1687 concentric circles, with the color interpolated from the start circle
1688 (at t=0) to the end circle (at t=1).
1689
1690 For each point (x, y) in the span, we want to find the
1691 interpolated circle that intersects that point. The center
1692 of the desired circle (Cx, Cy) falls at some distance t
1693 along the line segment between the start point (Sx, Sy) and
1694 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001695
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001696 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1697 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001698
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001699 The radius of the desired circle (r) is also a linear interpolation t
1700 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001701
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001702 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001703
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001704 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001705
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001706 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001707
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001708 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001709
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001710 (x - ((1 - t) * Sx + t * Ex))^2
1711 + (y - ((1 - t) * Sy + t * Ey))^2
1712 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001713
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001714 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001715
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001716 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1717 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1718 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001719
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001720 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1721
1722 [Dx^2 + Dy^2 - Dr^2)] * t^2
1723 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1724 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001725
1726 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001727 possible circles on which the point may fall. Solving for t yields
1728 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001729
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001730 If a<0, the start circle is entirely contained in the
1731 end circle, and one of the roots will be <0 or >1 (off the line
1732 segment). If a>0, the start circle falls at least partially
1733 outside the end circle (or vice versa), and the gradient
1734 defines a "tube" where a point may be on one circle (on the
1735 inside of the tube) or the other (outside of the tube). We choose
1736 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001737
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001738 In order to keep the math to within the limits of fixed point,
1739 we divide the entire quadratic by Dr^2, and replace
1740 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001741
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001742 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1743 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1744 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001745
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001746 (x' and y' are computed by appending the subtract and scale to the
1747 fDstToIndex matrix in the constructor).
1748
1749 Since the 'A' component of the quadratic is independent of x' and y', it
1750 is precomputed in the constructor. Since the 'B' component is linear in
1751 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001752 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001753 a perspective projection), it must be computed in the loop.
1754
1755*/
1756
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001757namespace {
1758
1759inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1760 SkScalar sr2d2, SkScalar foura,
1761 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001762 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001763 if (0 == foura) {
1764 return SkScalarToFixed(SkScalarDiv(-c, b));
1765 }
1766
reed@google.com84e9c082011-04-13 17:44:24 +00001767 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001768 if (discrim < 0) {
1769 discrim = -discrim;
1770 }
reed@google.com84e9c082011-04-13 17:44:24 +00001771 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1772 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001773 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001774 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001775 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001776 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001777 }
reed@google.com84e9c082011-04-13 17:44:24 +00001778 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001779}
1780
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001781typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1782 SkScalar fy, SkScalar dy,
1783 SkScalar b, SkScalar db,
1784 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001785 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001786 int count);
1787
1788void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1789 SkScalar fy, SkScalar dy,
1790 SkScalar b, SkScalar db,
1791 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001792 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001793 int count) {
1794 for (; count > 0; --count) {
1795 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1796 fOneOverTwoA, posRoot);
1797 SkFixed index = SkClampMax(t, 0xFFFF);
1798 SkASSERT(index <= 0xFFFF);
1799 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1800 fx += dx;
1801 fy += dy;
1802 b += db;
1803 }
1804}
1805void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1806 SkScalar fy, SkScalar dy,
1807 SkScalar b, SkScalar db,
1808 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001809 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001810 int count) {
1811 for (; count > 0; --count) {
1812 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1813 fOneOverTwoA, posRoot);
1814 SkFixed index = mirror_tileproc(t);
1815 SkASSERT(index <= 0xFFFF);
1816 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1817 fx += dx;
1818 fy += dy;
1819 b += db;
1820 }
1821}
1822
1823void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1824 SkScalar fy, SkScalar dy,
1825 SkScalar b, SkScalar db,
1826 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001827 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001828 int count) {
1829 for (; count > 0; --count) {
1830 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1831 fOneOverTwoA, posRoot);
1832 SkFixed index = repeat_tileproc(t);
1833 SkASSERT(index <= 0xFFFF);
1834 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1835 fx += dx;
1836 fy += dy;
1837 b += db;
1838 }
1839}
1840
1841
1842
1843}
1844
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001845class Two_Point_Radial_Gradient : public Gradient_Shader {
1846public:
1847 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1848 const SkPoint& end, SkScalar endRadius,
1849 const SkColor colors[], const SkScalar pos[],
1850 int colorCount, SkShader::TileMode mode,
1851 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001852 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1853 fCenter1(start),
1854 fCenter2(end),
1855 fRadius1(startRadius),
1856 fRadius2(endRadius) {
1857 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001858 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001859
1860 virtual BitmapType asABitmap(SkBitmap* bitmap,
1861 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001862 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001863 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001864 if (bitmap) {
1865 this->commonAsABitmap(bitmap);
1866 }
1867 SkScalar diffL = 0; // just to avoid gcc warning
1868 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001869 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001870 SkScalarSquare(fDiff.fY));
1871 }
1872 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001873 if (diffL) {
1874 SkScalar invDiffL = SkScalarInvert(diffL);
1875 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1876 SkScalarMul(invDiffL, fDiff.fX));
1877 } else {
1878 matrix->reset();
1879 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001880 matrix->preConcat(fPtsToUnit);
1881 }
1882 if (xy) {
1883 xy[0] = fTileMode;
1884 xy[1] = kClamp_TileMode;
1885 }
1886 if (NULL != twoPointRadialParams) {
1887 twoPointRadialParams[0] = diffL;
1888 twoPointRadialParams[1] = fStartRadius;
1889 twoPointRadialParams[2] = fDiffRadius;
1890 }
1891 return kTwoPointRadial_BitmapType;
1892 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001893
reed@google.com8e6d9142011-12-07 15:30:34 +00001894 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001895 if (info) {
1896 commonAsAGradient(info);
1897 info->fPoint[0] = fCenter1;
1898 info->fPoint[1] = fCenter2;
1899 info->fRadius[0] = fRadius1;
1900 info->fRadius[1] = fRadius2;
1901 }
1902 return kRadial2_GradientType;
1903 }
1904
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001905 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1906 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001907 SkASSERT(count > 0);
1908
1909 // Zero difference between radii: fill with transparent black.
1910 if (fDiffRadius == 0) {
1911 sk_bzero(dstC, count * sizeof(*dstC));
1912 return;
1913 }
1914 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1915 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001916 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001917
1918 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001919 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001920 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001921 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001922 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1923 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001924 SkScalar dx, fx = srcPt.fX;
1925 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001926
reed@google.com61eb0402011-04-15 12:11:12 +00001927 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001928 SkFixed fixedX, fixedY;
1929 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1930 dx = SkFixedToScalar(fixedX);
1931 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001932 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001933 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001934 dx = fDstToIndex.getScaleX();
1935 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001936 }
reed@google.com84e9c082011-04-13 17:44:24 +00001937 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1938 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1939 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1940 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001941
1942 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00001943 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001944 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00001945 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001946 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00001947 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001948 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001949 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001950 (*shadeProc)(fx, dx, fy, dy, b, db,
1951 fSr2D2, foura, fOneOverTwoA, posRoot,
1952 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00001953 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001954 SkScalar dstX = SkIntToScalar(x);
1955 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001956 for (; count > 0; --count) {
1957 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001958 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001959 SkScalar fx = srcPt.fX;
1960 SkScalar fy = srcPt.fY;
1961 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1962 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001963 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1964 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001965 SkFixed index = proc(t);
1966 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001967 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00001968 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001969 }
1970 }
1971 }
1972
reed@android.com6c59a172009-09-22 20:24:05 +00001973 virtual bool setContext(const SkBitmap& device,
1974 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00001975 const SkMatrix& matrix) SK_OVERRIDE {
1976 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00001977 return false;
1978 }
1979
1980 // we don't have a span16 proc
1981 fFlags &= ~kHasSpan16_Flag;
1982 return true;
1983 }
1984
djsollen@google.com54924242012-03-29 15:18:04 +00001985 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Two_Point_Radial_Gradient)
1986
1987protected:
1988 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
1989 : INHERITED(buffer),
djsollen@google.com2b2ede32012-04-12 13:24:04 +00001990 fCenter1(buffer.readPoint()),
1991 fCenter2(buffer.readPoint()),
djsollen@google.com54924242012-03-29 15:18:04 +00001992 fRadius1(buffer.readScalar()),
1993 fRadius2(buffer.readScalar()) {
1994 init();
1995 };
1996
1997 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00001998 this->INHERITED::flatten(buffer);
djsollen@google.com2b2ede32012-04-12 13:24:04 +00001999 buffer.writePoint(fCenter1);
2000 buffer.writePoint(fCenter2);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002001 buffer.writeScalar(fRadius1);
2002 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002003 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002004
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002005private:
2006 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002007 const SkPoint fCenter1;
2008 const SkPoint fCenter2;
2009 const SkScalar fRadius1;
2010 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002011 SkPoint fDiff;
2012 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002013
2014 void init() {
2015 fDiff = fCenter1 - fCenter2;
2016 fDiffRadius = fRadius2 - fRadius1;
2017 SkScalar inv = SkScalarInvert(fDiffRadius);
2018 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2019 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2020 fStartRadius = SkScalarMul(fRadius1, inv);
2021 fSr2D2 = SkScalarSquare(fStartRadius);
2022 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002023 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002024
2025 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2026 fPtsToUnit.postScale(inv, inv);
2027 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002028};
2029
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030///////////////////////////////////////////////////////////////////////////////
2031
2032class Sweep_Gradient : public Gradient_Shader {
2033public:
2034 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2035 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002036 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2037 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002038 {
2039 fPtsToUnit.setTranslate(-cx, -cy);
2040 }
reed@google.com7716afb2011-12-07 15:17:50 +00002041 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2042 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002043
2044 virtual BitmapType asABitmap(SkBitmap* bitmap,
2045 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002046 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002047 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002048 if (bitmap) {
2049 this->commonAsABitmap(bitmap);
2050 }
2051 if (matrix) {
2052 *matrix = fPtsToUnit;
2053 }
2054 if (xy) {
2055 xy[0] = fTileMode;
2056 xy[1] = kClamp_TileMode;
2057 }
2058 return kSweep_BitmapType;
2059 }
2060
reed@google.com7716afb2011-12-07 15:17:50 +00002061 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002062 if (info) {
2063 commonAsAGradient(info);
2064 info->fPoint[0] = fCenter;
2065 }
2066 return kSweep_GradientType;
2067 }
2068
djsollen@google.comba28d032012-03-26 17:57:35 +00002069 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Sweep_Gradient)
2070
reed@android.com8a1c16f2008-12-17 15:59:43 +00002071protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002072 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
djsollen@google.com54924242012-03-29 15:18:04 +00002073 : INHERITED(buffer),
djsollen@google.com2b2ede32012-04-12 13:24:04 +00002074 fCenter(buffer.readPoint()) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002075 }
djsollen@google.com54924242012-03-29 15:18:04 +00002076 virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
2077 this->INHERITED::flatten(buffer);
djsollen@google.com2b2ede32012-04-12 13:24:04 +00002078 buffer.writePoint(fCenter);
djsollen@google.com54924242012-03-29 15:18:04 +00002079 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002080
reed@android.com8a1c16f2008-12-17 15:59:43 +00002081private:
2082 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002083 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002084};
2085
2086#ifdef COMPUTE_SWEEP_TABLE
2087#define PI 3.14159265
2088static bool gSweepTableReady;
2089static uint8_t gSweepTable[65];
2090
2091/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2092 We scale the results to [0..32]
2093*/
reed@google.com61eb0402011-04-15 12:11:12 +00002094static const uint8_t* build_sweep_table() {
2095 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096 const int N = 65;
2097 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002098
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099 for (int i = 0; i < N; i++)
2100 {
2101 double arg = i / DENOM;
2102 double v = atan(arg);
2103 int iv = (int)round(v * DENOM * 2 / PI);
2104// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2105 printf("%d, ", iv);
2106 gSweepTable[i] = iv;
2107 }
2108 gSweepTableReady = true;
2109 }
2110 return gSweepTable;
2111}
2112#else
2113static const uint8_t gSweepTable[] = {
2114 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2115 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2116 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2117 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2118 32
2119};
2120static const uint8_t* build_sweep_table() { return gSweepTable; }
2121#endif
2122
2123// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2124// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2125// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2126
2127//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002128static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129 SkASSERT(numer <= denom);
2130 SkASSERT(numer > 0);
2131 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002132
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 int nbits = SkCLZ(numer);
2134 int dbits = SkCLZ(denom);
2135 int bits = 6 - nbits + dbits;
2136 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002137
reed@google.com61eb0402011-04-15 12:11:12 +00002138 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002140 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141
2142 denom <<= dbits - 1;
2143 numer <<= nbits - 1;
2144
2145 unsigned result = 0;
2146
2147 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002148 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002150 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002152 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002153
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002155 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 // make room for the rest of the answer bits
2157 result <<= bits;
2158 switch (bits) {
2159 case 6:
2160 if ((numer = (numer << 1) - denom) >= 0)
2161 result |= 32;
2162 else
2163 numer += denom;
2164 case 5:
2165 if ((numer = (numer << 1) - denom) >= 0)
2166 result |= 16;
2167 else
2168 numer += denom;
2169 case 4:
2170 if ((numer = (numer << 1) - denom) >= 0)
2171 result |= 8;
2172 else
2173 numer += denom;
2174 case 3:
2175 if ((numer = (numer << 1) - denom) >= 0)
2176 result |= 4;
2177 else
2178 numer += denom;
2179 case 2:
2180 if ((numer = (numer << 1) - denom) >= 0)
2181 result |= 2;
2182 else
2183 numer += denom;
2184 case 1:
2185 default: // not strictly need, but makes GCC make better ARM code
2186 if ((numer = (numer << 1) - denom) >= 0)
2187 result |= 1;
2188 else
2189 numer += denom;
2190 }
2191 }
2192 return result;
2193}
2194
2195// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002196static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197#ifdef SK_DEBUG
2198 {
2199 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002200 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002201 gOnce = true;
2202 SkASSERT(div_64(55, 55) == 64);
2203 SkASSERT(div_64(128, 256) == 32);
2204 SkASSERT(div_64(2326528, 4685824) == 31);
2205 SkASSERT(div_64(753664, 5210112) == 9);
2206 SkASSERT(div_64(229376, 4882432) == 3);
2207 SkASSERT(div_64(2, 64) == 2);
2208 SkASSERT(div_64(1, 64) == 1);
2209 // test that we handle underflow correctly
2210 SkASSERT(div_64(12345, 0x54321234) == 0);
2211 }
2212 }
2213#endif
2214
2215 SkASSERT(y > 0 && x > 0);
2216 const uint8_t* table = build_sweep_table();
2217
2218 unsigned result;
2219 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002220 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002221 // first part of the atan(v) = PI/2 - atan(1/v) identity
2222 // since our div_64 and table want v <= 1, where v = y/x
2223 SkTSwap<SkFixed>(x, y);
2224 }
2225
2226 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002227
reed@android.com8a1c16f2008-12-17 15:59:43 +00002228#ifdef SK_DEBUG
2229 {
2230 unsigned result2 = SkDivBits(y, x, 6);
2231 SkASSERT(result2 == result ||
2232 (result == 1 && result2 == 0));
2233 }
2234#endif
2235
2236 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2237 result = table[result];
2238
reed@google.com61eb0402011-04-15 12:11:12 +00002239 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002240 // complete the atan(v) = PI/2 - atan(1/v) identity
2241 result = 64 - result;
2242 // pin to 63
2243 result -= result >> 6;
2244 }
2245
2246 SkASSERT(result <= 63);
2247 return result;
2248}
2249
2250// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002251#ifdef SK_SCALAR_IS_FLOAT
2252static unsigned SkATan2_255(float y, float x) {
2253 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2254 static const float g255Over2PI = 40.584510488433314f;
2255
2256 float result = sk_float_atan2(y, x);
2257 if (result < 0) {
2258 result += 2 * SK_ScalarPI;
2259 }
2260 SkASSERT(result >= 0);
2261 // since our value is always >= 0, we can cast to int, which is faster than
2262 // calling floorf()
2263 int ir = (int)(result * g255Over2PI);
2264 SkASSERT(ir >= 0 && ir <= 255);
2265 return ir;
2266}
2267#else
reed@google.com61eb0402011-04-15 12:11:12 +00002268static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2269 if (x == 0) {
2270 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002271 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002272 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002273 return y < 0 ? 192 : 64;
2274 }
reed@google.com61eb0402011-04-15 12:11:12 +00002275 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002276 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002277 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002278
reed@android.com8a1c16f2008-12-17 15:59:43 +00002279 /* Find the right quadrant for x,y
2280 Since atan_0_90 only handles the first quadrant, we rotate x,y
2281 appropriately before calling it, and then add the right amount
2282 to account for the real quadrant.
2283 quadrant 0 : add 0 | x > 0 && y > 0
2284 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2285 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2286 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002287
reed@android.com8a1c16f2008-12-17 15:59:43 +00002288 map x<0 to (1 << 6)
2289 map y<0 to (3 << 6)
2290 add = map_x ^ map_y
2291 */
2292 int xsign = x >> 31;
2293 int ysign = y >> 31;
2294 int add = ((-xsign) ^ (ysign & 3)) << 6;
2295
2296#ifdef SK_DEBUG
2297 if (0 == add)
2298 SkASSERT(x > 0 && y > 0);
2299 else if (64 == add)
2300 SkASSERT(x < 0 && y > 0);
2301 else if (128 == add)
2302 SkASSERT(x < 0 && y < 0);
2303 else if (192 == add)
2304 SkASSERT(x > 0 && y < 0);
2305 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002306 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002307#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002308
reed@android.com8a1c16f2008-12-17 15:59:43 +00002309 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2310 where we need to rotate x,y by 90 or -90
2311 */
2312 x = (x ^ xsign) - xsign;
2313 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002314 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002315 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002316 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002317
2318 unsigned result = add + atan_0_90(y, x);
2319 SkASSERT(result < 256);
2320 return result;
2321}
reed@google.com51baf5a2011-09-21 13:38:36 +00002322#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002323
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002324void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2325 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002326 SkMatrix::MapXYProc proc = fDstToIndexProc;
2327 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002328 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002329 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002330
reed@google.com61eb0402011-04-15 12:11:12 +00002331 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002332 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2333 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002334 SkScalar dx, fx = srcPt.fX;
2335 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002336
reed@google.com61eb0402011-04-15 12:11:12 +00002337 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002338 SkFixed storage[2];
2339 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2340 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002341 dx = SkFixedToScalar(storage[0]);
2342 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002343 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002344 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002345 dx = matrix.getScaleX();
2346 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002347 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002348
reed@google.com61eb0402011-04-15 12:11:12 +00002349 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002350 *dstC++ = cache[SkATan2_255(fy, fx)];
2351 fx += dx;
2352 fy += dy;
2353 }
reed@google.com61eb0402011-04-15 12:11:12 +00002354 } else { // perspective case
2355 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002356 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002357 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2358 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002359 }
2360 }
2361}
2362
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002363void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2364 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002365 SkMatrix::MapXYProc proc = fDstToIndexProc;
2366 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002367 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002368 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002369 SkPoint srcPt;
2370
reed@google.com61eb0402011-04-15 12:11:12 +00002371 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002372 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2373 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002374 SkScalar dx, fx = srcPt.fX;
2375 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002376
reed@google.com61eb0402011-04-15 12:11:12 +00002377 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002378 SkFixed storage[2];
2379 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2380 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002381 dx = SkFixedToScalar(storage[0]);
2382 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002383 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002384 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002385 dx = matrix.getScaleX();
2386 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002387 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002388
reed@google.com61eb0402011-04-15 12:11:12 +00002389 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002390 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2391 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002392 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002393 fx += dx;
2394 fy += dy;
2395 }
reed@google.com61eb0402011-04-15 12:11:12 +00002396 } else { // perspective case
2397 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002398 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2399 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002400
reed@google.com51baf5a2011-09-21 13:38:36 +00002401 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002402 index >>= (8 - kCache16Bits);
2403 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002404 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002405 }
2406 }
2407}
2408
reed@google.com61eb0402011-04-15 12:11:12 +00002409///////////////////////////////////////////////////////////////////////////////
2410///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002411
2412// assumes colors is SkColor* and pos is SkScalar*
2413#define EXPAND_1_COLOR(count) \
2414 SkColor tmp[2]; \
2415 do { \
2416 if (1 == count) { \
2417 tmp[0] = tmp[1] = colors[0]; \
2418 colors = tmp; \
2419 pos = NULL; \
2420 count = 2; \
2421 } \
2422 } while (0)
2423
reed@google.com61eb0402011-04-15 12:11:12 +00002424SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2425 const SkColor colors[],
2426 const SkScalar pos[], int colorCount,
2427 SkShader::TileMode mode,
2428 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002429 if (NULL == pts || NULL == colors || colorCount < 1) {
2430 return NULL;
2431 }
2432 EXPAND_1_COLOR(colorCount);
2433
reed@android.comab840b82009-07-01 17:00:03 +00002434 return SkNEW_ARGS(Linear_Gradient,
2435 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002436}
2437
reed@google.com61eb0402011-04-15 12:11:12 +00002438SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2439 const SkColor colors[],
2440 const SkScalar pos[], int colorCount,
2441 SkShader::TileMode mode,
2442 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002443 if (radius <= 0 || NULL == colors || colorCount < 1) {
2444 return NULL;
2445 }
2446 EXPAND_1_COLOR(colorCount);
2447
reed@android.comab840b82009-07-01 17:00:03 +00002448 return SkNEW_ARGS(Radial_Gradient,
2449 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002450}
2451
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002452SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2453 SkScalar startRadius,
2454 const SkPoint& end,
2455 SkScalar endRadius,
2456 const SkColor colors[],
2457 const SkScalar pos[],
2458 int colorCount,
2459 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002460 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002461 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2462 return NULL;
2463 }
2464 EXPAND_1_COLOR(colorCount);
2465
2466 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002467 (start, startRadius, end, endRadius, colors, pos,
2468 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002469}
2470
reed@android.com8a1c16f2008-12-17 15:59:43 +00002471SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2472 const SkColor colors[],
2473 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002474 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002475 if (NULL == colors || count < 1) {
2476 return NULL;
2477 }
2478 EXPAND_1_COLOR(count);
2479
2480 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2481}
2482
caryclark@google.comd26147a2011-12-15 14:16:43 +00002483SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2484 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2485 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002486 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002487 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2488SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END