blob: 6aff4fc406b168ad278708c57e1584ba7e3b9f2b [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 }
345 buffer.read(&fPtsToUnit, sizeof(SkMatrix));
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 }
373 buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
374}
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;
1479 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1480 if (discrim < 0) {
1481 discrim = -discrim;
1482 }
1483 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1484 if (posRoot) {
1485 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1486 } else {
1487 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1488 }
1489}
1490#else
reed@google.com84e9c082011-04-13 17:44:24 +00001491static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1492 SkScalar sr2d2, SkScalar foura,
1493 SkScalar oneOverTwoA, bool posRoot) {
1494 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
1495 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001496 if (discrim < 0) {
1497 discrim = -discrim;
1498 }
reed@google.com84e9c082011-04-13 17:44:24 +00001499 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1500 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001501 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001502 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001503 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001504 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001505 }
reed@google.com84e9c082011-04-13 17:44:24 +00001506 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001507}
reed@google.come61414c2011-04-15 18:14:16 +00001508#endif
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001509
1510class Two_Point_Radial_Gradient : public Gradient_Shader {
1511public:
1512 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1513 const SkPoint& end, SkScalar endRadius,
1514 const SkColor colors[], const SkScalar pos[],
1515 int colorCount, SkShader::TileMode mode,
1516 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001517 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1518 fCenter1(start),
1519 fCenter2(end),
1520 fRadius1(startRadius),
1521 fRadius2(endRadius) {
1522 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001523 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001524
1525 virtual BitmapType asABitmap(SkBitmap* bitmap,
1526 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001527 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001528 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001529 if (bitmap) {
1530 this->commonAsABitmap(bitmap);
1531 }
1532 SkScalar diffL = 0; // just to avoid gcc warning
1533 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001534 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001535 SkScalarSquare(fDiff.fY));
1536 }
1537 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001538 if (diffL) {
1539 SkScalar invDiffL = SkScalarInvert(diffL);
1540 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1541 SkScalarMul(invDiffL, fDiff.fX));
1542 } else {
1543 matrix->reset();
1544 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001545 matrix->preConcat(fPtsToUnit);
1546 }
1547 if (xy) {
1548 xy[0] = fTileMode;
1549 xy[1] = kClamp_TileMode;
1550 }
1551 if (NULL != twoPointRadialParams) {
1552 twoPointRadialParams[0] = diffL;
1553 twoPointRadialParams[1] = fStartRadius;
1554 twoPointRadialParams[2] = fDiffRadius;
1555 }
1556 return kTwoPointRadial_BitmapType;
1557 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001558
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001559 virtual GradientType asAGradient(GradientInfo* info) const {
1560 if (info) {
1561 commonAsAGradient(info);
1562 info->fPoint[0] = fCenter1;
1563 info->fPoint[1] = fCenter2;
1564 info->fRadius[0] = fRadius1;
1565 info->fRadius[1] = fRadius2;
1566 }
1567 return kRadial2_GradientType;
1568 }
1569
reed@google.come61414c2011-04-15 18:14:16 +00001570#ifdef SK_USE_SLOW_2POINT_RADIAL_GRADIENT
1571 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1572 {
1573 SkASSERT(count > 0);
1574
1575 // Zero difference between radii: fill with transparent black.
1576 if (fDiffRadius == 0) {
1577 sk_bzero(dstC, count * sizeof(*dstC));
1578 return;
1579 }
1580 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1581 TileProc proc = fTileProc;
1582 const SkPMColor* cache = this->getCache32();
1583 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1584 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1585 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1586 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1587 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1588 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1589 bool posRoot = fDiffRadius < 0;
1590 if (fDstToIndexClass != kPerspective_MatrixClass)
1591 {
1592 SkPoint srcPt;
1593 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1594 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1595 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1596 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1597
1598 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1599 {
1600 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1601 }
1602 else
1603 {
1604 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1605 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1606 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1607 }
1608 SkFixed b = (SkFixedMul(diffx, fx) +
1609 SkFixedMul(diffy, fy) - startRadius) << 1;
1610 SkFixed db = (SkFixedMul(diffx, dx) +
1611 SkFixedMul(diffy, dy)) << 1;
1612 if (proc == clamp_tileproc)
1613 {
1614 for (; count > 0; --count) {
1615 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1616 SkFixed index = SkClampMax(t, 0xFFFF);
1617 SkASSERT(index <= 0xFFFF);
1618 *dstC++ = cache[index >> (16 - kCache32Bits)];
1619 fx += dx;
1620 fy += dy;
1621 b += db;
1622 }
1623 }
1624 else if (proc == mirror_tileproc)
1625 {
1626 for (; count > 0; --count) {
1627 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1628 SkFixed index = mirror_tileproc(t);
1629 SkASSERT(index <= 0xFFFF);
1630 *dstC++ = cache[index >> (16 - kCache32Bits)];
1631 fx += dx;
1632 fy += dy;
1633 b += db;
1634 }
1635 }
1636 else
1637 {
1638 SkASSERT(proc == repeat_tileproc);
1639 for (; count > 0; --count) {
1640 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1641 SkFixed index = repeat_tileproc(t);
1642 SkASSERT(index <= 0xFFFF);
1643 *dstC++ = cache[index >> (16 - kCache32Bits)];
1644 fx += dx;
1645 fy += dy;
1646 b += db;
1647 }
1648 }
1649 }
1650 else // perspective case
1651 {
1652 SkScalar dstX = SkIntToScalar(x);
1653 SkScalar dstY = SkIntToScalar(y);
1654 for (; count > 0; --count) {
1655 SkPoint srcPt;
1656 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1657 SkFixed fx = SkScalarToFixed(srcPt.fX);
1658 SkFixed fy = SkScalarToFixed(srcPt.fY);
1659 SkFixed b = (SkFixedMul(diffx, fx) +
1660 SkFixedMul(diffy, fy) - startRadius) << 1;
1661 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1662 SkFixed index = proc(t);
1663 SkASSERT(index <= 0xFFFF);
1664 *dstC++ = cache[index >> (16 - kCache32Bits)];
1665 dstX += SK_Scalar1;
1666 }
1667 }
1668 }
1669#else
reed@google.com61eb0402011-04-15 12:11:12 +00001670 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001671 SkASSERT(count > 0);
1672
1673 // Zero difference between radii: fill with transparent black.
1674 if (fDiffRadius == 0) {
1675 sk_bzero(dstC, count * sizeof(*dstC));
1676 return;
1677 }
1678 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1679 TileProc proc = fTileProc;
1680 const SkPMColor* cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001681
1682 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001683 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001684 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001685 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001686 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1687 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001688 SkScalar dx, fx = srcPt.fX;
1689 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001690
reed@google.com61eb0402011-04-15 12:11:12 +00001691 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001692 SkFixed fixedX, fixedY;
1693 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1694 dx = SkFixedToScalar(fixedX);
1695 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001696 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001697 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001698 dx = fDstToIndex.getScaleX();
1699 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001700 }
reed@google.com84e9c082011-04-13 17:44:24 +00001701 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1702 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1703 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1704 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001705 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001706 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001707 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001708 SkFixed index = SkClampMax(t, 0xFFFF);
1709 SkASSERT(index <= 0xFFFF);
1710 *dstC++ = cache[index >> (16 - kCache32Bits)];
1711 fx += dx;
1712 fy += dy;
1713 b += db;
1714 }
reed@google.com61eb0402011-04-15 12:11:12 +00001715 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001716 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001717 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001718 SkFixed index = mirror_tileproc(t);
1719 SkASSERT(index <= 0xFFFF);
1720 *dstC++ = cache[index >> (16 - kCache32Bits)];
1721 fx += dx;
1722 fy += dy;
1723 b += db;
1724 }
reed@google.com61eb0402011-04-15 12:11:12 +00001725 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001726 SkASSERT(proc == repeat_tileproc);
1727 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001728 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001729 SkFixed index = repeat_tileproc(t);
1730 SkASSERT(index <= 0xFFFF);
1731 *dstC++ = cache[index >> (16 - kCache32Bits)];
1732 fx += dx;
1733 fy += dy;
1734 b += db;
1735 }
1736 }
reed@google.com61eb0402011-04-15 12:11:12 +00001737 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001738 SkScalar dstX = SkIntToScalar(x);
1739 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001740 for (; count > 0; --count) {
1741 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001742 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001743 SkScalar fx = srcPt.fX;
1744 SkScalar fy = srcPt.fY;
1745 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1746 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1747 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001748 SkFixed index = proc(t);
1749 SkASSERT(index <= 0xFFFF);
1750 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001751 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001752 }
1753 }
1754 }
reed@google.come61414c2011-04-15 18:14:16 +00001755#endif
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001756
reed@android.com6c59a172009-09-22 20:24:05 +00001757 virtual bool setContext(const SkBitmap& device,
1758 const SkPaint& paint,
1759 const SkMatrix& matrix) {
1760 if (!this->INHERITED::setContext(device, paint, matrix)) {
1761 return false;
1762 }
1763
1764 // we don't have a span16 proc
1765 fFlags &= ~kHasSpan16_Flag;
1766 return true;
1767 }
1768
reed@google.com55b8e8c2011-01-13 16:22:35 +00001769 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001770 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1771 }
1772
reed@android.combcfc7332009-11-10 16:19:39 +00001773 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1774 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001775 buffer.writeScalar(fCenter1.fX);
1776 buffer.writeScalar(fCenter1.fY);
1777 buffer.writeScalar(fCenter2.fX);
1778 buffer.writeScalar(fCenter2.fY);
1779 buffer.writeScalar(fRadius1);
1780 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001781 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001782
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001783protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001784 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001785 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001786 fCenter1(unflatten_point(buffer)),
1787 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001788 fRadius1(buffer.readScalar()),
1789 fRadius2(buffer.readScalar()) {
1790 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001791 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001792 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001793
1794private:
1795 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001796 const SkPoint fCenter1;
1797 const SkPoint fCenter2;
1798 const SkScalar fRadius1;
1799 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001800 SkPoint fDiff;
1801 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001802
1803 void init() {
1804 fDiff = fCenter1 - fCenter2;
1805 fDiffRadius = fRadius2 - fRadius1;
1806 SkScalar inv = SkScalarInvert(fDiffRadius);
1807 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1808 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1809 fStartRadius = SkScalarMul(fRadius1, inv);
1810 fSr2D2 = SkScalarSquare(fStartRadius);
1811 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1812 fOneOverTwoA = SkScalarInvert(fA * 2);
1813
1814 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1815 fPtsToUnit.postScale(inv, inv);
1816 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001817};
1818
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819///////////////////////////////////////////////////////////////////////////////
1820
1821class Sweep_Gradient : public Gradient_Shader {
1822public:
1823 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1824 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001825 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1826 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827 {
1828 fPtsToUnit.setTranslate(-cx, -cy);
1829 }
1830 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1831 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001832
1833 virtual BitmapType asABitmap(SkBitmap* bitmap,
1834 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001835 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001836 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001837 if (bitmap) {
1838 this->commonAsABitmap(bitmap);
1839 }
1840 if (matrix) {
1841 *matrix = fPtsToUnit;
1842 }
1843 if (xy) {
1844 xy[0] = fTileMode;
1845 xy[1] = kClamp_TileMode;
1846 }
1847 return kSweep_BitmapType;
1848 }
1849
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001850 virtual GradientType asAGradient(GradientInfo* info) const {
1851 if (info) {
1852 commonAsAGradient(info);
1853 info->fPoint[0] = fCenter;
1854 }
1855 return kSweep_GradientType;
1856 }
1857
reed@android.com8a1c16f2008-12-17 15:59:43 +00001858 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1859 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1860 }
1861
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001862 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1863 this->INHERITED::flatten(buffer);
1864 buffer.writeScalar(fCenter.fX);
1865 buffer.writeScalar(fCenter.fY);
1866 }
1867
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001869 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1870 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001871 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001872 }
1873
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 virtual Factory getFactory() { return CreateProc; }
1875
1876private:
1877 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001878 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879};
1880
1881#ifdef COMPUTE_SWEEP_TABLE
1882#define PI 3.14159265
1883static bool gSweepTableReady;
1884static uint8_t gSweepTable[65];
1885
1886/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1887 We scale the results to [0..32]
1888*/
reed@google.com61eb0402011-04-15 12:11:12 +00001889static const uint8_t* build_sweep_table() {
1890 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891 const int N = 65;
1892 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001893
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 for (int i = 0; i < N; i++)
1895 {
1896 double arg = i / DENOM;
1897 double v = atan(arg);
1898 int iv = (int)round(v * DENOM * 2 / PI);
1899// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1900 printf("%d, ", iv);
1901 gSweepTable[i] = iv;
1902 }
1903 gSweepTableReady = true;
1904 }
1905 return gSweepTable;
1906}
1907#else
1908static const uint8_t gSweepTable[] = {
1909 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1910 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1911 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1912 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1913 32
1914};
1915static const uint8_t* build_sweep_table() { return gSweepTable; }
1916#endif
1917
1918// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1919// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1920// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1921
1922//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001923static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001924 SkASSERT(numer <= denom);
1925 SkASSERT(numer > 0);
1926 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001927
reed@android.com8a1c16f2008-12-17 15:59:43 +00001928 int nbits = SkCLZ(numer);
1929 int dbits = SkCLZ(denom);
1930 int bits = 6 - nbits + dbits;
1931 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001932
reed@google.com61eb0402011-04-15 12:11:12 +00001933 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001934 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001935 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936
1937 denom <<= dbits - 1;
1938 numer <<= nbits - 1;
1939
1940 unsigned result = 0;
1941
1942 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001943 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001944 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001945 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001947 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001948
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001950 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 // make room for the rest of the answer bits
1952 result <<= bits;
1953 switch (bits) {
1954 case 6:
1955 if ((numer = (numer << 1) - denom) >= 0)
1956 result |= 32;
1957 else
1958 numer += denom;
1959 case 5:
1960 if ((numer = (numer << 1) - denom) >= 0)
1961 result |= 16;
1962 else
1963 numer += denom;
1964 case 4:
1965 if ((numer = (numer << 1) - denom) >= 0)
1966 result |= 8;
1967 else
1968 numer += denom;
1969 case 3:
1970 if ((numer = (numer << 1) - denom) >= 0)
1971 result |= 4;
1972 else
1973 numer += denom;
1974 case 2:
1975 if ((numer = (numer << 1) - denom) >= 0)
1976 result |= 2;
1977 else
1978 numer += denom;
1979 case 1:
1980 default: // not strictly need, but makes GCC make better ARM code
1981 if ((numer = (numer << 1) - denom) >= 0)
1982 result |= 1;
1983 else
1984 numer += denom;
1985 }
1986 }
1987 return result;
1988}
1989
1990// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001991static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992#ifdef SK_DEBUG
1993 {
1994 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001995 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 gOnce = true;
1997 SkASSERT(div_64(55, 55) == 64);
1998 SkASSERT(div_64(128, 256) == 32);
1999 SkASSERT(div_64(2326528, 4685824) == 31);
2000 SkASSERT(div_64(753664, 5210112) == 9);
2001 SkASSERT(div_64(229376, 4882432) == 3);
2002 SkASSERT(div_64(2, 64) == 2);
2003 SkASSERT(div_64(1, 64) == 1);
2004 // test that we handle underflow correctly
2005 SkASSERT(div_64(12345, 0x54321234) == 0);
2006 }
2007 }
2008#endif
2009
2010 SkASSERT(y > 0 && x > 0);
2011 const uint8_t* table = build_sweep_table();
2012
2013 unsigned result;
2014 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002015 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016 // first part of the atan(v) = PI/2 - atan(1/v) identity
2017 // since our div_64 and table want v <= 1, where v = y/x
2018 SkTSwap<SkFixed>(x, y);
2019 }
2020
2021 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002022
reed@android.com8a1c16f2008-12-17 15:59:43 +00002023#ifdef SK_DEBUG
2024 {
2025 unsigned result2 = SkDivBits(y, x, 6);
2026 SkASSERT(result2 == result ||
2027 (result == 1 && result2 == 0));
2028 }
2029#endif
2030
2031 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2032 result = table[result];
2033
reed@google.com61eb0402011-04-15 12:11:12 +00002034 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002035 // complete the atan(v) = PI/2 - atan(1/v) identity
2036 result = 64 - result;
2037 // pin to 63
2038 result -= result >> 6;
2039 }
2040
2041 SkASSERT(result <= 63);
2042 return result;
2043}
2044
2045// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com61eb0402011-04-15 12:11:12 +00002046static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2047 if (x == 0) {
2048 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002050 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 return y < 0 ? 192 : 64;
2052 }
reed@google.com61eb0402011-04-15 12:11:12 +00002053 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002055 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002056
reed@android.com8a1c16f2008-12-17 15:59:43 +00002057 /* Find the right quadrant for x,y
2058 Since atan_0_90 only handles the first quadrant, we rotate x,y
2059 appropriately before calling it, and then add the right amount
2060 to account for the real quadrant.
2061 quadrant 0 : add 0 | x > 0 && y > 0
2062 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2063 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2064 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002065
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066 map x<0 to (1 << 6)
2067 map y<0 to (3 << 6)
2068 add = map_x ^ map_y
2069 */
2070 int xsign = x >> 31;
2071 int ysign = y >> 31;
2072 int add = ((-xsign) ^ (ysign & 3)) << 6;
2073
2074#ifdef SK_DEBUG
2075 if (0 == add)
2076 SkASSERT(x > 0 && y > 0);
2077 else if (64 == add)
2078 SkASSERT(x < 0 && y > 0);
2079 else if (128 == add)
2080 SkASSERT(x < 0 && y < 0);
2081 else if (192 == add)
2082 SkASSERT(x > 0 && y < 0);
2083 else
2084 SkASSERT(!"bad value for add");
2085#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002086
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2088 where we need to rotate x,y by 90 or -90
2089 */
2090 x = (x ^ xsign) - xsign;
2091 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002092 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002094 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002095
2096 unsigned result = add + atan_0_90(y, x);
2097 SkASSERT(result < 256);
2098 return result;
2099}
2100
reed@google.com61eb0402011-04-15 12:11:12 +00002101void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102 SkMatrix::MapXYProc proc = fDstToIndexProc;
2103 const SkMatrix& matrix = fDstToIndex;
2104 const SkPMColor* cache = this->getCache32();
2105 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002106
reed@google.com61eb0402011-04-15 12:11:12 +00002107 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002108 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2109 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2110 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2111 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002112
reed@google.com61eb0402011-04-15 12:11:12 +00002113 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002114 SkFixed storage[2];
2115 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2116 &storage[0], &storage[1]);
2117 dx = storage[0];
2118 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002119 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2121 dx = SkScalarToFixed(matrix.getScaleX());
2122 dy = SkScalarToFixed(matrix.getSkewY());
2123 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002124
reed@google.com61eb0402011-04-15 12:11:12 +00002125 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 *dstC++ = cache[SkATan2_255(fy, fx)];
2127 fx += dx;
2128 fy += dy;
2129 }
reed@google.com61eb0402011-04-15 12:11:12 +00002130 } else { // perspective case
2131 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2133 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002134
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2136 SkScalarToFixed(srcPt.fX));
2137 *dstC++ = cache[index];
2138 }
2139 }
2140}
2141
reed@google.com61eb0402011-04-15 12:11:12 +00002142void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143 SkMatrix::MapXYProc proc = fDstToIndexProc;
2144 const SkMatrix& matrix = fDstToIndex;
2145 const uint16_t* cache = this->getCache16();
2146 int toggle = ((x ^ y) & 1) << kCache16Bits;
2147 SkPoint srcPt;
2148
reed@google.com61eb0402011-04-15 12:11:12 +00002149 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2151 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2152 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2153 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002154
reed@google.com61eb0402011-04-15 12:11:12 +00002155 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 SkFixed storage[2];
2157 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2158 &storage[0], &storage[1]);
2159 dx = storage[0];
2160 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002161 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002162 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2163 dx = SkScalarToFixed(matrix.getScaleX());
2164 dy = SkScalarToFixed(matrix.getSkewY());
2165 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002166
reed@google.com61eb0402011-04-15 12:11:12 +00002167 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2169 *dstC++ = cache[toggle + index];
2170 toggle ^= (1 << kCache16Bits);
2171 fx += dx;
2172 fy += dy;
2173 }
reed@google.com61eb0402011-04-15 12:11:12 +00002174 } else { // perspective case
2175 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002176 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2177 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002178
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2180 SkScalarToFixed(srcPt.fX));
2181 index >>= (8 - kCache16Bits);
2182 *dstC++ = cache[toggle + index];
2183 toggle ^= (1 << kCache16Bits);
2184 }
2185 }
2186}
2187
reed@google.com61eb0402011-04-15 12:11:12 +00002188///////////////////////////////////////////////////////////////////////////////
2189///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190
2191// assumes colors is SkColor* and pos is SkScalar*
2192#define EXPAND_1_COLOR(count) \
2193 SkColor tmp[2]; \
2194 do { \
2195 if (1 == count) { \
2196 tmp[0] = tmp[1] = colors[0]; \
2197 colors = tmp; \
2198 pos = NULL; \
2199 count = 2; \
2200 } \
2201 } while (0)
2202
reed@google.com61eb0402011-04-15 12:11:12 +00002203SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2204 const SkColor colors[],
2205 const SkScalar pos[], int colorCount,
2206 SkShader::TileMode mode,
2207 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002208 if (NULL == pts || NULL == colors || colorCount < 1) {
2209 return NULL;
2210 }
2211 EXPAND_1_COLOR(colorCount);
2212
reed@android.comab840b82009-07-01 17:00:03 +00002213 return SkNEW_ARGS(Linear_Gradient,
2214 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215}
2216
reed@google.com61eb0402011-04-15 12:11:12 +00002217SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2218 const SkColor colors[],
2219 const SkScalar pos[], int colorCount,
2220 SkShader::TileMode mode,
2221 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002222 if (radius <= 0 || NULL == colors || colorCount < 1) {
2223 return NULL;
2224 }
2225 EXPAND_1_COLOR(colorCount);
2226
reed@android.comab840b82009-07-01 17:00:03 +00002227 return SkNEW_ARGS(Radial_Gradient,
2228 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002229}
2230
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002231SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2232 SkScalar startRadius,
2233 const SkPoint& end,
2234 SkScalar endRadius,
2235 const SkColor colors[],
2236 const SkScalar pos[],
2237 int colorCount,
2238 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002239 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002240 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2241 return NULL;
2242 }
2243 EXPAND_1_COLOR(colorCount);
2244
2245 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002246 (start, startRadius, end, endRadius, colors, pos,
2247 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002248}
2249
reed@android.com8a1c16f2008-12-17 15:59:43 +00002250SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2251 const SkColor colors[],
2252 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002253 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002254 if (NULL == colors || count < 1) {
2255 return NULL;
2256 }
2257 EXPAND_1_COLOR(count);
2258
2259 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2260}
2261
2262static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2263 Linear_Gradient::CreateProc);
2264
2265static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2266 Radial_Gradient::CreateProc);
2267
2268static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2269 Sweep_Gradient::CreateProc);