blob: c9929b4180ad4b17962f16388ec8ae626ba777f9 [file] [log] [blame]
reed@google.com0e734bd2011-12-08 17:24:44 +00001
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkGradientShader.h"
tomhudson@google.come8c984d2012-01-09 13:45:36 +000011#include "SkClampRange.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000013#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000014#include "SkUnitMapper.h"
15#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000016#include "SkTemplates.h"
17#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000018
reed@google.com0e734bd2011-12-08 17:24:44 +000019#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
20 #define USE_DITHER_32BIT_GRADIENT
21#endif
22
reed@google.com5eb158d2011-04-15 15:50:34 +000023static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
24 int count) {
25 if (count > 0) {
26 if (v0 == v1) {
27 sk_memset32(dst, v0, count);
28 } else {
29 int pairs = count >> 1;
30 for (int i = 0; i < pairs; i++) {
31 *dst++ = v0;
32 *dst++ = v1;
33 }
34 if (count & 1) {
35 *dst = v0;
36 }
37 }
38 }
39}
40
reed@google.com61eb0402011-04-15 12:11:12 +000041///////////////////////////////////////////////////////////////////////////////
tomhudson@google.com9ce767c2011-04-25 20:49:39 +000042// Can't use a two-argument function with side effects like this in a
43// constructor's initializer's argument list because the order of
44// evaluations in that context is undefined (and backwards on linux/gcc).
45static SkPoint unflatten_point(SkReader32& buffer) {
46 SkPoint retval;
47 retval.fX = buffer.readScalar();
48 retval.fY = buffer.readScalar();
49 return retval;
50}
51
reed@google.comc98a0aa2012-02-02 19:33:08 +000052// Clamp
reed@android.com8a1c16f2008-12-17 15:59:43 +000053
reed@android.com41bccf52009-04-03 13:33:51 +000054static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000055 return SkClampMax(x, 0xFFFF);
56}
57
reed@google.comc98a0aa2012-02-02 19:33:08 +000058// Repeat
59
reed@android.com41bccf52009-04-03 13:33:51 +000060static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 return x & 0xFFFF;
62}
63
reed@google.comc98a0aa2012-02-02 19:33:08 +000064static inline int repeat_bits(int x, const int bits) {
65 return x & ((1 << bits) - 1);
66}
67
68static inline int repeat_8bits(int x) {
69 return x & 0xFF;
70}
71
72// Mirror
73
epoger@google.com5468c902012-02-02 20:41:45 +000074// Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly.
75// See http://code.google.com/p/skia/issues/detail?id=472
76#if defined(_MSC_VER) && (_MSC_VER >= 1600)
77#pragma optimize("", off)
78#endif
79
reed@android.com41bccf52009-04-03 13:33:51 +000080static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000081 int s = x << 15 >> 31;
82 return (x ^ s) & 0xFFFF;
83}
84
reed@android.com200645d2009-12-14 16:41:57 +000085static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000086#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000087 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000089 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000090#else
reed@android.com200645d2009-12-14 16:41:57 +000091 int s = x << (31 - bits) >> 31;
92 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000093#endif
94}
95
reed@android.com41bccf52009-04-03 13:33:51 +000096static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000097#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000098 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +0000100 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 return x & 255;
102#else
103 int s = x << 23 >> 31;
104 return (x ^ s) & 0xFF;
105#endif
106}
107
epoger@google.com5468c902012-02-02 20:41:45 +0000108#if defined(_MSC_VER) && (_MSC_VER >= 1600)
109#pragma optimize("", on)
110#endif
111
reed@google.com61eb0402011-04-15 12:11:12 +0000112///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113
reed@google.comc98a0aa2012-02-02 19:33:08 +0000114typedef SkFixed (*TileProc)(SkFixed);
115
116static const TileProc gTileProcs[] = {
117 clamp_tileproc,
118 repeat_tileproc,
119 mirror_tileproc
120};
121
122///////////////////////////////////////////////////////////////////////////////
123///////////////////////////////////////////////////////////////////////////////
124
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125class Gradient_Shader : public SkShader {
126public:
127 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000128 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 virtual ~Gradient_Shader();
130
131 // overrides
reed@google.com7716afb2011-12-07 15:17:50 +0000132 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
133 virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000134 virtual bool isOpaque() const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000135
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000136 enum {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000137 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
138 /// it, use a larger cache.
139 kCache16Bits = 8,
140 kGradient16Length = (1 << kCache16Bits),
141 /// Each cache gets 1 extra entry at the end so we don't have to
142 /// test for end-of-cache in lerps. This is also the value used
143 /// to stride *writes* into the dither cache; it must not be zero.
144 /// Total space for a cache is 2x kCache16Count entries: one
145 /// regular cache, one for dithering.
146 kCache16Count = kGradient16Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000147 kCache16Shift = 16 - kCache16Bits,
148 kSqrt16Shift = 8 - kCache16Bits,
149
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000150 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
151 /// it, use a larger cache.
152 kCache32Bits = 8,
153 kGradient32Length = (1 << kCache32Bits),
154 /// Each cache gets 1 extra entry at the end so we don't have to
155 /// test for end-of-cache in lerps. This is also the value used
156 /// to stride *writes* into the dither cache; it must not be zero.
157 /// Total space for a cache is 2x kCache32Count entries: one
158 /// regular cache, one for dithering.
159 kCache32Count = kGradient32Length + 1,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000160 kCache32Shift = 16 - kCache32Bits,
161 kSqrt32Shift = 8 - kCache32Bits,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000162
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000163 /// This value is used to *read* the dither cache; it may be 0
164 /// if dithering is disabled.
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000165#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000166 kDitherStride32 = kCache32Count,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000167#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000168 kDitherStride32 = 0,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000169#endif
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000170 kDitherStride16 = kCache16Count,
171 kLerpRemainderMask32 = (1 << (16 - kCache32Bits)) - 1
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000172 };
173
174
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175protected:
176 Gradient_Shader(SkFlattenableReadBuffer& );
177 SkUnitMapper* fMapper;
178 SkMatrix fPtsToUnit; // set by subclass
179 SkMatrix fDstToIndex;
180 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000181 TileMode fTileMode;
182 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000183 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184 uint8_t fDstToIndexClass;
185 uint8_t fFlags;
186
187 struct Rec {
188 SkFixed fPos; // 0...1
189 uint32_t fScale; // (1 << 24) / range
190 };
191 Rec* fRecs;
192
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000194 const uint16_t* getCache16() const;
195 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196
reed@google.com7c2f27d2011-03-07 19:29:00 +0000197 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000198 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000199
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200private:
201 enum {
202 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
203
reed@android.com1c12abe2009-07-02 15:01:02 +0000204 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000205 };
206 SkColor fStorage[(kStorageSize + 3) >> 2];
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000207 SkColor* fOrigColors; // original colors, before modulation by paint in setContext
208 bool fColorsAreOpaque;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209
reed@google.com7c2f27d2011-03-07 19:29:00 +0000210 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
211 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212
reed@google.com7c2f27d2011-03-07 19:29:00 +0000213 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
214 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000215 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 +0000216
reed@android.com512a8762009-12-14 15:25:36 +0000217 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000218 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
219 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000220 void setCacheAlpha(U8CPU alpha) const;
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000221 void initCommon();
reed@android.com512a8762009-12-14 15:25:36 +0000222
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223 typedef SkShader INHERITED;
224};
225
reed@android.com41bccf52009-04-03 13:33:51 +0000226static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000227 SkASSERT(x >= 0 && x <= SK_Scalar1);
228
229#ifdef SK_SCALAR_IS_FLOAT
230 return (unsigned)(x * 0xFFFF);
231#else
232 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
233#endif
234}
235
reed@android.com41bccf52009-04-03 13:33:51 +0000236Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
237 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000238 SkASSERT(colorCount > 1);
239
240 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
241
242 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000243 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
246 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
247 fTileMode = mode;
248 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000249
reed@android.com41bccf52009-04-03 13:33:51 +0000250 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000251 fCache32 = NULL;
252 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000253
reed@android.com41bccf52009-04-03 13:33:51 +0000254 /* Note: we let the caller skip the first and/or last position.
255 i.e. pos[0] = 0.3, pos[1] = 0.7
256 In these cases, we insert dummy entries to ensure that the final data
257 will be bracketed by [0, 1].
258 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
259
260 Thus colorCount (the caller's value, and fColorCount (our value) may
261 differ by up to 2. In the above example:
262 colorCount = 2
263 fColorCount = 4
264 */
265 fColorCount = colorCount;
266 // check if we need to add in dummy start and/or end position/colors
267 bool dummyFirst = false;
268 bool dummyLast = false;
269 if (pos) {
270 dummyFirst = pos[0] != 0;
271 dummyLast = pos[colorCount - 1] != SK_Scalar1;
272 fColorCount += dummyFirst + dummyLast;
273 }
274
275 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000276 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000277 fOrigColors = reinterpret_cast<SkColor*>(
278 sk_malloc_throw(size * fColorCount));
279 }
280 else {
281 fOrigColors = fStorage;
282 }
283
284 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285 {
reed@android.com41bccf52009-04-03 13:33:51 +0000286 SkColor* origColors = fOrigColors;
287 if (dummyFirst) {
288 *origColors++ = colors[0];
289 }
290 memcpy(origColors, colors, colorCount * sizeof(SkColor));
291 if (dummyLast) {
292 origColors += colorCount;
293 *origColors = colors[colorCount - 1];
294 }
295 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296
reed@android.com1c12abe2009-07-02 15:01:02 +0000297 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000298 if (fColorCount > 2) {
299 Rec* recs = fRecs;
300 recs->fPos = 0;
301 // recs->fScale = 0; // unused;
302 recs += 1;
303 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 /* We need to convert the user's array of relative positions into
305 fixed-point positions and scale factors. We need these results
306 to be strictly monotonic (no two values equal or out of order).
307 Hence this complex loop that just jams a zero for the scale
308 value if it sees a segment out of order, and it assures that
309 we start at 0 and end at 1.0
310 */
311 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000312 int startIndex = dummyFirst ? 0 : 1;
313 int count = colorCount + dummyLast;
314 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315 // force the last value to be 1.0
316 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000317 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000319 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 }
reed@android.com41bccf52009-04-03 13:33:51 +0000322 // pin curr withing range
323 if (curr < 0) {
324 curr = 0;
325 } else if (curr > SK_Fixed1) {
326 curr = SK_Fixed1;
327 }
328 recs->fPos = curr;
329 if (curr > prev) {
330 recs->fScale = (1 << 24) / (curr - prev);
331 } else {
332 recs->fScale = 0; // ignore this segment
333 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334 // get ready for the next value
335 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000336 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 }
reed@android.com41bccf52009-04-03 13:33:51 +0000338 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 SkFixed dp = SK_Fixed1 / (colorCount - 1);
340 SkFixed p = dp;
341 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000342 for (int i = 1; i < colorCount; i++) {
343 recs->fPos = p;
344 recs->fScale = scale;
345 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346 p += dp;
347 }
348 }
349 }
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000350 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351}
352
353Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000354 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 fCacheAlpha = 256;
356
357 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
358
359 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000360 fCache32 = NULL;
361 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362
reed@android.com41bccf52009-04-03 13:33:51 +0000363 int colorCount = fColorCount = buffer.readU32();
364 if (colorCount > kColorStorageCount) {
365 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
366 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
367 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000369 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000370 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371
372 fTileMode = (TileMode)buffer.readU8();
373 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000374 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000375 if (colorCount > 2) {
376 Rec* recs = fRecs;
377 recs[0].fPos = 0;
378 for (int i = 1; i < colorCount; i++) {
379 recs[i].fPos = buffer.readS32();
380 recs[i].fScale = buffer.readU32();
381 }
382 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000383 SkReadMatrix(&buffer, &fPtsToUnit);
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000384 this->initCommon();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000385}
386
reed@android.com41bccf52009-04-03 13:33:51 +0000387Gradient_Shader::~Gradient_Shader() {
388 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000390 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000391 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000392 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000393 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000394 }
reed@google.com82065d62011-02-07 15:30:46 +0000395 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000396}
397
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000398void Gradient_Shader::initCommon() {
399 fFlags = 0;
400 unsigned colorAlpha = 0xFF;
401 for (int i = 0; i < fColorCount; i++) {
402 colorAlpha &= SkColorGetA(fOrigColors[i]);
403 }
404 fColorsAreOpaque = colorAlpha == 0xFF;
405}
406
reed@android.com41bccf52009-04-03 13:33:51 +0000407void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000408 this->INHERITED::flatten(buffer);
409 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000410 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
412 buffer.write8(fTileMode);
413 if (fColorCount > 2) {
414 Rec* recs = fRecs;
415 for (int i = 1; i < fColorCount; i++) {
416 buffer.write32(recs[i].fPos);
417 buffer.write32(recs[i].fScale);
418 }
419 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000420 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000421}
422
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000423bool Gradient_Shader::isOpaque() const {
424 return fColorsAreOpaque;
425}
426
reed@android.com8a1c16f2008-12-17 15:59:43 +0000427bool Gradient_Shader::setContext(const SkBitmap& device,
428 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000429 const SkMatrix& matrix) {
430 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000432 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433
434 const SkMatrix& inverse = this->getTotalInverse();
435
436 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
437 return false;
438 }
439
440 fDstToIndexProc = fDstToIndex.getMapXYProc();
441 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
442
443 // now convert our colors in to PMColors
444 unsigned paintAlpha = this->getPaintAlpha();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000445
446 fFlags = this->INHERITED::getFlags();
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000447 if (fColorsAreOpaque && paintAlpha == 0xFF) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000448 fFlags |= kOpaqueAlpha_Flag;
449 }
450 // we can do span16 as long as our individual colors are opaque,
451 // regardless of the paint's alpha
junov@chromium.orgb6e16192011-12-09 15:48:03 +0000452 if (fColorsAreOpaque) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000453 fFlags |= kHasSpan16_Flag;
454 }
455
reed@google.com95eed982011-07-05 17:01:56 +0000456 this->setCacheAlpha(paintAlpha);
457 return true;
458}
459
460void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000461 // if the new alpha differs from the previous time we were called, inval our cache
462 // this will trigger the cache to be rebuilt.
463 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000464 if (fCacheAlpha != alpha) {
465 fCache16 = NULL; // inval the cache
466 fCache32 = NULL; // inval the cache
467 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000468 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000469 if (fCache32PixelRef) {
470 fCache32PixelRef->notifyPixelsChanged();
471 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000472 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000473}
474
reed@android.com41bccf52009-04-03 13:33:51 +0000475static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000476 SkASSERT(a == SkToU8(a));
477 SkASSERT(b == SkToU8(b));
478 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000479 return a + ((b - a) * scale >> 8);
480}
481
reed@android.com41bccf52009-04-03 13:33:51 +0000482static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
483 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000484#if 0
485 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
486 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
487 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
488 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
489
490 return SkPackARGB32(a, r, g, b);
491#else
492 int otherBlend = 256 - blend;
493
494#if 0
495 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
496 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
497 SkASSERT((t0 & t1) == 0);
498 return t0 | t1;
499#else
500 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
501 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
502#endif
503
504#endif
505}
506
507#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
508
reed@android.com41bccf52009-04-03 13:33:51 +0000509/** We take the original colors, not our premultiplied PMColors, since we can
510 build a 16bit table as long as the original colors are opaque, even if the
511 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000512*/
reed@android.com512a8762009-12-14 15:25:36 +0000513void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
514 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000515 SkASSERT(count > 1);
516 SkASSERT(SkColorGetA(c0) == 0xFF);
517 SkASSERT(SkColorGetA(c1) == 0xFF);
518
519 SkFixed r = SkColorGetR(c0);
520 SkFixed g = SkColorGetG(c0);
521 SkFixed b = SkColorGetB(c0);
522
523 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
524 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
525 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
526
527 r = SkIntToFixed(r) + 0x8000;
528 g = SkIntToFixed(g) + 0x8000;
529 b = SkIntToFixed(b) + 0x8000;
530
531 do {
532 unsigned rr = r >> 16;
533 unsigned gg = g >> 16;
534 unsigned bb = b >> 16;
535 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000536 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000537 cache += 1;
538 r += dr;
539 g += dg;
540 b += db;
541 } while (--count != 0);
542}
543
reed@google.com55b8e8c2011-01-13 16:22:35 +0000544/*
545 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
546 * semantics of how we 2x2 dither 32->16
547 */
548static inline U8CPU dither_fixed_to_8(SkFixed n) {
549 n >>= 8;
550 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
551}
552
553/*
554 * For dithering with premultiply, we want to ceiling the alpha component,
555 * to ensure that it is always >= any color component.
556 */
557static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
558 n >>= 8;
559 return ((n << 1) - (n | (n >> 8))) >> 8;
560}
561
562void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
563 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000564 SkASSERT(count > 1);
565
reed@android.com1c12abe2009-07-02 15:01:02 +0000566 // need to apply paintAlpha to our two endpoints
567 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
568 SkFixed da;
569 {
570 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
571 da = SkIntToFixed(tmp - a) / (count - 1);
572 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573
reed@android.com1c12abe2009-07-02 15:01:02 +0000574 SkFixed r = SkColorGetR(c0);
575 SkFixed g = SkColorGetG(c0);
576 SkFixed b = SkColorGetB(c0);
577 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
578 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
579 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000580
581 a = SkIntToFixed(a) + 0x8000;
582 r = SkIntToFixed(r) + 0x8000;
583 g = SkIntToFixed(g) + 0x8000;
584 b = SkIntToFixed(b) + 0x8000;
585
586 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000587 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000588 cache[kCache32Count] =
589 SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
590 dither_fixed_to_8(r),
591 dither_fixed_to_8(g),
592 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000593 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000594 a += da;
595 r += dr;
596 g += dg;
597 b += db;
598 } while (--count != 0);
599}
600
reed@android.com41bccf52009-04-03 13:33:51 +0000601static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000602 SkASSERT((unsigned)x <= SK_Fixed1);
603 return x - (x >> 16);
604}
605
reed@android.com200645d2009-12-14 16:41:57 +0000606static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000607 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000608 if (6 == bits) {
609 return (x << 10) | (x << 4) | (x >> 2);
610 }
611 if (8 == bits) {
612 return (x << 8) | x;
613 }
614 sk_throw();
615 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616}
617
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000618/** We duplicate the last value in each half of the cache so that
619 interpolation doesn't have to special-case being at the last point.
620*/
621static void complete_16bit_cache(uint16_t* cache, int stride) {
622 cache[stride - 1] = cache[stride - 2];
623 cache[2 * stride - 1] = cache[2 * stride - 2];
624}
625
reed@google.com7c2f27d2011-03-07 19:29:00 +0000626const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000627 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000628 // double the count for dither entries
629 const int entryCount = kCache16Count * 2;
630 const size_t allocSize = sizeof(uint16_t) * entryCount;
631
reed@android.com3c9b2a42009-08-27 19:28:37 +0000632 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000633 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000634 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000635 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000636 if (fColorCount == 2) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000637 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
638 kGradient16Length);
reed@android.com41bccf52009-04-03 13:33:51 +0000639 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000640 Rec* rec = fRecs;
641 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000642 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000643 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 SkASSERT(nextIndex < kCache16Count);
645
646 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000647 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 prevIndex = nextIndex;
649 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000650 // one extra space left over at the end for complete_16bit_cache()
651 SkASSERT(prevIndex == kGradient16Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 }
653
reed@android.com41bccf52009-04-03 13:33:51 +0000654 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000655 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 uint16_t* linear = fCache16; // just computed linear data
657 uint16_t* mapped = fCache16Storage; // storage for mapped data
658 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000659 for (int i = 0; i < kGradient16Length; i++) {
reed@android.com200645d2009-12-14 16:41:57 +0000660 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000661 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000662 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663 }
664 sk_free(fCache16);
665 fCache16 = fCache16Storage;
666 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000667 complete_16bit_cache(fCache16, kCache16Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668 }
669 return fCache16;
670}
671
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000672/** We duplicate the last value in each half of the cache so that
673 interpolation doesn't have to special-case being at the last point.
674*/
675static void complete_32bit_cache(SkPMColor* cache, int stride) {
676 cache[stride - 1] = cache[stride - 2];
677 cache[2 * stride - 1] = cache[2 * stride - 2];
678}
679
reed@google.com7c2f27d2011-03-07 19:29:00 +0000680const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000681 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000682 // double the count for dither entries
683 const int entryCount = kCache32Count * 2;
684 const size_t allocSize = sizeof(SkPMColor) * entryCount;
685
reed@google.comdc731fd2010-12-23 15:19:47 +0000686 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000687 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
688 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000689 }
690 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000691 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000692 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000693 kGradient32Length, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000694 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 Rec* rec = fRecs;
696 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000697 for (int i = 1; i < fColorCount; i++) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000698 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000699 SkASSERT(nextIndex < kGradient32Length);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700
701 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000702 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
703 fOrigColors[i],
704 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705 prevIndex = nextIndex;
706 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000707 SkASSERT(prevIndex == kGradient32Length - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708 }
709
reed@android.com41bccf52009-04-03 13:33:51 +0000710 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000711 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000712 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000714 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715 SkUnitMapper* map = fMapper;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000716 for (int i = 0; i < kGradient32Length; i++) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000717 int index = map->mapUnit16((i << 8) | i) >> 8;
718 mapped[i] = linear[index];
719 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000720 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000721 fCache32PixelRef->unref();
722 fCache32PixelRef = newPR;
723 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724 }
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000725 complete_32bit_cache(fCache32, kCache32Count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726 }
727 return fCache32;
728}
729
reed@google.comdc731fd2010-12-23 15:19:47 +0000730/*
731 * Because our caller might rebuild the same (logically the same) gradient
732 * over and over, we'd like to return exactly the same "bitmap" if possible,
733 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
734 * To do that, we maintain a private cache of built-bitmaps, based on our
735 * colors and positions. Note: we don't try to flatten the fMapper, so if one
736 * is present, we skip the cache for now.
737 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000738void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000739 // our caller assumes no external alpha, so we ensure that our cache is
740 // built with 0xFF
741 this->setCacheAlpha(0xFF);
742
reed@google.comdc731fd2010-12-23 15:19:47 +0000743 // don't have a way to put the mapper into our cache-key yet
744 if (fMapper) {
745 // force our cahce32pixelref to be built
746 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000747 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000748 bitmap->setPixelRef(fCache32PixelRef);
749 return;
750 }
751
752 // build our key: [numColors + colors[] + {positions[]} ]
753 int count = 1 + fColorCount;
754 if (fColorCount > 2) {
755 count += fColorCount - 1; // fRecs[].fPos
756 }
757
758 SkAutoSTMalloc<16, int32_t> storage(count);
759 int32_t* buffer = storage.get();
760
761 *buffer++ = fColorCount;
762 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
763 buffer += fColorCount;
764 if (fColorCount > 2) {
765 for (int i = 1; i < fColorCount; i++) {
766 *buffer++ = fRecs[i].fPos;
767 }
768 }
769 SkASSERT(buffer - storage.get() == count);
770
771 ///////////////////////////////////
772
digit@google.com1771cbf2012-01-26 21:26:40 +0000773 SK_DECLARE_STATIC_MUTEX(gMutex);
reed@google.comdc731fd2010-12-23 15:19:47 +0000774 static SkBitmapCache* gCache;
775 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
776 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
777 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000778
reed@google.comdc731fd2010-12-23 15:19:47 +0000779 if (NULL == gCache) {
780 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
781 }
782 size_t size = count * sizeof(int32_t);
783
784 if (!gCache->find(storage.get(), size, bitmap)) {
785 // force our cahce32pixelref to be built
786 (void)this->getCache32();
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000787 // Only expose the linear section of the cache; don't let the caller
788 // know about the padding at the end to make interpolation faster.
789 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
reed@google.comdc731fd2010-12-23 15:19:47 +0000790 bitmap->setPixelRef(fCache32PixelRef);
791
792 gCache->add(storage.get(), size, *bitmap);
793 }
794}
795
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000796void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
797 if (info) {
798 if (info->fColorCount >= fColorCount) {
799 if (info->fColors) {
800 memcpy(info->fColors, fOrigColors,
801 fColorCount * sizeof(SkColor));
802 }
803 if (info->fColorOffsets) {
804 if (fColorCount == 2) {
805 info->fColorOffsets[0] = 0;
806 info->fColorOffsets[1] = SK_Scalar1;
807 } else if (fColorCount > 2) {
808 for (int i = 0; i < fColorCount; i++)
809 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
810 }
811 }
812 }
813 info->fColorCount = fColorCount;
814 info->fTileMode = fTileMode;
815 }
816}
817
reed@google.com61eb0402011-04-15 12:11:12 +0000818///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819
reed@android.com41bccf52009-04-03 13:33:51 +0000820static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000821 SkVector vec = pts[1] - pts[0];
822 SkScalar mag = vec.length();
823 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
824
825 vec.scale(inv);
826 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
827 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
828 matrix->postScale(inv, inv);
829}
830
831///////////////////////////////////////////////////////////////////////////////
832
833class Linear_Gradient : public Gradient_Shader {
834public:
835 Linear_Gradient(const SkPoint pts[2],
836 const SkColor colors[], const SkScalar pos[], int colorCount,
837 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000838 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
839 fStart(pts[0]),
840 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 {
842 pts_to_unit_matrix(pts, &fPtsToUnit);
843 }
reed@android.com9b46e772009-06-05 12:24:41 +0000844
reed@google.com7716afb2011-12-07 15:17:50 +0000845 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
846 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
847 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
848 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*,
849 SkScalar* twoPointRadialParams) const SK_OVERRIDE;
850 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851
reed@google.com55b8e8c2011-01-13 16:22:35 +0000852 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 return SkNEW_ARGS(Linear_Gradient, (buffer));
854 }
855
reed@google.com7716afb2011-12-07 15:17:50 +0000856 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000857 this->INHERITED::flatten(buffer);
858 buffer.writeScalar(fStart.fX);
859 buffer.writeScalar(fStart.fY);
860 buffer.writeScalar(fEnd.fX);
861 buffer.writeScalar(fEnd.fY);
862 }
863
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000865 Linear_Gradient(SkFlattenableReadBuffer& buffer)
866 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000867 fStart(unflatten_point(buffer)),
868 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000869 }
reed@google.com7716afb2011-12-07 15:17:50 +0000870 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871
872private:
873 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000874 const SkPoint fStart;
875 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000876};
877
reed@android.com5119bdb2009-06-12 21:27:03 +0000878bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
879 const SkMatrix& matrix) {
880 if (!this->INHERITED::setContext(device, paint, matrix)) {
881 return false;
882 }
883
884 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
885 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000886 fFlags |= SkShader::kConstInY32_Flag;
887 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
888 // only claim this if we do have a 16bit mode (i.e. none of our
889 // colors have alpha), and if we are not dithering (which obviously
890 // is not const in Y).
891 fFlags |= SkShader::kConstInY16_Flag;
892 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000893 }
894 return true;
895}
896
reed@google.com5eb158d2011-04-15 15:50:34 +0000897#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000898 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000899 unsigned fi = fx >> Gradient_Shader::kCache32Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000900 SkASSERT(fi <= 0xFF); \
901 fx += dx; \
902 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000903 toggle ^= Gradient_Shader::kDitherStride32; \
reed@google.com13659f12011-04-18 19:59:38 +0000904 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000905
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000906namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +0000907
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000908typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +0000909 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000910 int toggle, int count);
911
tomhudson@google.com13a847a2012-01-20 13:59:14 +0000912// This function is deprecated, and will be replaced by
913// shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it.
914void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
915 SkPMColor* SK_RESTRICT dstC,
916 const SkPMColor* SK_RESTRICT cache,
917 int toggle, int count) {
918 // We're a vertical gradient, so no change in a span.
919 // If colors change sharply across the gradient, dithering is
920 // insufficient (it subsamples the color space) and we need to lerp.
921 unsigned fullIndex = proc(fx);
922 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
923 sk_memset32_dither(dstC,
924 cache[toggle + fi],
925 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
926 count);
927}
928
929// Linear interpolation (lerp) is unnecessary if there are no sharp
930// discontinuities in the gradient - which must be true if there are
931// only 2 colors - but it's cheap.
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000932void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
933 SkPMColor* SK_RESTRICT dstC,
934 const SkPMColor* SK_RESTRICT cache,
935 int toggle, int count) {
936 // We're a vertical gradient, so no change in a span.
937 // If colors change sharply across the gradient, dithering is
938 // insufficient (it subsamples the color space) and we need to lerp.
939 unsigned fullIndex = proc(fx);
940 unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
941 unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32;
942 SkPMColor lerp =
943 SkFastFourByteInterp(
944 cache[toggle + fi + 1],
945 cache[toggle + fi], remainder);
946 SkPMColor dlerp =
947 SkFastFourByteInterp(
948 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1],
949 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], remainder);
950 sk_memset32_dither(dstC, lerp, dlerp, count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000951}
952
953void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
954 SkPMColor* SK_RESTRICT dstC,
955 const SkPMColor* SK_RESTRICT cache,
956 int toggle, int count) {
957 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000958 range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000959
960 if ((count = range.fCount0) > 0) {
961 sk_memset32_dither(dstC,
962 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000963 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000964 count);
965 dstC += count;
966 }
967 if ((count = range.fCount1) > 0) {
968 int unroll = count >> 3;
969 fx = range.fFx1;
970 for (int i = 0; i < unroll; i++) {
971 NO_CHECK_ITER; NO_CHECK_ITER;
972 NO_CHECK_ITER; NO_CHECK_ITER;
973 NO_CHECK_ITER; NO_CHECK_ITER;
974 NO_CHECK_ITER; NO_CHECK_ITER;
975 }
976 if ((count &= 7) > 0) {
977 do {
978 NO_CHECK_ITER;
979 } while (--count != 0);
980 }
981 }
982 if ((count = range.fCount2) > 0) {
983 sk_memset32_dither(dstC,
984 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000985 cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000986 count);
987 }
988}
989
tomhudson@google.come8c984d2012-01-09 13:45:36 +0000990void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
991 SkPMColor* SK_RESTRICT dstC,
992 const SkPMColor* SK_RESTRICT cache,
993 int toggle, int count) {
994 do {
995 unsigned fi = mirror_8bits(fx >> 8);
996 SkASSERT(fi <= 0xFF);
997 fx += dx;
998 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +0000999 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001000 } while (--count != 0);
1001}
1002
1003void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1004 SkPMColor* SK_RESTRICT dstC,
1005 const SkPMColor* SK_RESTRICT cache,
1006 int toggle, int count) {
1007 do {
1008 unsigned fi = repeat_8bits(fx >> 8);
1009 SkASSERT(fi <= 0xFF);
1010 fx += dx;
1011 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001012 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001013 } while (--count != 0);
1014}
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001015
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001016}
1017
1018void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1019 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001020 SkASSERT(count > 0);
1021
1022 SkPoint srcPt;
1023 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1024 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001025 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com0e734bd2011-12-08 17:24:44 +00001026#ifdef USE_DITHER_32BIT_GRADIENT
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001027 int toggle = ((x ^ y) & 1) * kDitherStride32;
reed@google.com0e734bd2011-12-08 17:24:44 +00001028#else
1029 int toggle = 0;
reed@google.com0e734bd2011-12-08 17:24:44 +00001030#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001031
reed@android.comc552a432009-06-12 20:02:50 +00001032 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001033 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1034 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001035 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1036
reed@android.comc552a432009-06-12 20:02:50 +00001037 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001038 SkFixed dxStorage[1];
1039 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1040 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +00001041 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001042 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1043 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1044 }
1045
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001046 LinearShadeProc shadeProc = shadeSpan_linear_repeat;
reed@android.comc552a432009-06-12 20:02:50 +00001047 if (SkFixedNearlyZero(dx)) {
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001048#ifdef SK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS
1049 if (fColorCount > 2) {
1050 shadeProc = shadeSpan_linear_vertical_lerp;
1051 } else {
1052 shadeProc = shadeSpan_linear_vertical;
1053 }
1054#else
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001055 shadeProc = shadeSpan_linear_vertical_lerp;
tomhudson@google.com13a847a2012-01-20 13:59:14 +00001056#endif
reed@android.comc552a432009-06-12 20:02:50 +00001057 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001058 shadeProc = shadeSpan_linear_clamp;
reed@android.comc552a432009-06-12 20:02:50 +00001059 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001060 shadeProc = shadeSpan_linear_mirror;
reed@android.comc552a432009-06-12 20:02:50 +00001061 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001062 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001064 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comc552a432009-06-12 20:02:50 +00001065 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001066 SkScalar dstX = SkIntToScalar(x);
1067 SkScalar dstY = SkIntToScalar(y);
1068 do {
1069 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1070 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1071 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001072 *dstC++ = cache[toggle + (fi >> kCache32Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001073 toggle ^= Gradient_Shader::kDitherStride32;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074 dstX += SK_Scalar1;
1075 } while (--count != 0);
1076 }
1077}
1078
reed@google.com55b8e8c2011-01-13 16:22:35 +00001079SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +00001080 SkMatrix* matrix,
1081 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +00001082 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001083 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001084 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085 }
1086 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001087 matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001088 matrix->preConcat(fPtsToUnit);
1089 }
1090 if (xy) {
1091 xy[0] = fTileMode;
1092 xy[1] = kClamp_TileMode;
1093 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001094 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001095}
1096
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001097SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
1098 if (info) {
1099 commonAsAGradient(info);
1100 info->fPoint[0] = fStart;
1101 info->fPoint[1] = fEnd;
1102 }
1103 return kLinear_GradientType;
1104}
1105
reed@android.com3c9b2a42009-08-27 19:28:37 +00001106static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
1107 int count) {
1108 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001109 *dst++ = value;
1110 count -= 1;
1111 SkTSwap(value, other);
1112 }
1113
1114 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001115
reed@android.com3c9b2a42009-08-27 19:28:37 +00001116 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001117 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001118 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120
reed@google.com5eb158d2011-04-15 15:50:34 +00001121#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +00001122 do { \
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001123 unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001124 SkASSERT(fi < Gradient_Shader::kCache16Count); \
reed@google.com5eb158d2011-04-15 15:50:34 +00001125 fx += dx; \
1126 *dstC++ = cache[toggle + fi]; \
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001127 toggle ^= Gradient_Shader::kDitherStride16; \
reed@google.com13659f12011-04-18 19:59:38 +00001128 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001129
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001130namespace {
reed@google.com5eb158d2011-04-15 15:50:34 +00001131
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001132typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001133 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001134 int toggle, int count);
1135
1136void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
1137 uint16_t* SK_RESTRICT dstC,
1138 const uint16_t* SK_RESTRICT cache,
1139 int toggle, int count) {
1140 // we're a vertical gradient, so no change in a span
1141 unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001142 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001143 dither_memset16(dstC, cache[toggle + fi],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001144 cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001145
1146}
1147
1148void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
1149 uint16_t* SK_RESTRICT dstC,
1150 const uint16_t* SK_RESTRICT cache,
1151 int toggle, int count) {
1152 SkClampRange range;
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001153 range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001154
1155 if ((count = range.fCount0) > 0) {
1156 dither_memset16(dstC,
1157 cache[toggle + range.fV0],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001158 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001159 count);
1160 dstC += count;
1161 }
1162 if ((count = range.fCount1) > 0) {
1163 int unroll = count >> 3;
1164 fx = range.fFx1;
1165 for (int i = 0; i < unroll; i++) {
1166 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1167 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1168 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1169 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1170 }
1171 if ((count &= 7) > 0) {
1172 do {
1173 NO_CHECK_ITER_16;
1174 } while (--count != 0);
1175 }
1176 }
1177 if ((count = range.fCount2) > 0) {
1178 dither_memset16(dstC,
1179 cache[toggle + range.fV1],
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001180 cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001181 count);
1182 }
1183}
1184
1185void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
1186 uint16_t* SK_RESTRICT dstC,
1187 const uint16_t* SK_RESTRICT cache,
1188 int toggle, int count) {
1189 do {
1190 unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
1191 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001192 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001193 fx += dx;
1194 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001195 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001196 } while (--count != 0);
1197}
1198
1199void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
1200 uint16_t* SK_RESTRICT dstC,
1201 const uint16_t* SK_RESTRICT cache,
1202 int toggle, int count) {
1203 SkASSERT(proc == repeat_tileproc);
1204 do {
1205 unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
1206 Gradient_Shader::kCache16Bits);
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001207 SkASSERT(fi < Gradient_Shader::kCache16Count);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001208 fx += dx;
1209 *dstC++ = cache[toggle + fi];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001210 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001211 } while (--count != 0);
1212}
1213}
1214
1215void Linear_Gradient::shadeSpan16(int x, int y,
1216 uint16_t* SK_RESTRICT dstC, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217 SkASSERT(count > 0);
1218
1219 SkPoint srcPt;
1220 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1221 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001222 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001223 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001225 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001226 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1227 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1229
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001230 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231 SkFixed dxStorage[1];
1232 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1233 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001234 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1236 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1237 }
1238
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001239 LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001240 if (SkFixedNearlyZero(dx)) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001241 shadeProc = shadeSpan16_linear_vertical;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001242 } else if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001243 shadeProc = shadeSpan16_linear_clamp;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001244 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001245 shadeProc = shadeSpan16_linear_mirror;
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001246 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001247 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001249 (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001250 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001251 SkScalar dstX = SkIntToScalar(x);
1252 SkScalar dstY = SkIntToScalar(y);
1253 do {
1254 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1255 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1256 SkASSERT(fi <= 0xFFFF);
1257
reed@android.com512a8762009-12-14 15:25:36 +00001258 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001260 toggle ^= Gradient_Shader::kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261
1262 dstX += SK_Scalar1;
1263 } while (--count != 0);
1264 }
1265}
1266
1267///////////////////////////////////////////////////////////////////////////////
1268
1269#define kSQRT_TABLE_BITS 11
1270#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1271
1272#include "SkRadialGradient_Table.h"
1273
1274#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1275
1276#include <stdio.h>
1277
reed@google.com61eb0402011-04-15 12:11:12 +00001278void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1280
1281 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1282 SkASSERT(file);
1283 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1284
reed@google.com61eb0402011-04-15 12:11:12 +00001285 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1286 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001287 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001288 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289
1290 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1291
1292 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001293 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001294 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001295 }
1296 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001298 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 }
1300 ::fprintf(file, "};\n");
1301 ::fclose(file);
1302}
1303
1304#endif
1305
1306
reed@google.com61eb0402011-04-15 12:11:12 +00001307static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1308 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001309 SkScalar inv = SkScalarInvert(radius);
1310
1311 matrix->setTranslate(-center.fX, -center.fY);
1312 matrix->postScale(inv, inv);
1313}
1314
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001315
1316namespace {
1317
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001318typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
1319 SkScalar sfy, SkScalar sdy,
1320 uint16_t* dstC, const uint16_t* cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001321 int toggle, int count);
1322
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001323void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
1324 SkScalar sfy, SkScalar sdy,
1325 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001326 int toggle, int count) {
1327 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
1328
1329 /* knock these down so we can pin against +- 0x7FFF, which is an
1330 immediate load, rather than 0xFFFF which is slower. This is a
1331 compromise, since it reduces our precision, but that appears
1332 to be visually OK. If we decide this is OK for all of our cases,
1333 we could (it seems) put this scale-down into fDstToIndex,
1334 to avoid having to do these extra shifts each time.
1335 */
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001336 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1337 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1338 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1339 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001340 // might perform this check for the other modes,
1341 // but the win will be a smaller % of the total
1342 if (dy == 0) {
1343 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1344 fy *= fy;
1345 do {
1346 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1347 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1348 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1349 fx += dx;
1350 *dstC++ = cache[toggle +
1351 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001352 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001353 } while (--count != 0);
1354 } else {
1355 do {
1356 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1357 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1358 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1359 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1360 fx += dx;
1361 fy += dy;
1362 *dstC++ = cache[toggle +
1363 (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001364 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001365 } while (--count != 0);
1366 }
1367}
1368
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001369void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
1370 SkScalar sfy, SkScalar sdy,
1371 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001372 int toggle, int count) {
1373 do {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001374#ifdef SK_SCALAR_IS_FLOAT
1375 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1376 SkFixed dist = SkFloatToFixed(fdist);
1377#else
1378 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1379 SkFixedSquare(sfy);
1380 if (magnitudeSquared < 0) // Overflow.
1381 magnitudeSquared = SK_FixedMax;
1382 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1383#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001384 unsigned fi = mirror_tileproc(dist);
1385 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001386 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001387 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001388 sfx += sdx;
1389 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001390 } while (--count != 0);
1391}
1392
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001393void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
1394 SkScalar sfy, SkScalar sdy,
1395 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001396 int toggle, int count) {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001397 SkFixed fx = SkScalarToFixed(sfx);
1398 SkFixed dx = SkScalarToFixed(sdx);
1399 SkFixed fy = SkScalarToFixed(sfy);
1400 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001401 do {
1402 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1403 unsigned fi = repeat_tileproc(dist);
1404 SkASSERT(fi <= 0xFFFF);
1405 fx += dx;
1406 fy += dy;
1407 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001408 toggle ^= Gradient_Shader::kDitherStride16;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001409 } while (--count != 0);
1410}
1411
1412}
1413
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414class Radial_Gradient : public Gradient_Shader {
1415public:
1416 Radial_Gradient(const SkPoint& center, SkScalar radius,
1417 const SkColor colors[], const SkScalar pos[], int colorCount,
1418 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001419 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1420 fCenter(center),
1421 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422 {
1423 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1424 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1425
1426 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1427 }
reed@google.com61eb0402011-04-15 12:11:12 +00001428
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001429 virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
1430 SK_OVERRIDE;
1431 virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
1432 int count) SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433 SkASSERT(count > 0);
1434
1435 SkPoint srcPt;
1436 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1437 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001438 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001439 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440
reed@android.com3c9b2a42009-08-27 19:28:37 +00001441 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001442 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1443 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001444
1445 SkScalar sdx = fDstToIndex.getScaleX();
1446 SkScalar sdy = fDstToIndex.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447
reed@android.com3c9b2a42009-08-27 19:28:37 +00001448 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449 SkFixed storage[2];
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001450 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1451 &storage[0], &storage[1]);
1452 sdx = SkFixedToScalar(storage[0]);
1453 sdy = SkFixedToScalar(storage[1]);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001454 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 }
1457
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001458 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001459 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001460 shadeProc = shadeSpan16_radial_clamp;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001461 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001462 shadeProc = shadeSpan16_radial_mirror;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001463 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 SkASSERT(proc == repeat_tileproc);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 }
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001466 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
1467 cache, toggle, count);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001468 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 SkScalar dstX = SkIntToScalar(x);
1470 SkScalar dstY = SkIntToScalar(y);
1471 do {
1472 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1473 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1474 SkASSERT(fi <= 0xFFFF);
1475
1476 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001477 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001478 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001479
1480 dstX += SK_Scalar1;
1481 } while (--count != 0);
1482 }
1483 }
1484
reed@google.com55b8e8c2011-01-13 16:22:35 +00001485 virtual BitmapType asABitmap(SkBitmap* bitmap,
1486 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001487 TileMode* xy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001488 SkScalar* twoPointRadialParams)
1489 const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00001490 if (bitmap) {
1491 this->commonAsABitmap(bitmap);
1492 }
1493 if (matrix) {
tomhudson@google.com13e812c2012-01-18 21:28:01 +00001494 matrix->setScale(SkIntToScalar(kGradient32Length),
1495 SkIntToScalar(kGradient32Length));
reed@google.comdc731fd2010-12-23 15:19:47 +00001496 matrix->preConcat(fPtsToUnit);
1497 }
1498 if (xy) {
1499 xy[0] = fTileMode;
1500 xy[1] = kClamp_TileMode;
1501 }
1502 return kRadial_BitmapType;
1503 }
reed@google.com7716afb2011-12-07 15:17:50 +00001504 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001505 if (info) {
1506 commonAsAGradient(info);
1507 info->fPoint[0] = fCenter;
1508 info->fRadius[0] = fRadius;
1509 }
1510 return kRadial_GradientType;
1511 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001512
reed@google.com8e6d9142011-12-07 15:30:34 +00001513 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514 return SkNEW_ARGS(Radial_Gradient, (buffer));
1515 }
1516
reed@google.com7716afb2011-12-07 15:17:50 +00001517 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001518 this->INHERITED::flatten(buffer);
1519 buffer.writeScalar(fCenter.fX);
1520 buffer.writeScalar(fCenter.fY);
1521 buffer.writeScalar(fRadius);
1522 }
1523
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001525 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1526 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001527 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001528 fRadius(buffer.readScalar()) {
1529 }
reed@google.com7716afb2011-12-07 15:17:50 +00001530 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531
1532private:
1533 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001534 const SkPoint fCenter;
1535 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536};
1537
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001538namespace {
1539
1540inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001541 // fast, overly-conservative test: checks unit square instead
1542 // of unit circle
1543 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
1544 (fx <= -SK_FixedHalf && dx <= 0);
1545 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
1546 (fy <= -SK_FixedHalf && dy <= 0);
1547
1548 return xClamped || yClamped;
1549}
1550
1551// Return true if (fx * fy) is always inside the unit circle
1552// SkPin32 is expensive, but so are all the SkFixedMul in this test,
1553// so it shouldn't be run if count is small.
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001554inline bool no_need_for_radial_pin(int fx, int dx,
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001555 int fy, int dy, int count) {
1556 SkASSERT(count > 0);
1557 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1558 return false;
1559 }
1560 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
1561 return false;
1562 }
1563 fx += (count - 1) * dx;
1564 fy += (count - 1) * dy;
1565 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
1566 return false;
1567 }
1568 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
1569}
1570
1571#define UNPINNED_RADIAL_STEP \
1572 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001573 *dstC++ = cache[toggle + \
1574 (sqrt_table[fi] >> Gradient_Shader::kSqrt32Shift)]; \
1575 toggle ^= Gradient_Shader::kDitherStride32; \
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001576 fx += dx; \
1577 fy += dy;
1578
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001579typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
1580 SkScalar sfy, SkScalar sdy,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001581 SkPMColor* dstC, const SkPMColor* cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001582 int count, int toggle);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001583
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001584// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001585void shadeSpan_radial_clamp(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.com5ea050f2011-09-26 15:03:55 +00001589 // Floating point seems to be slower than fixed point,
1590 // even when we have float hardware.
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001591 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001592 SkFixed fx = SkScalarToFixed(sfx) >> 1;
1593 SkFixed dx = SkScalarToFixed(sdx) >> 1;
1594 SkFixed fy = SkScalarToFixed(sfy) >> 1;
1595 SkFixed dy = SkScalarToFixed(sdy) >> 1;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001596 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001597 unsigned fi = Gradient_Shader::kGradient32Length;
1598 sk_memset32_dither(dstC,
1599 cache[toggle + fi],
1600 cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi],
1601 count);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001602 } else if ((count > 4) &&
1603 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
1604 unsigned fi;
1605 // 4x unroll appears to be no faster than 2x unroll on Linux
1606 while (count > 1) {
1607 UNPINNED_RADIAL_STEP;
1608 UNPINNED_RADIAL_STEP;
1609 count -= 2;
1610 }
1611 if (count) {
1612 UNPINNED_RADIAL_STEP;
1613 }
1614 }
1615 else {
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001616 // Specializing for dy == 0 gains us 25% on Skia benchmarks
1617 if (dy == 0) {
1618 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1619 yy *= yy;
1620 do {
1621 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1622 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
1623 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001624 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1625 Gradient_Shader::kSqrt32Shift)];
1626 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001627 fx += dx;
1628 } while (--count != 0);
1629 } else {
1630 do {
1631 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1632 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1633 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1634 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001635 *dstC++ = cache[toggle + (sqrt_table[fi] >>
1636 Gradient_Shader::kSqrt32Shift)];
1637 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001638 fx += dx;
1639 fy += dy;
1640 } while (--count != 0);
1641 }
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001642 }
1643}
1644
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001645// Unrolling this loop doesn't seem to help (when float); we're stalling to
1646// get the results of the sqrt (?), and don't have enough extra registers to
1647// have many in flight.
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001648void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
1649 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001650 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001651 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001652 do {
tomhudson@google.comf925ba42012-01-11 21:54:26 +00001653#ifdef SK_SCALAR_IS_FLOAT
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001654 float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
1655 SkFixed dist = SkFloatToFixed(fdist);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001656#else
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001657 SkFixed magnitudeSquared = SkFixedSquare(sfx) +
1658 SkFixedSquare(sfy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001659 if (magnitudeSquared < 0) // Overflow.
1660 magnitudeSquared = SK_FixedMax;
1661 SkFixed dist = SkFixedSqrt(magnitudeSquared);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001662#endif
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001663 unsigned fi = mirror_tileproc(dist);
1664 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001665 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1666 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001667 sfx += sdx;
1668 sfy += sdy;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001669 } while (--count != 0);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001670}
1671
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001672void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
1673 SkScalar sfy, SkScalar sdy,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001674 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001675 int count, int toggle) {
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001676 SkFixed fx = SkScalarToFixed(sfx);
1677 SkFixed dx = SkScalarToFixed(sdx);
1678 SkFixed fy = SkScalarToFixed(sfy);
1679 SkFixed dy = SkScalarToFixed(sdy);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001680 do {
1681 SkFixed magnitudeSquared = SkFixedSquare(fx) +
1682 SkFixedSquare(fy);
1683 if (magnitudeSquared < 0) // Overflow.
1684 magnitudeSquared = SK_FixedMax;
1685 SkFixed dist = SkFixedSqrt(magnitudeSquared);
1686 unsigned fi = repeat_tileproc(dist);
1687 SkASSERT(fi <= 0xFFFF);
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001688 *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache32Shift)];
1689 toggle ^= Gradient_Shader::kDitherStride32;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001690 fx += dx;
1691 fy += dy;
1692 } while (--count != 0);
1693}
1694}
1695
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001696void Radial_Gradient::shadeSpan(int x, int y,
1697 SkPMColor* SK_RESTRICT dstC, int count) {
1698 SkASSERT(count > 0);
1699
1700 SkPoint srcPt;
1701 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1702 TileProc proc = fTileProc;
tomhudson@google.com3e0f22c2011-09-30 14:31:28 +00001703 const SkPMColor* SK_RESTRICT cache = this->getCache32();
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001704#ifdef USE_DITHER_32BIT_GRADIENT
1705 int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32;
1706#else
1707 int toggle = 0;
1708#endif
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001709
1710 if (fDstToIndexClass != kPerspective_MatrixClass) {
1711 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1712 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001713 SkScalar sdx = fDstToIndex.getScaleX();
1714 SkScalar sdy = fDstToIndex.getSkewY();
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001715
1716 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
1717 SkFixed storage[2];
tomhudson@google.com51a283b2012-01-12 16:21:43 +00001718 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
1719 &storage[0], &storage[1]);
1720 sdx = SkFixedToScalar(storage[0]);
1721 sdy = SkFixedToScalar(storage[1]);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001722 } else {
1723 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001724 }
1725
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001726 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001727 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001728 shadeProc = shadeSpan_radial_clamp;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001729 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001730 shadeProc = shadeSpan_radial_mirror;
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001731 } else {
1732 SkASSERT(proc == repeat_tileproc);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001733 }
tomhudson@google.com480e21f2012-02-02 14:11:18 +00001734 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001735 } else { // perspective case
1736 SkScalar dstX = SkIntToScalar(x);
1737 SkScalar dstY = SkIntToScalar(y);
1738 do {
1739 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1740 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1741 SkASSERT(fi <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001742 *dstC++ = cache[fi >> Gradient_Shader::kCache32Shift];
tomhudson@google.com5ea050f2011-09-26 15:03:55 +00001743 dstX += SK_Scalar1;
1744 } while (--count != 0);
1745 }
1746}
1747
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001748/* Two-point radial gradients are specified by two circles, each with a center
1749 point and radius. The gradient can be considered to be a series of
1750 concentric circles, with the color interpolated from the start circle
1751 (at t=0) to the end circle (at t=1).
1752
1753 For each point (x, y) in the span, we want to find the
1754 interpolated circle that intersects that point. The center
1755 of the desired circle (Cx, Cy) falls at some distance t
1756 along the line segment between the start point (Sx, Sy) and
1757 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001758
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001759 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1760 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001761
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001762 The radius of the desired circle (r) is also a linear interpolation t
1763 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001764
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001765 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001766
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001767 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001768
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001769 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001770
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001771 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001772
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001773 (x - ((1 - t) * Sx + t * Ex))^2
1774 + (y - ((1 - t) * Sy + t * Ey))^2
1775 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001776
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001777 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001778
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001779 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1780 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1781 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001782
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001783 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1784
1785 [Dx^2 + Dy^2 - Dr^2)] * t^2
1786 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1787 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001788
1789 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001790 possible circles on which the point may fall. Solving for t yields
1791 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001792
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001793 If a<0, the start circle is entirely contained in the
1794 end circle, and one of the roots will be <0 or >1 (off the line
1795 segment). If a>0, the start circle falls at least partially
1796 outside the end circle (or vice versa), and the gradient
1797 defines a "tube" where a point may be on one circle (on the
1798 inside of the tube) or the other (outside of the tube). We choose
1799 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001800
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001801 In order to keep the math to within the limits of fixed point,
1802 we divide the entire quadratic by Dr^2, and replace
1803 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001804
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001805 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1806 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1807 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001808
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001809 (x' and y' are computed by appending the subtract and scale to the
1810 fDstToIndex matrix in the constructor).
1811
1812 Since the 'A' component of the quadratic is independent of x' and y', it
1813 is precomputed in the constructor. Since the 'B' component is linear in
1814 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001815 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001816 a perspective projection), it must be computed in the loop.
1817
1818*/
1819
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001820namespace {
1821
1822inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1823 SkScalar sr2d2, SkScalar foura,
1824 SkScalar oneOverTwoA, bool posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001825 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001826 if (0 == foura) {
1827 return SkScalarToFixed(SkScalarDiv(-c, b));
1828 }
1829
reed@google.com84e9c082011-04-13 17:44:24 +00001830 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001831 if (discrim < 0) {
1832 discrim = -discrim;
1833 }
reed@google.com84e9c082011-04-13 17:44:24 +00001834 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1835 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001836 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001837 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001838 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001839 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001840 }
reed@google.com84e9c082011-04-13 17:44:24 +00001841 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001842}
1843
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001844typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
1845 SkScalar fy, SkScalar dy,
1846 SkScalar b, SkScalar db,
1847 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001848 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001849 int count);
1850
1851void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
1852 SkScalar fy, SkScalar dy,
1853 SkScalar b, SkScalar db,
1854 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001855 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001856 int count) {
1857 for (; count > 0; --count) {
1858 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1859 fOneOverTwoA, posRoot);
1860 SkFixed index = SkClampMax(t, 0xFFFF);
1861 SkASSERT(index <= 0xFFFF);
1862 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1863 fx += dx;
1864 fy += dy;
1865 b += db;
1866 }
1867}
1868void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
1869 SkScalar fy, SkScalar dy,
1870 SkScalar b, SkScalar db,
1871 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001872 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001873 int count) {
1874 for (; count > 0; --count) {
1875 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1876 fOneOverTwoA, posRoot);
1877 SkFixed index = mirror_tileproc(t);
1878 SkASSERT(index <= 0xFFFF);
1879 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1880 fx += dx;
1881 fy += dy;
1882 b += db;
1883 }
1884}
1885
1886void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
1887 SkScalar fy, SkScalar dy,
1888 SkScalar b, SkScalar db,
1889 SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
tomhudson@google.com6a8df702012-01-12 20:13:49 +00001890 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001891 int count) {
1892 for (; count > 0; --count) {
1893 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
1894 fOneOverTwoA, posRoot);
1895 SkFixed index = repeat_tileproc(t);
1896 SkASSERT(index <= 0xFFFF);
1897 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
1898 fx += dx;
1899 fy += dy;
1900 b += db;
1901 }
1902}
1903
1904
1905
1906}
1907
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001908class Two_Point_Radial_Gradient : public Gradient_Shader {
1909public:
1910 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1911 const SkPoint& end, SkScalar endRadius,
1912 const SkColor colors[], const SkScalar pos[],
1913 int colorCount, SkShader::TileMode mode,
1914 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001915 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1916 fCenter1(start),
1917 fCenter2(end),
1918 fRadius1(startRadius),
1919 fRadius2(endRadius) {
1920 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001921 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001922
1923 virtual BitmapType asABitmap(SkBitmap* bitmap,
1924 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001925 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001926 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001927 if (bitmap) {
1928 this->commonAsABitmap(bitmap);
1929 }
1930 SkScalar diffL = 0; // just to avoid gcc warning
1931 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001932 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001933 SkScalarSquare(fDiff.fY));
1934 }
1935 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001936 if (diffL) {
1937 SkScalar invDiffL = SkScalarInvert(diffL);
1938 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1939 SkScalarMul(invDiffL, fDiff.fX));
1940 } else {
1941 matrix->reset();
1942 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001943 matrix->preConcat(fPtsToUnit);
1944 }
1945 if (xy) {
1946 xy[0] = fTileMode;
1947 xy[1] = kClamp_TileMode;
1948 }
1949 if (NULL != twoPointRadialParams) {
1950 twoPointRadialParams[0] = diffL;
1951 twoPointRadialParams[1] = fStartRadius;
1952 twoPointRadialParams[2] = fDiffRadius;
1953 }
1954 return kTwoPointRadial_BitmapType;
1955 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001956
reed@google.com8e6d9142011-12-07 15:30:34 +00001957 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001958 if (info) {
1959 commonAsAGradient(info);
1960 info->fPoint[0] = fCenter1;
1961 info->fPoint[1] = fCenter2;
1962 info->fRadius[0] = fRadius1;
1963 info->fRadius[1] = fRadius2;
1964 }
1965 return kRadial2_GradientType;
1966 }
1967
tomhudson@google.come8c984d2012-01-09 13:45:36 +00001968 virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
1969 int count) SK_OVERRIDE {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001970 SkASSERT(count > 0);
1971
1972 // Zero difference between radii: fill with transparent black.
1973 if (fDiffRadius == 0) {
1974 sk_bzero(dstC, count * sizeof(*dstC));
1975 return;
1976 }
1977 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1978 TileProc proc = fTileProc;
reed@google.comdd5bd672011-09-20 15:56:13 +00001979 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001980
1981 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001982 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001983 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001984 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001985 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1986 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001987 SkScalar dx, fx = srcPt.fX;
1988 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001989
reed@google.com61eb0402011-04-15 12:11:12 +00001990 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001991 SkFixed fixedX, fixedY;
1992 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1993 dx = SkFixedToScalar(fixedX);
1994 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001995 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001996 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001997 dx = fDstToIndex.getScaleX();
1998 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001999 }
reed@google.com84e9c082011-04-13 17:44:24 +00002000 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
2001 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
2002 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
2003 SkScalarMul(fDiff.fY, dy)) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002004
2005 TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
reed@google.com61eb0402011-04-15 12:11:12 +00002006 if (proc == clamp_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002007 shadeProc = shadeSpan_twopoint_clamp;
reed@google.com61eb0402011-04-15 12:11:12 +00002008 } else if (proc == mirror_tileproc) {
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002009 shadeProc = shadeSpan_twopoint_mirror;
reed@google.com61eb0402011-04-15 12:11:12 +00002010 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002011 SkASSERT(proc == repeat_tileproc);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002012 }
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002013 (*shadeProc)(fx, dx, fy, dy, b, db,
2014 fSr2D2, foura, fOneOverTwoA, posRoot,
2015 dstC, cache, count);
reed@google.com61eb0402011-04-15 12:11:12 +00002016 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00002017 SkScalar dstX = SkIntToScalar(x);
2018 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002019 for (; count > 0; --count) {
2020 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00002021 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00002022 SkScalar fx = srcPt.fX;
2023 SkScalar fy = srcPt.fY;
2024 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
2025 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002026 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
2027 fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002028 SkFixed index = proc(t);
2029 SkASSERT(index <= 0xFFFF);
tomhudson@google.come8c984d2012-01-09 13:45:36 +00002030 *dstC++ = cache[index >> Gradient_Shader::kCache32Shift];
reed@android.com6c59a172009-09-22 20:24:05 +00002031 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002032 }
2033 }
2034 }
2035
reed@android.com6c59a172009-09-22 20:24:05 +00002036 virtual bool setContext(const SkBitmap& device,
2037 const SkPaint& paint,
reed@google.com8e6d9142011-12-07 15:30:34 +00002038 const SkMatrix& matrix) SK_OVERRIDE {
2039 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com6c59a172009-09-22 20:24:05 +00002040 return false;
2041 }
2042
2043 // we don't have a span16 proc
2044 fFlags &= ~kHasSpan16_Flag;
2045 return true;
2046 }
2047
reed@google.com8e6d9142011-12-07 15:30:34 +00002048 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002049 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
2050 }
2051
reed@google.com7716afb2011-12-07 15:17:50 +00002052 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
reed@android.combcfc7332009-11-10 16:19:39 +00002053 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002054 buffer.writeScalar(fCenter1.fX);
2055 buffer.writeScalar(fCenter1.fY);
2056 buffer.writeScalar(fCenter2.fX);
2057 buffer.writeScalar(fCenter2.fY);
2058 buffer.writeScalar(fRadius1);
2059 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00002060 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002061
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002062protected:
reed@android.combcfc7332009-11-10 16:19:39 +00002063 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002064 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002065 fCenter1(unflatten_point(buffer)),
2066 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002067 fRadius1(buffer.readScalar()),
2068 fRadius2(buffer.readScalar()) {
2069 init();
reed@android.combcfc7332009-11-10 16:19:39 +00002070 };
reed@google.com7716afb2011-12-07 15:17:50 +00002071 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002072
2073private:
2074 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002075 const SkPoint fCenter1;
2076 const SkPoint fCenter2;
2077 const SkScalar fRadius1;
2078 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002079 SkPoint fDiff;
2080 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002081
2082 void init() {
2083 fDiff = fCenter1 - fCenter2;
2084 fDiffRadius = fRadius2 - fRadius1;
2085 SkScalar inv = SkScalarInvert(fDiffRadius);
2086 fDiff.fX = SkScalarMul(fDiff.fX, inv);
2087 fDiff.fY = SkScalarMul(fDiff.fY, inv);
2088 fStartRadius = SkScalarMul(fRadius1, inv);
2089 fSr2D2 = SkScalarSquare(fStartRadius);
2090 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00002091 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002092
2093 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
2094 fPtsToUnit.postScale(inv, inv);
2095 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002096};
2097
reed@android.com8a1c16f2008-12-17 15:59:43 +00002098///////////////////////////////////////////////////////////////////////////////
2099
2100class Sweep_Gradient : public Gradient_Shader {
2101public:
2102 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
2103 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002104 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
2105 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00002106 {
2107 fPtsToUnit.setTranslate(-cx, -cy);
2108 }
reed@google.com7716afb2011-12-07 15:17:50 +00002109 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
2110 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002111
2112 virtual BitmapType asABitmap(SkBitmap* bitmap,
2113 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00002114 TileMode* xy,
reed@google.com7716afb2011-12-07 15:17:50 +00002115 SkScalar* twoPointRadialParams) const SK_OVERRIDE {
reed@google.comdc731fd2010-12-23 15:19:47 +00002116 if (bitmap) {
2117 this->commonAsABitmap(bitmap);
2118 }
2119 if (matrix) {
2120 *matrix = fPtsToUnit;
2121 }
2122 if (xy) {
2123 xy[0] = fTileMode;
2124 xy[1] = kClamp_TileMode;
2125 }
2126 return kSweep_BitmapType;
2127 }
2128
reed@google.com7716afb2011-12-07 15:17:50 +00002129 virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002130 if (info) {
2131 commonAsAGradient(info);
2132 info->fPoint[0] = fCenter;
2133 }
2134 return kSweep_GradientType;
2135 }
2136
reed@google.com8e6d9142011-12-07 15:30:34 +00002137 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 return SkNEW_ARGS(Sweep_Gradient, (buffer));
2139 }
2140
reed@google.com7716afb2011-12-07 15:17:50 +00002141 virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002142 this->INHERITED::flatten(buffer);
2143 buffer.writeScalar(fCenter.fX);
2144 buffer.writeScalar(fCenter.fY);
2145 }
2146
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002148 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
2149 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00002150 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002151 }
2152
reed@google.com7716afb2011-12-07 15:17:50 +00002153 virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154
2155private:
2156 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00002157 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158};
2159
2160#ifdef COMPUTE_SWEEP_TABLE
2161#define PI 3.14159265
2162static bool gSweepTableReady;
2163static uint8_t gSweepTable[65];
2164
2165/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
2166 We scale the results to [0..32]
2167*/
reed@google.com61eb0402011-04-15 12:11:12 +00002168static const uint8_t* build_sweep_table() {
2169 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002170 const int N = 65;
2171 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002172
reed@android.com8a1c16f2008-12-17 15:59:43 +00002173 for (int i = 0; i < N; i++)
2174 {
2175 double arg = i / DENOM;
2176 double v = atan(arg);
2177 int iv = (int)round(v * DENOM * 2 / PI);
2178// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
2179 printf("%d, ", iv);
2180 gSweepTable[i] = iv;
2181 }
2182 gSweepTableReady = true;
2183 }
2184 return gSweepTable;
2185}
2186#else
2187static const uint8_t gSweepTable[] = {
2188 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
2189 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
2190 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
2191 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
2192 32
2193};
2194static const uint8_t* build_sweep_table() { return gSweepTable; }
2195#endif
2196
2197// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
2198// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
2199// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
2200
2201//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00002202static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002203 SkASSERT(numer <= denom);
2204 SkASSERT(numer > 0);
2205 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002206
reed@android.com8a1c16f2008-12-17 15:59:43 +00002207 int nbits = SkCLZ(numer);
2208 int dbits = SkCLZ(denom);
2209 int bits = 6 - nbits + dbits;
2210 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002211
reed@google.com61eb0402011-04-15 12:11:12 +00002212 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00002213 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002214 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215
2216 denom <<= dbits - 1;
2217 numer <<= nbits - 1;
2218
2219 unsigned result = 0;
2220
2221 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00002222 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002223 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00002224 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002225 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00002226 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002227
reed@android.com8a1c16f2008-12-17 15:59:43 +00002228 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00002229 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002230 // make room for the rest of the answer bits
2231 result <<= bits;
2232 switch (bits) {
2233 case 6:
2234 if ((numer = (numer << 1) - denom) >= 0)
2235 result |= 32;
2236 else
2237 numer += denom;
2238 case 5:
2239 if ((numer = (numer << 1) - denom) >= 0)
2240 result |= 16;
2241 else
2242 numer += denom;
2243 case 4:
2244 if ((numer = (numer << 1) - denom) >= 0)
2245 result |= 8;
2246 else
2247 numer += denom;
2248 case 3:
2249 if ((numer = (numer << 1) - denom) >= 0)
2250 result |= 4;
2251 else
2252 numer += denom;
2253 case 2:
2254 if ((numer = (numer << 1) - denom) >= 0)
2255 result |= 2;
2256 else
2257 numer += denom;
2258 case 1:
2259 default: // not strictly need, but makes GCC make better ARM code
2260 if ((numer = (numer << 1) - denom) >= 0)
2261 result |= 1;
2262 else
2263 numer += denom;
2264 }
2265 }
2266 return result;
2267}
2268
2269// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00002270static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002271#ifdef SK_DEBUG
2272 {
2273 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002274 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002275 gOnce = true;
2276 SkASSERT(div_64(55, 55) == 64);
2277 SkASSERT(div_64(128, 256) == 32);
2278 SkASSERT(div_64(2326528, 4685824) == 31);
2279 SkASSERT(div_64(753664, 5210112) == 9);
2280 SkASSERT(div_64(229376, 4882432) == 3);
2281 SkASSERT(div_64(2, 64) == 2);
2282 SkASSERT(div_64(1, 64) == 1);
2283 // test that we handle underflow correctly
2284 SkASSERT(div_64(12345, 0x54321234) == 0);
2285 }
2286 }
2287#endif
2288
2289 SkASSERT(y > 0 && x > 0);
2290 const uint8_t* table = build_sweep_table();
2291
2292 unsigned result;
2293 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002294 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002295 // first part of the atan(v) = PI/2 - atan(1/v) identity
2296 // since our div_64 and table want v <= 1, where v = y/x
2297 SkTSwap<SkFixed>(x, y);
2298 }
2299
2300 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002301
reed@android.com8a1c16f2008-12-17 15:59:43 +00002302#ifdef SK_DEBUG
2303 {
2304 unsigned result2 = SkDivBits(y, x, 6);
2305 SkASSERT(result2 == result ||
2306 (result == 1 && result2 == 0));
2307 }
2308#endif
2309
2310 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2311 result = table[result];
2312
reed@google.com61eb0402011-04-15 12:11:12 +00002313 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002314 // complete the atan(v) = PI/2 - atan(1/v) identity
2315 result = 64 - result;
2316 // pin to 63
2317 result -= result >> 6;
2318 }
2319
2320 SkASSERT(result <= 63);
2321 return result;
2322}
2323
2324// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com51baf5a2011-09-21 13:38:36 +00002325#ifdef SK_SCALAR_IS_FLOAT
2326static unsigned SkATan2_255(float y, float x) {
2327 // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
2328 static const float g255Over2PI = 40.584510488433314f;
2329
2330 float result = sk_float_atan2(y, x);
2331 if (result < 0) {
2332 result += 2 * SK_ScalarPI;
2333 }
2334 SkASSERT(result >= 0);
2335 // since our value is always >= 0, we can cast to int, which is faster than
2336 // calling floorf()
2337 int ir = (int)(result * g255Over2PI);
2338 SkASSERT(ir >= 0 && ir <= 255);
2339 return ir;
2340}
2341#else
reed@google.com61eb0402011-04-15 12:11:12 +00002342static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2343 if (x == 0) {
2344 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002345 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002346 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002347 return y < 0 ? 192 : 64;
2348 }
reed@google.com61eb0402011-04-15 12:11:12 +00002349 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002350 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002351 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002352
reed@android.com8a1c16f2008-12-17 15:59:43 +00002353 /* Find the right quadrant for x,y
2354 Since atan_0_90 only handles the first quadrant, we rotate x,y
2355 appropriately before calling it, and then add the right amount
2356 to account for the real quadrant.
2357 quadrant 0 : add 0 | x > 0 && y > 0
2358 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2359 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2360 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002361
reed@android.com8a1c16f2008-12-17 15:59:43 +00002362 map x<0 to (1 << 6)
2363 map y<0 to (3 << 6)
2364 add = map_x ^ map_y
2365 */
2366 int xsign = x >> 31;
2367 int ysign = y >> 31;
2368 int add = ((-xsign) ^ (ysign & 3)) << 6;
2369
2370#ifdef SK_DEBUG
2371 if (0 == add)
2372 SkASSERT(x > 0 && y > 0);
2373 else if (64 == add)
2374 SkASSERT(x < 0 && y > 0);
2375 else if (128 == add)
2376 SkASSERT(x < 0 && y < 0);
2377 else if (192 == add)
2378 SkASSERT(x > 0 && y < 0);
2379 else
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002380 SkDEBUGFAIL("bad value for add");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002381#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002382
reed@android.com8a1c16f2008-12-17 15:59:43 +00002383 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2384 where we need to rotate x,y by 90 or -90
2385 */
2386 x = (x ^ xsign) - xsign;
2387 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002388 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002389 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002390 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002391
2392 unsigned result = add + atan_0_90(y, x);
2393 SkASSERT(result < 256);
2394 return result;
2395}
reed@google.com51baf5a2011-09-21 13:38:36 +00002396#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00002397
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002398void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
2399 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002400 SkMatrix::MapXYProc proc = fDstToIndexProc;
2401 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002402 const SkPMColor* SK_RESTRICT cache = this->getCache32();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002403 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002404
reed@google.com61eb0402011-04-15 12:11:12 +00002405 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002406 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2407 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002408 SkScalar dx, fx = srcPt.fX;
2409 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002410
reed@google.com61eb0402011-04-15 12:11:12 +00002411 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002412 SkFixed storage[2];
2413 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2414 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002415 dx = SkFixedToScalar(storage[0]);
2416 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002417 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002418 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002419 dx = matrix.getScaleX();
2420 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002421 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002422
reed@google.com61eb0402011-04-15 12:11:12 +00002423 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002424 *dstC++ = cache[SkATan2_255(fy, fx)];
2425 fx += dx;
2426 fy += dy;
2427 }
reed@google.com61eb0402011-04-15 12:11:12 +00002428 } else { // perspective case
2429 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002430 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
reed@google.com51baf5a2011-09-21 13:38:36 +00002431 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2432 *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002433 }
2434 }
2435}
2436
tomhudson@google.com6a8df702012-01-12 20:13:49 +00002437void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
2438 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002439 SkMatrix::MapXYProc proc = fDstToIndexProc;
2440 const SkMatrix& matrix = fDstToIndex;
reed@google.comdd5bd672011-09-20 15:56:13 +00002441 const uint16_t* SK_RESTRICT cache = this->getCache16();
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002442 int toggle = ((x ^ y) & 1) * kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002443 SkPoint srcPt;
2444
reed@google.com61eb0402011-04-15 12:11:12 +00002445 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002446 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2447 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com51baf5a2011-09-21 13:38:36 +00002448 SkScalar dx, fx = srcPt.fX;
2449 SkScalar dy, fy = srcPt.fY;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002450
reed@google.com61eb0402011-04-15 12:11:12 +00002451 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002452 SkFixed storage[2];
2453 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2454 &storage[0], &storage[1]);
reed@google.com51baf5a2011-09-21 13:38:36 +00002455 dx = SkFixedToScalar(storage[0]);
2456 dy = SkFixedToScalar(storage[1]);
reed@google.com61eb0402011-04-15 12:11:12 +00002457 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002458 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com51baf5a2011-09-21 13:38:36 +00002459 dx = matrix.getScaleX();
2460 dy = matrix.getSkewY();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002461 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002462
reed@google.com61eb0402011-04-15 12:11:12 +00002463 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002464 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2465 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002466 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002467 fx += dx;
2468 fy += dy;
2469 }
reed@google.com61eb0402011-04-15 12:11:12 +00002470 } else { // perspective case
2471 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002472 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2473 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002474
reed@google.com51baf5a2011-09-21 13:38:36 +00002475 int index = SkATan2_255(srcPt.fY, srcPt.fX);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002476 index >>= (8 - kCache16Bits);
2477 *dstC++ = cache[toggle + index];
tomhudson@google.com13e812c2012-01-18 21:28:01 +00002478 toggle ^= kDitherStride16;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002479 }
2480 }
2481}
2482
reed@google.com61eb0402011-04-15 12:11:12 +00002483///////////////////////////////////////////////////////////////////////////////
2484///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002485
2486// assumes colors is SkColor* and pos is SkScalar*
2487#define EXPAND_1_COLOR(count) \
2488 SkColor tmp[2]; \
2489 do { \
2490 if (1 == count) { \
2491 tmp[0] = tmp[1] = colors[0]; \
2492 colors = tmp; \
2493 pos = NULL; \
2494 count = 2; \
2495 } \
2496 } while (0)
2497
reed@google.com61eb0402011-04-15 12:11:12 +00002498SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2499 const SkColor colors[],
2500 const SkScalar pos[], int colorCount,
2501 SkShader::TileMode mode,
2502 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002503 if (NULL == pts || NULL == colors || colorCount < 1) {
2504 return NULL;
2505 }
2506 EXPAND_1_COLOR(colorCount);
2507
reed@android.comab840b82009-07-01 17:00:03 +00002508 return SkNEW_ARGS(Linear_Gradient,
2509 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002510}
2511
reed@google.com61eb0402011-04-15 12:11:12 +00002512SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2513 const SkColor colors[],
2514 const SkScalar pos[], int colorCount,
2515 SkShader::TileMode mode,
2516 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002517 if (radius <= 0 || NULL == colors || colorCount < 1) {
2518 return NULL;
2519 }
2520 EXPAND_1_COLOR(colorCount);
2521
reed@android.comab840b82009-07-01 17:00:03 +00002522 return SkNEW_ARGS(Radial_Gradient,
2523 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002524}
2525
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002526SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2527 SkScalar startRadius,
2528 const SkPoint& end,
2529 SkScalar endRadius,
2530 const SkColor colors[],
2531 const SkScalar pos[],
2532 int colorCount,
2533 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002534 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002535 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2536 return NULL;
2537 }
2538 EXPAND_1_COLOR(colorCount);
2539
2540 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002541 (start, startRadius, end, endRadius, colors, pos,
2542 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002543}
2544
reed@android.com8a1c16f2008-12-17 15:59:43 +00002545SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2546 const SkColor colors[],
2547 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002548 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002549 if (NULL == colors || count < 1) {
2550 return NULL;
2551 }
2552 EXPAND_1_COLOR(count);
2553
2554 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2555}
2556
caryclark@google.comd26147a2011-12-15 14:16:43 +00002557SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
2558 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient)
2559 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002560 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient)
caryclark@google.comd26147a2011-12-15 14:16:43 +00002561 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient)
2562SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END