blob: 89b9d34402f96ae80db08b7036b97fcb35710174 [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/* libs/graphics/effects/SkGradientShader.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
reed@google.com55b8e8c2011-01-13 16:22:35 +00005** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
reed@android.com8a1c16f2008-12-17 15:59:43 +00008**
reed@google.com55b8e8c2011-01-13 16:22:35 +00009** http://www.apache.org/licenses/LICENSE-2.0
reed@android.com8a1c16f2008-12-17 15:59:43 +000010**
reed@google.com55b8e8c2011-01-13 16:22:35 +000011** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
reed@android.com8a1c16f2008-12-17 15:59:43 +000015** limitations under the License.
16*/
17
18#include "SkGradientShader.h"
19#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000020#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021#include "SkUnitMapper.h"
22#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000023#include "SkTemplates.h"
24#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000025
reed@google.com9c7443d2011-01-17 18:46:37 +000026#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
27 #define USE_DITHER_32BIT_GRADIENT
28#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +000029
reed@google.com13659f12011-04-18 19:59:38 +000030#define SK_ENABLE_FAST_LINEAR_GRADIENTS
reed@google.com17705072011-04-18 12:43:32 +000031
32#ifdef SK_ENABLE_FAST_LINEAR_GRADIENTS
reed@google.com5eb158d2011-04-15 15:50:34 +000033static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
34 int count) {
35 if (count > 0) {
36 if (v0 == v1) {
37 sk_memset32(dst, v0, count);
38 } else {
39 int pairs = count >> 1;
40 for (int i = 0; i < pairs; i++) {
41 *dst++ = v0;
42 *dst++ = v1;
43 }
44 if (count & 1) {
45 *dst = v0;
46 }
47 }
48 }
49}
reed@google.com17705072011-04-18 12:43:32 +000050#endif
reed@google.com5eb158d2011-04-15 15:50:34 +000051
reed@google.com61eb0402011-04-15 12:11:12 +000052///////////////////////////////////////////////////////////////////////////////
tomhudson@google.com9ce767c2011-04-25 20:49:39 +000053// Can't use a two-argument function with side effects like this in a
54// constructor's initializer's argument list because the order of
55// evaluations in that context is undefined (and backwards on linux/gcc).
56static SkPoint unflatten_point(SkReader32& buffer) {
57 SkPoint retval;
58 retval.fX = buffer.readScalar();
59 retval.fY = buffer.readScalar();
60 return retval;
61}
62
63///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000064
65typedef SkFixed (*TileProc)(SkFixed);
66
reed@android.com41bccf52009-04-03 13:33:51 +000067static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 return SkClampMax(x, 0xFFFF);
69}
70
reed@android.com41bccf52009-04-03 13:33:51 +000071static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000072 return x & 0xFFFF;
73}
74
reed@android.com41bccf52009-04-03 13:33:51 +000075static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 int s = x << 15 >> 31;
77 return (x ^ s) & 0xFFFF;
78}
79
80static const TileProc gTileProcs[] = {
81 clamp_tileproc,
82 repeat_tileproc,
83 mirror_tileproc
84};
85
reed@google.com61eb0402011-04-15 12:11:12 +000086///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000087
reed@android.com200645d2009-12-14 16:41:57 +000088static inline int repeat_bits(int x, const int bits) {
89 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000090}
91
reed@android.com200645d2009-12-14 16:41:57 +000092static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000093#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000094 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000096 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000097#else
reed@android.com200645d2009-12-14 16:41:57 +000098 int s = x << (31 - bits) >> 31;
99 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100#endif
101}
102
reed@android.com41bccf52009-04-03 13:33:51 +0000103static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 return x & 0xFF;
105}
106
reed@android.com41bccf52009-04-03 13:33:51 +0000107static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +0000109 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +0000111 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 return x & 255;
113#else
114 int s = x << 23 >> 31;
115 return (x ^ s) & 0xFF;
116#endif
117}
118
reed@google.com61eb0402011-04-15 12:11:12 +0000119///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120
121class Gradient_Shader : public SkShader {
122public:
123 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000124 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 virtual ~Gradient_Shader();
126
127 // overrides
128 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
129 virtual uint32_t getFlags() { return fFlags; }
130
131protected:
132 Gradient_Shader(SkFlattenableReadBuffer& );
133 SkUnitMapper* fMapper;
134 SkMatrix fPtsToUnit; // set by subclass
135 SkMatrix fDstToIndex;
136 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000137 TileMode fTileMode;
138 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000139 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 uint8_t fDstToIndexClass;
141 uint8_t fFlags;
142
143 struct Rec {
144 SkFixed fPos; // 0...1
145 uint32_t fScale; // (1 << 24) / range
146 };
147 Rec* fRecs;
148
149 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000150 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000152 kCache16Mask = kCache16Count - 1,
153 kCache16Shift = 16 - kCache16Bits,
154
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155 kCache32Bits = 8, // pretty much should always be 8
156 kCache32Count = 1 << kCache32Bits
157 };
158 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000159 const uint16_t* getCache16() const;
160 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161
reed@google.com7c2f27d2011-03-07 19:29:00 +0000162 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000163 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000164
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165private:
166 enum {
167 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
168
reed@android.com1c12abe2009-07-02 15:01:02 +0000169 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 };
171 SkColor fStorage[(kStorageSize + 3) >> 2];
172 SkColor* fOrigColors;
173
reed@google.com7c2f27d2011-03-07 19:29:00 +0000174 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
175 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176
reed@google.com7c2f27d2011-03-07 19:29:00 +0000177 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
178 mutable SkMallocPixelRef* fCache32PixelRef;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179 unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
180
reed@android.com512a8762009-12-14 15:25:36 +0000181 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000182 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
183 U8CPU alpha);
reed@android.com512a8762009-12-14 15:25:36 +0000184
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185 typedef SkShader INHERITED;
186};
187
reed@android.com41bccf52009-04-03 13:33:51 +0000188static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000189 SkASSERT(x >= 0 && x <= SK_Scalar1);
190
191#ifdef SK_SCALAR_IS_FLOAT
192 return (unsigned)(x * 0xFFFF);
193#else
194 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
195#endif
196}
197
reed@android.com41bccf52009-04-03 13:33:51 +0000198Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
199 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200 SkASSERT(colorCount > 1);
201
202 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
203
204 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000205 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000206
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
208 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
209 fTileMode = mode;
210 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000211
reed@android.com41bccf52009-04-03 13:33:51 +0000212 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000213 fCache32 = NULL;
214 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215
reed@android.com41bccf52009-04-03 13:33:51 +0000216 /* Note: we let the caller skip the first and/or last position.
217 i.e. pos[0] = 0.3, pos[1] = 0.7
218 In these cases, we insert dummy entries to ensure that the final data
219 will be bracketed by [0, 1].
220 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
221
222 Thus colorCount (the caller's value, and fColorCount (our value) may
223 differ by up to 2. In the above example:
224 colorCount = 2
225 fColorCount = 4
226 */
227 fColorCount = colorCount;
228 // check if we need to add in dummy start and/or end position/colors
229 bool dummyFirst = false;
230 bool dummyLast = false;
231 if (pos) {
232 dummyFirst = pos[0] != 0;
233 dummyLast = pos[colorCount - 1] != SK_Scalar1;
234 fColorCount += dummyFirst + dummyLast;
235 }
236
237 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000238 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000239 fOrigColors = reinterpret_cast<SkColor*>(
240 sk_malloc_throw(size * fColorCount));
241 }
242 else {
243 fOrigColors = fStorage;
244 }
245
246 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000247 {
reed@android.com41bccf52009-04-03 13:33:51 +0000248 SkColor* origColors = fOrigColors;
249 if (dummyFirst) {
250 *origColors++ = colors[0];
251 }
252 memcpy(origColors, colors, colorCount * sizeof(SkColor));
253 if (dummyLast) {
254 origColors += colorCount;
255 *origColors = colors[colorCount - 1];
256 }
257 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258
reed@android.com1c12abe2009-07-02 15:01:02 +0000259 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000260 if (fColorCount > 2) {
261 Rec* recs = fRecs;
262 recs->fPos = 0;
263 // recs->fScale = 0; // unused;
264 recs += 1;
265 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266 /* We need to convert the user's array of relative positions into
267 fixed-point positions and scale factors. We need these results
268 to be strictly monotonic (no two values equal or out of order).
269 Hence this complex loop that just jams a zero for the scale
270 value if it sees a segment out of order, and it assures that
271 we start at 0 and end at 1.0
272 */
273 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000274 int startIndex = dummyFirst ? 0 : 1;
275 int count = colorCount + dummyLast;
276 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000277 // force the last value to be 1.0
278 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000279 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000280 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000281 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000283 }
reed@android.com41bccf52009-04-03 13:33:51 +0000284 // pin curr withing range
285 if (curr < 0) {
286 curr = 0;
287 } else if (curr > SK_Fixed1) {
288 curr = SK_Fixed1;
289 }
290 recs->fPos = curr;
291 if (curr > prev) {
292 recs->fScale = (1 << 24) / (curr - prev);
293 } else {
294 recs->fScale = 0; // ignore this segment
295 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296 // get ready for the next value
297 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000298 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 }
reed@android.com41bccf52009-04-03 13:33:51 +0000300 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 SkFixed dp = SK_Fixed1 / (colorCount - 1);
302 SkFixed p = dp;
303 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000304 for (int i = 1; i < colorCount; i++) {
305 recs->fPos = p;
306 recs->fScale = scale;
307 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000308 p += dp;
309 }
310 }
311 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000312 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313}
314
315Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000316 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317 fCacheAlpha = 256;
318
319 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
320
321 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000322 fCache32 = NULL;
323 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000324
reed@android.com41bccf52009-04-03 13:33:51 +0000325 int colorCount = fColorCount = buffer.readU32();
326 if (colorCount > kColorStorageCount) {
327 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
328 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
329 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000331 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000332 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333
334 fTileMode = (TileMode)buffer.readU8();
335 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000336 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 if (colorCount > 2) {
338 Rec* recs = fRecs;
339 recs[0].fPos = 0;
340 for (int i = 1; i < colorCount; i++) {
341 recs[i].fPos = buffer.readS32();
342 recs[i].fScale = buffer.readU32();
343 }
344 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000345 SkReadMatrix(&buffer, &fPtsToUnit);
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000346 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347}
348
reed@android.com41bccf52009-04-03 13:33:51 +0000349Gradient_Shader::~Gradient_Shader() {
350 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000352 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000353 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000354 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000356 }
reed@google.com82065d62011-02-07 15:30:46 +0000357 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358}
359
reed@android.com41bccf52009-04-03 13:33:51 +0000360void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361 this->INHERITED::flatten(buffer);
362 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000363 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000364 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
365 buffer.write8(fTileMode);
366 if (fColorCount > 2) {
367 Rec* recs = fRecs;
368 for (int i = 1; i < fColorCount; i++) {
369 buffer.write32(recs[i].fPos);
370 buffer.write32(recs[i].fScale);
371 }
372 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000373 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000374}
375
376bool Gradient_Shader::setContext(const SkBitmap& device,
377 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000378 const SkMatrix& matrix) {
379 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000381 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000382
383 const SkMatrix& inverse = this->getTotalInverse();
384
385 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
386 return false;
387 }
388
389 fDstToIndexProc = fDstToIndex.getMapXYProc();
390 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
391
392 // now convert our colors in to PMColors
393 unsigned paintAlpha = this->getPaintAlpha();
394 unsigned colorAlpha = 0xFF;
395
reed@android.com3d06a8c2009-07-07 18:19:59 +0000396 // FIXME: record colorAlpha in constructor, since this is not affected
397 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000398 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000399 SkColor src = fOrigColors[i];
400 unsigned sa = SkColorGetA(src);
401 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000402 }
403
404 fFlags = this->INHERITED::getFlags();
405 if ((colorAlpha & paintAlpha) == 0xFF) {
406 fFlags |= kOpaqueAlpha_Flag;
407 }
408 // we can do span16 as long as our individual colors are opaque,
409 // regardless of the paint's alpha
410 if (0xFF == colorAlpha) {
411 fFlags |= kHasSpan16_Flag;
412 }
413
414 // if the new alpha differs from the previous time we were called, inval our cache
415 // this will trigger the cache to be rebuilt.
416 // we don't care about the first time, since the cache ptrs will already be NULL
417 if (fCacheAlpha != paintAlpha) {
418 fCache16 = NULL; // inval the cache
419 fCache32 = NULL; // inval the cache
420 fCacheAlpha = paintAlpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000421 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000422 if (fCache32PixelRef) {
423 fCache32PixelRef->notifyPixelsChanged();
424 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425 }
426 return true;
427}
428
reed@android.com41bccf52009-04-03 13:33:51 +0000429static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 SkASSERT(a == SkToU8(a));
431 SkASSERT(b == SkToU8(b));
432 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433 return a + ((b - a) * scale >> 8);
434}
435
reed@android.com41bccf52009-04-03 13:33:51 +0000436static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
437 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000438#if 0
439 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
440 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
441 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
442 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
443
444 return SkPackARGB32(a, r, g, b);
445#else
446 int otherBlend = 256 - blend;
447
448#if 0
449 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
450 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
451 SkASSERT((t0 & t1) == 0);
452 return t0 | t1;
453#else
454 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
455 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
456#endif
457
458#endif
459}
460
461#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
462
reed@android.com41bccf52009-04-03 13:33:51 +0000463/** We take the original colors, not our premultiplied PMColors, since we can
464 build a 16bit table as long as the original colors are opaque, even if the
465 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000466*/
reed@android.com512a8762009-12-14 15:25:36 +0000467void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
468 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000469 SkASSERT(count > 1);
470 SkASSERT(SkColorGetA(c0) == 0xFF);
471 SkASSERT(SkColorGetA(c1) == 0xFF);
472
473 SkFixed r = SkColorGetR(c0);
474 SkFixed g = SkColorGetG(c0);
475 SkFixed b = SkColorGetB(c0);
476
477 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
478 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
479 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
480
481 r = SkIntToFixed(r) + 0x8000;
482 g = SkIntToFixed(g) + 0x8000;
483 b = SkIntToFixed(b) + 0x8000;
484
485 do {
486 unsigned rr = r >> 16;
487 unsigned gg = g >> 16;
488 unsigned bb = b >> 16;
489 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000490 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000491 cache += 1;
492 r += dr;
493 g += dg;
494 b += db;
495 } while (--count != 0);
496}
497
reed@google.com55b8e8c2011-01-13 16:22:35 +0000498/*
499 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
500 * semantics of how we 2x2 dither 32->16
501 */
502static inline U8CPU dither_fixed_to_8(SkFixed n) {
503 n >>= 8;
504 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
505}
506
507/*
508 * For dithering with premultiply, we want to ceiling the alpha component,
509 * to ensure that it is always >= any color component.
510 */
511static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
512 n >>= 8;
513 return ((n << 1) - (n | (n >> 8))) >> 8;
514}
515
516void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
517 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000518 SkASSERT(count > 1);
519
reed@android.com1c12abe2009-07-02 15:01:02 +0000520 // need to apply paintAlpha to our two endpoints
521 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
522 SkFixed da;
523 {
524 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
525 da = SkIntToFixed(tmp - a) / (count - 1);
526 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000527
reed@android.com1c12abe2009-07-02 15:01:02 +0000528 SkFixed r = SkColorGetR(c0);
529 SkFixed g = SkColorGetG(c0);
530 SkFixed b = SkColorGetB(c0);
531 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
532 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
533 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000534
535 a = SkIntToFixed(a) + 0x8000;
536 r = SkIntToFixed(r) + 0x8000;
537 g = SkIntToFixed(g) + 0x8000;
538 b = SkIntToFixed(b) + 0x8000;
539
540 do {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000541 cache[0] = SkPreMultiplyARGB(a >> 16, r >> 16, g >> 16, b >> 16);
542 cache[kCache32Count] = SkPreMultiplyARGB(dither_ceil_fixed_to_8(a),
543 dither_fixed_to_8(r),
544 dither_fixed_to_8(g),
545 dither_fixed_to_8(b));
546 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000547 a += da;
548 r += dr;
549 g += dg;
550 b += db;
551 } while (--count != 0);
552}
553
reed@android.com41bccf52009-04-03 13:33:51 +0000554static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000555 SkASSERT((unsigned)x <= SK_Fixed1);
556 return x - (x >> 16);
557}
558
reed@android.com200645d2009-12-14 16:41:57 +0000559static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000560 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000561 if (6 == bits) {
562 return (x << 10) | (x << 4) | (x >> 2);
563 }
564 if (8 == bits) {
565 return (x << 8) | x;
566 }
567 sk_throw();
568 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000569}
570
reed@google.com7c2f27d2011-03-07 19:29:00 +0000571const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000572 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000573 // double the count for dither entries
574 const int entryCount = kCache16Count * 2;
575 const size_t allocSize = sizeof(uint16_t) * entryCount;
576
reed@android.com3c9b2a42009-08-27 19:28:37 +0000577 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000578 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000579 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000580 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000581 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000582 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000583 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584 Rec* rec = fRecs;
585 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000586 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000587 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588 SkASSERT(nextIndex < kCache16Count);
589
590 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000591 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000592 prevIndex = nextIndex;
593 }
594 SkASSERT(prevIndex == kCache16Count - 1);
595 }
596
reed@android.com41bccf52009-04-03 13:33:51 +0000597 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000598 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 uint16_t* linear = fCache16; // just computed linear data
600 uint16_t* mapped = fCache16Storage; // storage for mapped data
601 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000602 for (int i = 0; i < kCache16Count; i++) {
603 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000604 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000605 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606 }
607 sk_free(fCache16);
608 fCache16 = fCache16Storage;
609 }
610 }
611 return fCache16;
612}
613
reed@google.com7c2f27d2011-03-07 19:29:00 +0000614const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000615 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000616 // double the count for dither entries
617 const int entryCount = kCache32Count * 2;
618 const size_t allocSize = sizeof(SkPMColor) * entryCount;
619
reed@google.comdc731fd2010-12-23 15:19:47 +0000620 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000621 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
622 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000623 }
624 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000625 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000626 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
627 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000628 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 Rec* rec = fRecs;
630 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000631 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000632 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
633 SkASSERT(nextIndex < kCache32Count);
634
635 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000636 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
637 fOrigColors[i],
638 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000639 prevIndex = nextIndex;
640 }
641 SkASSERT(prevIndex == kCache32Count - 1);
642 }
643
reed@android.com41bccf52009-04-03 13:33:51 +0000644 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000645 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000646 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000648 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000649 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000650 for (int i = 0; i < kCache32Count; i++) {
651 int index = map->mapUnit16((i << 8) | i) >> 8;
652 mapped[i] = linear[index];
653 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000654 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000655 fCache32PixelRef->unref();
656 fCache32PixelRef = newPR;
657 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000658 }
659 }
660 return fCache32;
661}
662
reed@google.comdc731fd2010-12-23 15:19:47 +0000663/*
664 * Because our caller might rebuild the same (logically the same) gradient
665 * over and over, we'd like to return exactly the same "bitmap" if possible,
666 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
667 * To do that, we maintain a private cache of built-bitmaps, based on our
668 * colors and positions. Note: we don't try to flatten the fMapper, so if one
669 * is present, we skip the cache for now.
670 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000671void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.comdc731fd2010-12-23 15:19:47 +0000672 // don't have a way to put the mapper into our cache-key yet
673 if (fMapper) {
674 // force our cahce32pixelref to be built
675 (void)this->getCache32();
676 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
677 bitmap->setPixelRef(fCache32PixelRef);
678 return;
679 }
680
681 // build our key: [numColors + colors[] + {positions[]} ]
682 int count = 1 + fColorCount;
683 if (fColorCount > 2) {
684 count += fColorCount - 1; // fRecs[].fPos
685 }
686
687 SkAutoSTMalloc<16, int32_t> storage(count);
688 int32_t* buffer = storage.get();
689
690 *buffer++ = fColorCount;
691 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
692 buffer += fColorCount;
693 if (fColorCount > 2) {
694 for (int i = 1; i < fColorCount; i++) {
695 *buffer++ = fRecs[i].fPos;
696 }
697 }
698 SkASSERT(buffer - storage.get() == count);
699
700 ///////////////////////////////////
701
702 static SkMutex gMutex;
703 static SkBitmapCache* gCache;
704 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
705 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
706 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000707
reed@google.comdc731fd2010-12-23 15:19:47 +0000708 if (NULL == gCache) {
709 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
710 }
711 size_t size = count * sizeof(int32_t);
712
713 if (!gCache->find(storage.get(), size, bitmap)) {
714 // force our cahce32pixelref to be built
715 (void)this->getCache32();
716 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
717 bitmap->setPixelRef(fCache32PixelRef);
718
719 gCache->add(storage.get(), size, *bitmap);
720 }
721}
722
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000723void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
724 if (info) {
725 if (info->fColorCount >= fColorCount) {
726 if (info->fColors) {
727 memcpy(info->fColors, fOrigColors,
728 fColorCount * sizeof(SkColor));
729 }
730 if (info->fColorOffsets) {
731 if (fColorCount == 2) {
732 info->fColorOffsets[0] = 0;
733 info->fColorOffsets[1] = SK_Scalar1;
734 } else if (fColorCount > 2) {
735 for (int i = 0; i < fColorCount; i++)
736 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
737 }
738 }
739 }
740 info->fColorCount = fColorCount;
741 info->fTileMode = fTileMode;
742 }
743}
744
reed@google.com61eb0402011-04-15 12:11:12 +0000745///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746
reed@android.com41bccf52009-04-03 13:33:51 +0000747static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000748 SkVector vec = pts[1] - pts[0];
749 SkScalar mag = vec.length();
750 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
751
752 vec.scale(inv);
753 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
754 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
755 matrix->postScale(inv, inv);
756}
757
758///////////////////////////////////////////////////////////////////////////////
759
760class Linear_Gradient : public Gradient_Shader {
761public:
762 Linear_Gradient(const SkPoint pts[2],
763 const SkColor colors[], const SkScalar pos[], int colorCount,
764 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000765 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
766 fStart(pts[0]),
767 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000768 {
769 pts_to_unit_matrix(pts, &fPtsToUnit);
770 }
reed@android.com9b46e772009-06-05 12:24:41 +0000771
reed@android.com5119bdb2009-06-12 21:27:03 +0000772 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000773 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
774 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000775 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*,
reed@google.com7c2f27d2011-03-07 19:29:00 +0000776 TileMode*, SkScalar* twoPointRadialParams) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000777 virtual GradientType asAGradient(GradientInfo* info) const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778
reed@google.com55b8e8c2011-01-13 16:22:35 +0000779 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780 return SkNEW_ARGS(Linear_Gradient, (buffer));
781 }
782
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000783 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
784 this->INHERITED::flatten(buffer);
785 buffer.writeScalar(fStart.fX);
786 buffer.writeScalar(fStart.fY);
787 buffer.writeScalar(fEnd.fX);
788 buffer.writeScalar(fEnd.fY);
789 }
790
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000792 Linear_Gradient(SkFlattenableReadBuffer& buffer)
793 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000794 fStart(unflatten_point(buffer)),
795 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000796 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 virtual Factory getFactory() { return CreateProc; }
798
799private:
800 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000801 const SkPoint fStart;
802 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803};
804
reed@android.com5119bdb2009-06-12 21:27:03 +0000805bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
806 const SkMatrix& matrix) {
807 if (!this->INHERITED::setContext(device, paint, matrix)) {
808 return false;
809 }
810
811 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
812 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000813 fFlags |= SkShader::kConstInY32_Flag;
814 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
815 // only claim this if we do have a 16bit mode (i.e. none of our
816 // colors have alpha), and if we are not dithering (which obviously
817 // is not const in Y).
818 fFlags |= SkShader::kConstInY16_Flag;
819 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000820 }
821 return true;
822}
823
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@google.com61eb0402011-04-15 12:11:12 +0000825static inline bool no_need_for_clamp(int fx, int dx, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826 SkASSERT(count > 0);
827 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
828}
829
reed@google.com5eb158d2011-04-15 15:50:34 +0000830#include "SkClampRange.h"
831
832#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000833 do { \
834 unsigned fi = fx >> 8; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000835 SkASSERT(fi <= 0xFF); \
836 fx += dx; \
837 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000838 toggle ^= TOGGLE_MASK; \
839 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000840
841
reed@google.com61eb0402011-04-15 12:11:12 +0000842void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843 SkASSERT(count > 0);
844
845 SkPoint srcPt;
846 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
847 TileProc proc = fTileProc;
848 const SkPMColor* cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000849#ifdef USE_DITHER_32BIT_GRADIENT
850 int toggle = ((x ^ y) & 1) << kCache32Bits;
851 const int TOGGLE_MASK = (1 << kCache32Bits);
852#else
853 int toggle = 0;
854 const int TOGGLE_MASK = 0;
855#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856
reed@android.comc552a432009-06-12 20:02:50 +0000857 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000858 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
859 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
861
reed@android.comc552a432009-06-12 20:02:50 +0000862 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863 SkFixed dxStorage[1];
864 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
865 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000866 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000867 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
868 dx = SkScalarToFixed(fDstToIndex.getScaleX());
869 }
870
reed@android.comc552a432009-06-12 20:02:50 +0000871 if (SkFixedNearlyZero(dx)) {
872 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873 unsigned fi = proc(fx);
874 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000875 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000876 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000877 } else if (proc == clamp_tileproc) {
reed@google.com17705072011-04-18 12:43:32 +0000878#ifdef SK_ENABLE_FAST_LINEAR_GRADIENTS
reed@google.com5eb158d2011-04-15 15:50:34 +0000879 SkClampRange range;
880 range.init(fx, dx, count, 0, 0xFF);
881
882 if ((count = range.fCount0) > 0) {
883 sk_memset32_dither(dstC,
884 cache[toggle + range.fV0],
885 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
886 count);
887 dstC += count;
888 }
889 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +0000890 int unroll = count >> 3;
891 fx = range.fFx1;
892 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000893 NO_CHECK_ITER; NO_CHECK_ITER;
894 NO_CHECK_ITER; NO_CHECK_ITER;
895 NO_CHECK_ITER; NO_CHECK_ITER;
896 NO_CHECK_ITER; NO_CHECK_ITER;
897 }
898 if ((count &= 7) > 0) {
899 do {
900 NO_CHECK_ITER;
901 } while (--count != 0);
902 }
903 }
904 if ((count = range.fCount2) > 0) {
905 sk_memset32_dither(dstC,
906 cache[toggle + range.fV1],
907 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
908 count);
909 }
reed@google.com17705072011-04-18 12:43:32 +0000910#else
911 do {
912 unsigned fi = SkClampMax(fx >> 8, 0xFF);
913 SkASSERT(fi <= 0xFF);
914 fx += dx;
915 *dstC++ = cache[toggle + fi];
916 toggle ^= TOGGLE_MASK;
917 } while (--count != 0);
reed@google.com5eb158d2011-04-15 15:50:34 +0000918#endif
reed@android.comc552a432009-06-12 20:02:50 +0000919 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000920 do {
921 unsigned fi = mirror_8bits(fx >> 8);
922 SkASSERT(fi <= 0xFF);
923 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000924 *dstC++ = cache[toggle + fi];
925 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000926 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000927 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000928 SkASSERT(proc == repeat_tileproc);
929 do {
930 unsigned fi = repeat_8bits(fx >> 8);
931 SkASSERT(fi <= 0xFF);
932 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000933 *dstC++ = cache[toggle + fi];
934 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000935 } while (--count != 0);
936 }
reed@android.comc552a432009-06-12 20:02:50 +0000937 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938 SkScalar dstX = SkIntToScalar(x);
939 SkScalar dstY = SkIntToScalar(y);
940 do {
941 dstProc(fDstToIndex, dstX, dstY, &srcPt);
942 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
943 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000944 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
945 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946 dstX += SK_Scalar1;
947 } while (--count != 0);
948 }
949}
950
reed@google.com55b8e8c2011-01-13 16:22:35 +0000951SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000952 SkMatrix* matrix,
953 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000954 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000956 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000957 }
958 if (matrix) {
959 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
960 matrix->preConcat(fPtsToUnit);
961 }
962 if (xy) {
963 xy[0] = fTileMode;
964 xy[1] = kClamp_TileMode;
965 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000966 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967}
968
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000969SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
970 if (info) {
971 commonAsAGradient(info);
972 info->fPoint[0] = fStart;
973 info->fPoint[1] = fEnd;
974 }
975 return kLinear_GradientType;
976}
977
reed@android.com3c9b2a42009-08-27 19:28:37 +0000978static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
979 int count) {
980 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000981 *dst++ = value;
982 count -= 1;
983 SkTSwap(value, other);
984 }
985
986 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000987
reed@android.com3c9b2a42009-08-27 19:28:37 +0000988 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000990 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992
reed@google.com5eb158d2011-04-15 15:50:34 +0000993#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +0000994 do { \
995 unsigned fi = fx >> kCache16Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000996 SkASSERT(fi <= kCache16Mask); \
997 fx += dx; \
998 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000999 toggle ^= TOGGLE_MASK; \
1000 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +00001001
1002
reed@google.com61eb0402011-04-15 12:11:12 +00001003void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 SkASSERT(count > 0);
1005
1006 SkPoint srcPt;
1007 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1008 TileProc proc = fTileProc;
1009 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@google.com5eb158d2011-04-15 15:50:34 +00001011 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001013 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001014 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1015 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001016 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1017
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001018 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019 SkFixed dxStorage[1];
1020 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1021 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001022 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1024 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1025 }
1026
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001027 if (SkFixedNearlyZero(dx)) {
1028 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +00001029 unsigned fi = proc(fx) >> kCache16Shift;
1030 SkASSERT(fi <= kCache16Mask);
reed@google.com5eb158d2011-04-15 15:50:34 +00001031 dither_memset16(dstC, cache[toggle + fi],
1032 cache[(toggle ^ TOGGLE_MASK) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001033 } else if (proc == clamp_tileproc) {
reed@google.com17705072011-04-18 12:43:32 +00001034#ifdef SK_ENABLE_FAST_LINEAR_GRADIENTS
reed@google.com5eb158d2011-04-15 15:50:34 +00001035 SkClampRange range;
1036 range.init(fx, dx, count, 0, kCache16Mask);
1037
1038 if ((count = range.fCount0) > 0) {
1039 dither_memset16(dstC,
1040 cache[toggle + range.fV0],
1041 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
1042 count);
1043 dstC += count;
1044 }
1045 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +00001046 int unroll = count >> 3;
1047 fx = range.fFx1;
1048 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001049 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1050 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1051 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1052 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1053 }
1054 if ((count &= 7) > 0) {
1055 do {
1056 NO_CHECK_ITER_16;
1057 } while (--count != 0);
1058 }
1059 }
1060 if ((count = range.fCount2) > 0) {
1061 dither_memset16(dstC,
1062 cache[toggle + range.fV1],
1063 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
1064 count);
1065 }
reed@google.com17705072011-04-18 12:43:32 +00001066#else
1067 do {
1068 unsigned fi = SkClampMax(fx >> kCache16Shift, kCache16Mask);
1069 SkASSERT(fi <= kCache16Mask);
1070 fx += dx;
1071 *dstC++ = cache[toggle + fi];
1072 toggle ^= TOGGLE_MASK;
1073 } while (--count != 0);
reed@google.com5eb158d2011-04-15 15:50:34 +00001074#endif
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001075 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001076 do {
reed@android.com200645d2009-12-14 16:41:57 +00001077 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001078 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001079 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001080 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001081 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001083 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001084 SkASSERT(proc == repeat_tileproc);
1085 do {
reed@android.com200645d2009-12-14 16:41:57 +00001086 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001087 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001088 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001089 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001090 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001091 } while (--count != 0);
1092 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001093 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001094 SkScalar dstX = SkIntToScalar(x);
1095 SkScalar dstY = SkIntToScalar(y);
1096 do {
1097 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1098 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1099 SkASSERT(fi <= 0xFFFF);
1100
reed@android.com512a8762009-12-14 15:25:36 +00001101 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001102 *dstC++ = cache[toggle + index];
reed@google.com5eb158d2011-04-15 15:50:34 +00001103 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001104
1105 dstX += SK_Scalar1;
1106 } while (--count != 0);
1107 }
1108}
1109
1110///////////////////////////////////////////////////////////////////////////////
1111
1112#define kSQRT_TABLE_BITS 11
1113#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1114
1115#include "SkRadialGradient_Table.h"
1116
1117#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1118
1119#include <stdio.h>
1120
reed@google.com61eb0402011-04-15 12:11:12 +00001121void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1123
1124 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1125 SkASSERT(file);
1126 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1127
reed@google.com61eb0402011-04-15 12:11:12 +00001128 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1129 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001130 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001131 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001132
1133 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1134
1135 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001136 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001137 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001138 }
1139 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001140 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001141 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001142 }
1143 ::fprintf(file, "};\n");
1144 ::fclose(file);
1145}
1146
1147#endif
1148
1149
reed@google.com61eb0402011-04-15 12:11:12 +00001150static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1151 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001152 SkScalar inv = SkScalarInvert(radius);
1153
1154 matrix->setTranslate(-center.fX, -center.fY);
1155 matrix->postScale(inv, inv);
1156}
1157
1158class Radial_Gradient : public Gradient_Shader {
1159public:
1160 Radial_Gradient(const SkPoint& center, SkScalar radius,
1161 const SkColor colors[], const SkScalar pos[], int colorCount,
1162 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001163 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1164 fCenter(center),
1165 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001166 {
1167 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1168 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1169
1170 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1171 }
reed@google.com61eb0402011-04-15 12:11:12 +00001172
1173 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001174 SkASSERT(count > 0);
1175
1176 SkPoint srcPt;
1177 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1178 TileProc proc = fTileProc;
1179 const SkPMColor* cache = this->getCache32();
1180
reed@google.com61eb0402011-04-15 12:11:12 +00001181 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001182 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1183 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001184 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1185 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1186
reed@google.com61eb0402011-04-15 12:11:12 +00001187 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 SkFixed storage[2];
1189 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1190 dx = storage[0];
1191 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00001192 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001193 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1194 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1195 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1196 }
1197
reed@google.com61eb0402011-04-15 12:11:12 +00001198 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 const uint8_t* sqrt_table = gSqrt8Table;
1200 fx >>= 1;
1201 dx >>= 1;
1202 fy >>= 1;
1203 dy >>= 1;
1204 do {
1205 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1206 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1207 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1208 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1209 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1210 fx += dx;
1211 fy += dy;
1212 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001213 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001215 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1216 if (magnitudeSquared < 0) // Overflow.
1217 magnitudeSquared = SK_FixedMax;
1218 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001219 unsigned fi = mirror_tileproc(dist);
1220 SkASSERT(fi <= 0xFFFF);
1221 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1222 fx += dx;
1223 fy += dy;
1224 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001225 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001226 SkASSERT(proc == repeat_tileproc);
1227 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001228 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1229 if (magnitudeSquared < 0) // Overflow.
1230 magnitudeSquared = SK_FixedMax;
1231 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 unsigned fi = repeat_tileproc(dist);
1233 SkASSERT(fi <= 0xFFFF);
1234 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1235 fx += dx;
1236 fy += dy;
1237 } while (--count != 0);
1238 }
reed@google.com61eb0402011-04-15 12:11:12 +00001239 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 SkScalar dstX = SkIntToScalar(x);
1241 SkScalar dstY = SkIntToScalar(y);
1242 do {
1243 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1244 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1245 SkASSERT(fi <= 0xFFFF);
1246 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1247 dstX += SK_Scalar1;
1248 } while (--count != 0);
1249 }
1250 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001251
1252 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 SkASSERT(count > 0);
1254
1255 SkPoint srcPt;
1256 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1257 TileProc proc = fTileProc;
1258 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260
reed@android.com3c9b2a42009-08-27 19:28:37 +00001261 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001262 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1263 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1265 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1266
reed@android.com3c9b2a42009-08-27 19:28:37 +00001267 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001268 SkFixed storage[2];
1269 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1270 dx = storage[0];
1271 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001272 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1274 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1275 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1276 }
1277
reed@android.com3c9b2a42009-08-27 19:28:37 +00001278 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 const uint8_t* sqrt_table = gSqrt8Table;
1280
1281 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1282 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1283 precision, but that appears to be visually OK. If we decide this is OK for
1284 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1285 to avoid having to do these extra shifts each time.
1286 */
1287 fx >>= 1;
1288 dx >>= 1;
1289 fy >>= 1;
1290 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001291 if (dy == 0) { // might perform this check for the other modes, but the win will be a smaller % of the total
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1293 fy *= fy;
1294 do {
1295 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1296 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1297 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1298 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1300 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001302 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303 do {
1304 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1305 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1306 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1307 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1308 fx += dx;
1309 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1311 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312 } while (--count != 0);
1313 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001314 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315 do {
1316 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1317 unsigned fi = mirror_tileproc(dist);
1318 SkASSERT(fi <= 0xFFFF);
1319 fx += dx;
1320 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001321 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1322 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001324 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001325 SkASSERT(proc == repeat_tileproc);
1326 do {
1327 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1328 unsigned fi = repeat_tileproc(dist);
1329 SkASSERT(fi <= 0xFFFF);
1330 fx += dx;
1331 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001332 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1333 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334 } while (--count != 0);
1335 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001336 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 SkScalar dstX = SkIntToScalar(x);
1338 SkScalar dstY = SkIntToScalar(y);
1339 do {
1340 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1341 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1342 SkASSERT(fi <= 0xFFFF);
1343
1344 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 *dstC++ = cache[toggle + index];
1346 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001347
1348 dstX += SK_Scalar1;
1349 } while (--count != 0);
1350 }
1351 }
1352
reed@google.com55b8e8c2011-01-13 16:22:35 +00001353 virtual BitmapType asABitmap(SkBitmap* bitmap,
1354 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001355 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001356 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001357 if (bitmap) {
1358 this->commonAsABitmap(bitmap);
1359 }
1360 if (matrix) {
1361 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1362 matrix->preConcat(fPtsToUnit);
1363 }
1364 if (xy) {
1365 xy[0] = fTileMode;
1366 xy[1] = kClamp_TileMode;
1367 }
1368 return kRadial_BitmapType;
1369 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001370 virtual GradientType asAGradient(GradientInfo* info) const {
1371 if (info) {
1372 commonAsAGradient(info);
1373 info->fPoint[0] = fCenter;
1374 info->fRadius[0] = fRadius;
1375 }
1376 return kRadial_GradientType;
1377 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001378
1379 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 return SkNEW_ARGS(Radial_Gradient, (buffer));
1381 }
1382
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001383 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1384 this->INHERITED::flatten(buffer);
1385 buffer.writeScalar(fCenter.fX);
1386 buffer.writeScalar(fCenter.fY);
1387 buffer.writeScalar(fRadius);
1388 }
1389
reed@android.com8a1c16f2008-12-17 15:59:43 +00001390protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001391 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1392 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001393 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001394 fRadius(buffer.readScalar()) {
1395 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 virtual Factory getFactory() { return CreateProc; }
1397
1398private:
1399 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001400 const SkPoint fCenter;
1401 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402};
1403
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001404/* Two-point radial gradients are specified by two circles, each with a center
1405 point and radius. The gradient can be considered to be a series of
1406 concentric circles, with the color interpolated from the start circle
1407 (at t=0) to the end circle (at t=1).
1408
1409 For each point (x, y) in the span, we want to find the
1410 interpolated circle that intersects that point. The center
1411 of the desired circle (Cx, Cy) falls at some distance t
1412 along the line segment between the start point (Sx, Sy) and
1413 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001414
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001415 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1416 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001417
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001418 The radius of the desired circle (r) is also a linear interpolation t
1419 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001420
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001421 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001422
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001423 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001424
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001425 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001426
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001427 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001428
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001429 (x - ((1 - t) * Sx + t * Ex))^2
1430 + (y - ((1 - t) * Sy + t * Ey))^2
1431 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001432
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001433 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001434
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001435 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1436 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1437 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001438
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001439 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1440
1441 [Dx^2 + Dy^2 - Dr^2)] * t^2
1442 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1443 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001444
1445 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001446 possible circles on which the point may fall. Solving for t yields
1447 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001448
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001449 If a<0, the start circle is entirely contained in the
1450 end circle, and one of the roots will be <0 or >1 (off the line
1451 segment). If a>0, the start circle falls at least partially
1452 outside the end circle (or vice versa), and the gradient
1453 defines a "tube" where a point may be on one circle (on the
1454 inside of the tube) or the other (outside of the tube). We choose
1455 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001456
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001457 In order to keep the math to within the limits of fixed point,
1458 we divide the entire quadratic by Dr^2, and replace
1459 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001460
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001461 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1462 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1463 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001464
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001465 (x' and y' are computed by appending the subtract and scale to the
1466 fDstToIndex matrix in the constructor).
1467
1468 Since the 'A' component of the quadratic is independent of x' and y', it
1469 is precomputed in the constructor. Since the 'B' component is linear in
1470 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001471 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001472 a perspective projection), it must be computed in the loop.
1473
1474*/
1475
reed@google.come61414c2011-04-15 18:14:16 +00001476#ifdef SK_USE_SLOW_2POINT_RADIAL_GRADIENT
1477static inline SkFixed two_point_radial(SkFixed b, SkFixed fx, SkFixed fy, SkFixed sr2d2, SkFixed foura, SkFixed oneOverTwoA, bool posRoot) {
1478 SkFixed c = SkFixedSquare(fx) + SkFixedSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001479 if (0 == foura) {
1480 return SkFixedDiv(-c, b);
1481 }
reed@google.come61414c2011-04-15 18:14:16 +00001482 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1483 if (discrim < 0) {
1484 discrim = -discrim;
1485 }
1486 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1487 if (posRoot) {
1488 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1489 } else {
1490 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1491 }
1492}
1493#else
reed@google.com84e9c082011-04-13 17:44:24 +00001494static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1495 SkScalar sr2d2, SkScalar foura,
1496 SkScalar oneOverTwoA, bool posRoot) {
1497 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001498 if (0 == foura) {
1499 return SkScalarToFixed(SkScalarDiv(-c, b));
1500 }
1501
reed@google.com84e9c082011-04-13 17:44:24 +00001502 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001503 if (discrim < 0) {
1504 discrim = -discrim;
1505 }
reed@google.com84e9c082011-04-13 17:44:24 +00001506 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1507 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001508 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001509 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001510 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001511 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001512 }
reed@google.com84e9c082011-04-13 17:44:24 +00001513 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001514}
reed@google.come61414c2011-04-15 18:14:16 +00001515#endif
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001516
1517class Two_Point_Radial_Gradient : public Gradient_Shader {
1518public:
1519 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1520 const SkPoint& end, SkScalar endRadius,
1521 const SkColor colors[], const SkScalar pos[],
1522 int colorCount, SkShader::TileMode mode,
1523 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001524 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1525 fCenter1(start),
1526 fCenter2(end),
1527 fRadius1(startRadius),
1528 fRadius2(endRadius) {
1529 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001530 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001531
1532 virtual BitmapType asABitmap(SkBitmap* bitmap,
1533 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001534 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001535 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001536 if (bitmap) {
1537 this->commonAsABitmap(bitmap);
1538 }
1539 SkScalar diffL = 0; // just to avoid gcc warning
1540 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001541 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001542 SkScalarSquare(fDiff.fY));
1543 }
1544 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001545 if (diffL) {
1546 SkScalar invDiffL = SkScalarInvert(diffL);
1547 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1548 SkScalarMul(invDiffL, fDiff.fX));
1549 } else {
1550 matrix->reset();
1551 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001552 matrix->preConcat(fPtsToUnit);
1553 }
1554 if (xy) {
1555 xy[0] = fTileMode;
1556 xy[1] = kClamp_TileMode;
1557 }
1558 if (NULL != twoPointRadialParams) {
1559 twoPointRadialParams[0] = diffL;
1560 twoPointRadialParams[1] = fStartRadius;
1561 twoPointRadialParams[2] = fDiffRadius;
1562 }
1563 return kTwoPointRadial_BitmapType;
1564 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001565
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001566 virtual GradientType asAGradient(GradientInfo* info) const {
1567 if (info) {
1568 commonAsAGradient(info);
1569 info->fPoint[0] = fCenter1;
1570 info->fPoint[1] = fCenter2;
1571 info->fRadius[0] = fRadius1;
1572 info->fRadius[1] = fRadius2;
1573 }
1574 return kRadial2_GradientType;
1575 }
1576
reed@google.come61414c2011-04-15 18:14:16 +00001577#ifdef SK_USE_SLOW_2POINT_RADIAL_GRADIENT
1578 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1579 {
1580 SkASSERT(count > 0);
1581
1582 // Zero difference between radii: fill with transparent black.
1583 if (fDiffRadius == 0) {
1584 sk_bzero(dstC, count * sizeof(*dstC));
1585 return;
1586 }
1587 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1588 TileProc proc = fTileProc;
1589 const SkPMColor* cache = this->getCache32();
1590 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1591 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1592 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1593 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1594 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1595 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1596 bool posRoot = fDiffRadius < 0;
1597 if (fDstToIndexClass != kPerspective_MatrixClass)
1598 {
1599 SkPoint srcPt;
1600 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1601 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1602 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1603 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1604
1605 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1606 {
1607 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1608 }
1609 else
1610 {
1611 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1612 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1613 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1614 }
1615 SkFixed b = (SkFixedMul(diffx, fx) +
1616 SkFixedMul(diffy, fy) - startRadius) << 1;
1617 SkFixed db = (SkFixedMul(diffx, dx) +
1618 SkFixedMul(diffy, dy)) << 1;
1619 if (proc == clamp_tileproc)
1620 {
1621 for (; count > 0; --count) {
1622 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1623 SkFixed index = SkClampMax(t, 0xFFFF);
1624 SkASSERT(index <= 0xFFFF);
1625 *dstC++ = cache[index >> (16 - kCache32Bits)];
1626 fx += dx;
1627 fy += dy;
1628 b += db;
1629 }
1630 }
1631 else if (proc == mirror_tileproc)
1632 {
1633 for (; count > 0; --count) {
1634 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1635 SkFixed index = mirror_tileproc(t);
1636 SkASSERT(index <= 0xFFFF);
1637 *dstC++ = cache[index >> (16 - kCache32Bits)];
1638 fx += dx;
1639 fy += dy;
1640 b += db;
1641 }
1642 }
1643 else
1644 {
1645 SkASSERT(proc == repeat_tileproc);
1646 for (; count > 0; --count) {
1647 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1648 SkFixed index = repeat_tileproc(t);
1649 SkASSERT(index <= 0xFFFF);
1650 *dstC++ = cache[index >> (16 - kCache32Bits)];
1651 fx += dx;
1652 fy += dy;
1653 b += db;
1654 }
1655 }
1656 }
1657 else // perspective case
1658 {
1659 SkScalar dstX = SkIntToScalar(x);
1660 SkScalar dstY = SkIntToScalar(y);
1661 for (; count > 0; --count) {
1662 SkPoint srcPt;
1663 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1664 SkFixed fx = SkScalarToFixed(srcPt.fX);
1665 SkFixed fy = SkScalarToFixed(srcPt.fY);
1666 SkFixed b = (SkFixedMul(diffx, fx) +
1667 SkFixedMul(diffy, fy) - startRadius) << 1;
1668 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1669 SkFixed index = proc(t);
1670 SkASSERT(index <= 0xFFFF);
1671 *dstC++ = cache[index >> (16 - kCache32Bits)];
1672 dstX += SK_Scalar1;
1673 }
1674 }
1675 }
1676#else
reed@google.com61eb0402011-04-15 12:11:12 +00001677 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001678 SkASSERT(count > 0);
1679
1680 // Zero difference between radii: fill with transparent black.
1681 if (fDiffRadius == 0) {
1682 sk_bzero(dstC, count * sizeof(*dstC));
1683 return;
1684 }
1685 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1686 TileProc proc = fTileProc;
1687 const SkPMColor* cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001688
1689 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001690 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001691 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001692 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001693 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1694 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001695 SkScalar dx, fx = srcPt.fX;
1696 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001697
reed@google.com61eb0402011-04-15 12:11:12 +00001698 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001699 SkFixed fixedX, fixedY;
1700 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1701 dx = SkFixedToScalar(fixedX);
1702 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001703 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001704 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001705 dx = fDstToIndex.getScaleX();
1706 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001707 }
reed@google.com84e9c082011-04-13 17:44:24 +00001708 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1709 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1710 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1711 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001712 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001713 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001714 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001715 SkFixed index = SkClampMax(t, 0xFFFF);
1716 SkASSERT(index <= 0xFFFF);
1717 *dstC++ = cache[index >> (16 - kCache32Bits)];
1718 fx += dx;
1719 fy += dy;
1720 b += db;
1721 }
reed@google.com61eb0402011-04-15 12:11:12 +00001722 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001723 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001724 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001725 SkFixed index = mirror_tileproc(t);
1726 SkASSERT(index <= 0xFFFF);
1727 *dstC++ = cache[index >> (16 - kCache32Bits)];
1728 fx += dx;
1729 fy += dy;
1730 b += db;
1731 }
reed@google.com61eb0402011-04-15 12:11:12 +00001732 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001733 SkASSERT(proc == repeat_tileproc);
1734 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001735 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001736 SkFixed index = repeat_tileproc(t);
1737 SkASSERT(index <= 0xFFFF);
1738 *dstC++ = cache[index >> (16 - kCache32Bits)];
1739 fx += dx;
1740 fy += dy;
1741 b += db;
1742 }
1743 }
reed@google.com61eb0402011-04-15 12:11:12 +00001744 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001745 SkScalar dstX = SkIntToScalar(x);
1746 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001747 for (; count > 0; --count) {
1748 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001749 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001750 SkScalar fx = srcPt.fX;
1751 SkScalar fy = srcPt.fY;
1752 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1753 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1754 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001755 SkFixed index = proc(t);
1756 SkASSERT(index <= 0xFFFF);
1757 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001758 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001759 }
1760 }
1761 }
reed@google.come61414c2011-04-15 18:14:16 +00001762#endif
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001763
reed@android.com6c59a172009-09-22 20:24:05 +00001764 virtual bool setContext(const SkBitmap& device,
1765 const SkPaint& paint,
1766 const SkMatrix& matrix) {
1767 if (!this->INHERITED::setContext(device, paint, matrix)) {
1768 return false;
1769 }
1770
1771 // we don't have a span16 proc
1772 fFlags &= ~kHasSpan16_Flag;
1773 return true;
1774 }
1775
reed@google.com55b8e8c2011-01-13 16:22:35 +00001776 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001777 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1778 }
1779
reed@android.combcfc7332009-11-10 16:19:39 +00001780 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1781 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001782 buffer.writeScalar(fCenter1.fX);
1783 buffer.writeScalar(fCenter1.fY);
1784 buffer.writeScalar(fCenter2.fX);
1785 buffer.writeScalar(fCenter2.fY);
1786 buffer.writeScalar(fRadius1);
1787 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001788 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001789
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001790protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001791 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001792 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001793 fCenter1(unflatten_point(buffer)),
1794 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001795 fRadius1(buffer.readScalar()),
1796 fRadius2(buffer.readScalar()) {
1797 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001798 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001799 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001800
1801private:
1802 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001803 const SkPoint fCenter1;
1804 const SkPoint fCenter2;
1805 const SkScalar fRadius1;
1806 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001807 SkPoint fDiff;
1808 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001809
1810 void init() {
1811 fDiff = fCenter1 - fCenter2;
1812 fDiffRadius = fRadius2 - fRadius1;
1813 SkScalar inv = SkScalarInvert(fDiffRadius);
1814 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1815 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1816 fStartRadius = SkScalarMul(fRadius1, inv);
1817 fSr2D2 = SkScalarSquare(fStartRadius);
1818 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1819 fOneOverTwoA = SkScalarInvert(fA * 2);
1820
1821 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1822 fPtsToUnit.postScale(inv, inv);
1823 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001824};
1825
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826///////////////////////////////////////////////////////////////////////////////
1827
1828class Sweep_Gradient : public Gradient_Shader {
1829public:
1830 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1831 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001832 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1833 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834 {
1835 fPtsToUnit.setTranslate(-cx, -cy);
1836 }
1837 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1838 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001839
1840 virtual BitmapType asABitmap(SkBitmap* bitmap,
1841 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001842 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001843 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001844 if (bitmap) {
1845 this->commonAsABitmap(bitmap);
1846 }
1847 if (matrix) {
1848 *matrix = fPtsToUnit;
1849 }
1850 if (xy) {
1851 xy[0] = fTileMode;
1852 xy[1] = kClamp_TileMode;
1853 }
1854 return kSweep_BitmapType;
1855 }
1856
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001857 virtual GradientType asAGradient(GradientInfo* info) const {
1858 if (info) {
1859 commonAsAGradient(info);
1860 info->fPoint[0] = fCenter;
1861 }
1862 return kSweep_GradientType;
1863 }
1864
reed@android.com8a1c16f2008-12-17 15:59:43 +00001865 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1866 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1867 }
1868
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001869 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1870 this->INHERITED::flatten(buffer);
1871 buffer.writeScalar(fCenter.fX);
1872 buffer.writeScalar(fCenter.fY);
1873 }
1874
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001876 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1877 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001878 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001879 }
1880
reed@android.com8a1c16f2008-12-17 15:59:43 +00001881 virtual Factory getFactory() { return CreateProc; }
1882
1883private:
1884 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001885 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886};
1887
1888#ifdef COMPUTE_SWEEP_TABLE
1889#define PI 3.14159265
1890static bool gSweepTableReady;
1891static uint8_t gSweepTable[65];
1892
1893/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1894 We scale the results to [0..32]
1895*/
reed@google.com61eb0402011-04-15 12:11:12 +00001896static const uint8_t* build_sweep_table() {
1897 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001898 const int N = 65;
1899 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001900
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901 for (int i = 0; i < N; i++)
1902 {
1903 double arg = i / DENOM;
1904 double v = atan(arg);
1905 int iv = (int)round(v * DENOM * 2 / PI);
1906// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1907 printf("%d, ", iv);
1908 gSweepTable[i] = iv;
1909 }
1910 gSweepTableReady = true;
1911 }
1912 return gSweepTable;
1913}
1914#else
1915static const uint8_t gSweepTable[] = {
1916 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1917 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1918 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1919 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1920 32
1921};
1922static const uint8_t* build_sweep_table() { return gSweepTable; }
1923#endif
1924
1925// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1926// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1927// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1928
1929//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001930static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 SkASSERT(numer <= denom);
1932 SkASSERT(numer > 0);
1933 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001934
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 int nbits = SkCLZ(numer);
1936 int dbits = SkCLZ(denom);
1937 int bits = 6 - nbits + dbits;
1938 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001939
reed@google.com61eb0402011-04-15 12:11:12 +00001940 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001941 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001942 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001943
1944 denom <<= dbits - 1;
1945 numer <<= nbits - 1;
1946
1947 unsigned result = 0;
1948
1949 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001950 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001952 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001953 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001954 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001955
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001957 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001958 // make room for the rest of the answer bits
1959 result <<= bits;
1960 switch (bits) {
1961 case 6:
1962 if ((numer = (numer << 1) - denom) >= 0)
1963 result |= 32;
1964 else
1965 numer += denom;
1966 case 5:
1967 if ((numer = (numer << 1) - denom) >= 0)
1968 result |= 16;
1969 else
1970 numer += denom;
1971 case 4:
1972 if ((numer = (numer << 1) - denom) >= 0)
1973 result |= 8;
1974 else
1975 numer += denom;
1976 case 3:
1977 if ((numer = (numer << 1) - denom) >= 0)
1978 result |= 4;
1979 else
1980 numer += denom;
1981 case 2:
1982 if ((numer = (numer << 1) - denom) >= 0)
1983 result |= 2;
1984 else
1985 numer += denom;
1986 case 1:
1987 default: // not strictly need, but makes GCC make better ARM code
1988 if ((numer = (numer << 1) - denom) >= 0)
1989 result |= 1;
1990 else
1991 numer += denom;
1992 }
1993 }
1994 return result;
1995}
1996
1997// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001998static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001999#ifdef SK_DEBUG
2000 {
2001 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00002002 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 gOnce = true;
2004 SkASSERT(div_64(55, 55) == 64);
2005 SkASSERT(div_64(128, 256) == 32);
2006 SkASSERT(div_64(2326528, 4685824) == 31);
2007 SkASSERT(div_64(753664, 5210112) == 9);
2008 SkASSERT(div_64(229376, 4882432) == 3);
2009 SkASSERT(div_64(2, 64) == 2);
2010 SkASSERT(div_64(1, 64) == 1);
2011 // test that we handle underflow correctly
2012 SkASSERT(div_64(12345, 0x54321234) == 0);
2013 }
2014 }
2015#endif
2016
2017 SkASSERT(y > 0 && x > 0);
2018 const uint8_t* table = build_sweep_table();
2019
2020 unsigned result;
2021 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002022 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002023 // first part of the atan(v) = PI/2 - atan(1/v) identity
2024 // since our div_64 and table want v <= 1, where v = y/x
2025 SkTSwap<SkFixed>(x, y);
2026 }
2027
2028 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002029
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030#ifdef SK_DEBUG
2031 {
2032 unsigned result2 = SkDivBits(y, x, 6);
2033 SkASSERT(result2 == result ||
2034 (result == 1 && result2 == 0));
2035 }
2036#endif
2037
2038 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2039 result = table[result];
2040
reed@google.com61eb0402011-04-15 12:11:12 +00002041 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 // complete the atan(v) = PI/2 - atan(1/v) identity
2043 result = 64 - result;
2044 // pin to 63
2045 result -= result >> 6;
2046 }
2047
2048 SkASSERT(result <= 63);
2049 return result;
2050}
2051
2052// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com61eb0402011-04-15 12:11:12 +00002053static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2054 if (x == 0) {
2055 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002056 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002057 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002058 return y < 0 ? 192 : 64;
2059 }
reed@google.com61eb0402011-04-15 12:11:12 +00002060 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002062 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002063
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 /* Find the right quadrant for x,y
2065 Since atan_0_90 only handles the first quadrant, we rotate x,y
2066 appropriately before calling it, and then add the right amount
2067 to account for the real quadrant.
2068 quadrant 0 : add 0 | x > 0 && y > 0
2069 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2070 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2071 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002072
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073 map x<0 to (1 << 6)
2074 map y<0 to (3 << 6)
2075 add = map_x ^ map_y
2076 */
2077 int xsign = x >> 31;
2078 int ysign = y >> 31;
2079 int add = ((-xsign) ^ (ysign & 3)) << 6;
2080
2081#ifdef SK_DEBUG
2082 if (0 == add)
2083 SkASSERT(x > 0 && y > 0);
2084 else if (64 == add)
2085 SkASSERT(x < 0 && y > 0);
2086 else if (128 == add)
2087 SkASSERT(x < 0 && y < 0);
2088 else if (192 == add)
2089 SkASSERT(x > 0 && y < 0);
2090 else
2091 SkASSERT(!"bad value for add");
2092#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002093
reed@android.com8a1c16f2008-12-17 15:59:43 +00002094 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2095 where we need to rotate x,y by 90 or -90
2096 */
2097 x = (x ^ xsign) - xsign;
2098 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002099 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002100 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002101 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102
2103 unsigned result = add + atan_0_90(y, x);
2104 SkASSERT(result < 256);
2105 return result;
2106}
2107
reed@google.com61eb0402011-04-15 12:11:12 +00002108void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002109 SkMatrix::MapXYProc proc = fDstToIndexProc;
2110 const SkMatrix& matrix = fDstToIndex;
2111 const SkPMColor* cache = this->getCache32();
2112 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002113
reed@google.com61eb0402011-04-15 12:11:12 +00002114 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2116 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2117 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2118 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002119
reed@google.com61eb0402011-04-15 12:11:12 +00002120 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121 SkFixed storage[2];
2122 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2123 &storage[0], &storage[1]);
2124 dx = storage[0];
2125 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002126 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002127 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2128 dx = SkScalarToFixed(matrix.getScaleX());
2129 dy = SkScalarToFixed(matrix.getSkewY());
2130 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002131
reed@google.com61eb0402011-04-15 12:11:12 +00002132 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 *dstC++ = cache[SkATan2_255(fy, fx)];
2134 fx += dx;
2135 fy += dy;
2136 }
reed@google.com61eb0402011-04-15 12:11:12 +00002137 } else { // perspective case
2138 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2140 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002141
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2143 SkScalarToFixed(srcPt.fX));
2144 *dstC++ = cache[index];
2145 }
2146 }
2147}
2148
reed@google.com61eb0402011-04-15 12:11:12 +00002149void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 SkMatrix::MapXYProc proc = fDstToIndexProc;
2151 const SkMatrix& matrix = fDstToIndex;
2152 const uint16_t* cache = this->getCache16();
2153 int toggle = ((x ^ y) & 1) << kCache16Bits;
2154 SkPoint srcPt;
2155
reed@google.com61eb0402011-04-15 12:11:12 +00002156 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2158 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2159 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2160 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002161
reed@google.com61eb0402011-04-15 12:11:12 +00002162 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 SkFixed storage[2];
2164 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2165 &storage[0], &storage[1]);
2166 dx = storage[0];
2167 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002168 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2170 dx = SkScalarToFixed(matrix.getScaleX());
2171 dy = SkScalarToFixed(matrix.getSkewY());
2172 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002173
reed@google.com61eb0402011-04-15 12:11:12 +00002174 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2176 *dstC++ = cache[toggle + index];
2177 toggle ^= (1 << kCache16Bits);
2178 fx += dx;
2179 fy += dy;
2180 }
reed@google.com61eb0402011-04-15 12:11:12 +00002181 } else { // perspective case
2182 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2184 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002185
reed@android.com8a1c16f2008-12-17 15:59:43 +00002186 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2187 SkScalarToFixed(srcPt.fX));
2188 index >>= (8 - kCache16Bits);
2189 *dstC++ = cache[toggle + index];
2190 toggle ^= (1 << kCache16Bits);
2191 }
2192 }
2193}
2194
reed@google.com61eb0402011-04-15 12:11:12 +00002195///////////////////////////////////////////////////////////////////////////////
2196///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197
2198// assumes colors is SkColor* and pos is SkScalar*
2199#define EXPAND_1_COLOR(count) \
2200 SkColor tmp[2]; \
2201 do { \
2202 if (1 == count) { \
2203 tmp[0] = tmp[1] = colors[0]; \
2204 colors = tmp; \
2205 pos = NULL; \
2206 count = 2; \
2207 } \
2208 } while (0)
2209
reed@google.com61eb0402011-04-15 12:11:12 +00002210SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2211 const SkColor colors[],
2212 const SkScalar pos[], int colorCount,
2213 SkShader::TileMode mode,
2214 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215 if (NULL == pts || NULL == colors || colorCount < 1) {
2216 return NULL;
2217 }
2218 EXPAND_1_COLOR(colorCount);
2219
reed@android.comab840b82009-07-01 17:00:03 +00002220 return SkNEW_ARGS(Linear_Gradient,
2221 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002222}
2223
reed@google.com61eb0402011-04-15 12:11:12 +00002224SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2225 const SkColor colors[],
2226 const SkScalar pos[], int colorCount,
2227 SkShader::TileMode mode,
2228 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002229 if (radius <= 0 || NULL == colors || colorCount < 1) {
2230 return NULL;
2231 }
2232 EXPAND_1_COLOR(colorCount);
2233
reed@android.comab840b82009-07-01 17:00:03 +00002234 return SkNEW_ARGS(Radial_Gradient,
2235 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002236}
2237
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002238SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2239 SkScalar startRadius,
2240 const SkPoint& end,
2241 SkScalar endRadius,
2242 const SkColor colors[],
2243 const SkScalar pos[],
2244 int colorCount,
2245 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002246 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002247 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2248 return NULL;
2249 }
2250 EXPAND_1_COLOR(colorCount);
2251
2252 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002253 (start, startRadius, end, endRadius, colors, pos,
2254 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002255}
2256
reed@android.com8a1c16f2008-12-17 15:59:43 +00002257SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2258 const SkColor colors[],
2259 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002260 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002261 if (NULL == colors || count < 1) {
2262 return NULL;
2263 }
2264 EXPAND_1_COLOR(count);
2265
2266 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2267}
2268
2269static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2270 Linear_Gradient::CreateProc);
2271
2272static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2273 Radial_Gradient::CreateProc);
2274
2275static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2276 Sweep_Gradient::CreateProc);
tomhudson@google.com938d6042011-04-27 13:57:03 +00002277
2278static SkFlattenable::Registrar
2279 gTwoPointRadialGradientReg("Two_Point_Radial_Gradient",
2280 Two_Point_Radial_Gradient::CreateProc);