blob: 5af04862f2e563604d9926efbedc36068bb2b2e7 [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///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000053
54typedef SkFixed (*TileProc)(SkFixed);
55
reed@android.com41bccf52009-04-03 13:33:51 +000056static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 return SkClampMax(x, 0xFFFF);
58}
59
reed@android.com41bccf52009-04-03 13:33:51 +000060static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 return x & 0xFFFF;
62}
63
reed@android.com41bccf52009-04-03 13:33:51 +000064static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 int s = x << 15 >> 31;
66 return (x ^ s) & 0xFFFF;
67}
68
69static const TileProc gTileProcs[] = {
70 clamp_tileproc,
71 repeat_tileproc,
72 mirror_tileproc
73};
74
reed@google.com61eb0402011-04-15 12:11:12 +000075///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000076
reed@android.com200645d2009-12-14 16:41:57 +000077static inline int repeat_bits(int x, const int bits) {
78 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000079}
80
reed@android.com200645d2009-12-14 16:41:57 +000081static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000082#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000083 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000085 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000086#else
reed@android.com200645d2009-12-14 16:41:57 +000087 int s = x << (31 - bits) >> 31;
88 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000089#endif
90}
91
reed@android.com41bccf52009-04-03 13:33:51 +000092static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000093 return x & 0xFF;
94}
95
reed@android.com41bccf52009-04-03 13:33:51 +000096static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000097#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000098 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +0000100 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 return x & 255;
102#else
103 int s = x << 23 >> 31;
104 return (x ^ s) & 0xFF;
105#endif
106}
107
reed@google.com61eb0402011-04-15 12:11:12 +0000108///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109
110class Gradient_Shader : public SkShader {
111public:
112 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000113 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 virtual ~Gradient_Shader();
115
116 // overrides
117 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
118 virtual uint32_t getFlags() { return fFlags; }
119
120protected:
121 Gradient_Shader(SkFlattenableReadBuffer& );
122 SkUnitMapper* fMapper;
123 SkMatrix fPtsToUnit; // set by subclass
124 SkMatrix fDstToIndex;
125 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 TileMode fTileMode;
127 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000128 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 uint8_t fDstToIndexClass;
130 uint8_t fFlags;
131
132 struct Rec {
133 SkFixed fPos; // 0...1
134 uint32_t fScale; // (1 << 24) / range
135 };
136 Rec* fRecs;
137
138 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000139 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000141 kCache16Mask = kCache16Count - 1,
142 kCache16Shift = 16 - kCache16Bits,
143
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144 kCache32Bits = 8, // pretty much should always be 8
145 kCache32Count = 1 << kCache32Bits
146 };
147 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000148 const uint16_t* getCache16() const;
149 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000150
reed@google.com7c2f27d2011-03-07 19:29:00 +0000151 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000152 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000153
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154private:
155 enum {
156 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
157
reed@android.com1c12abe2009-07-02 15:01:02 +0000158 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 };
160 SkColor fStorage[(kStorageSize + 3) >> 2];
161 SkColor* fOrigColors;
162
reed@google.com7c2f27d2011-03-07 19:29:00 +0000163 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
164 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165
reed@google.com7c2f27d2011-03-07 19:29:00 +0000166 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
167 mutable SkMallocPixelRef* fCache32PixelRef;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168 unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
169
reed@android.com512a8762009-12-14 15:25:36 +0000170 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000171 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
172 U8CPU alpha);
reed@android.com512a8762009-12-14 15:25:36 +0000173
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 typedef SkShader INHERITED;
175};
176
reed@android.com41bccf52009-04-03 13:33:51 +0000177static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178 SkASSERT(x >= 0 && x <= SK_Scalar1);
179
180#ifdef SK_SCALAR_IS_FLOAT
181 return (unsigned)(x * 0xFFFF);
182#else
183 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
184#endif
185}
186
reed@android.com41bccf52009-04-03 13:33:51 +0000187Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
188 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000189 SkASSERT(colorCount > 1);
190
191 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
192
193 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000194 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
197 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
198 fTileMode = mode;
199 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000200
reed@android.com41bccf52009-04-03 13:33:51 +0000201 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000202 fCache32 = NULL;
203 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204
reed@android.com41bccf52009-04-03 13:33:51 +0000205 /* Note: we let the caller skip the first and/or last position.
206 i.e. pos[0] = 0.3, pos[1] = 0.7
207 In these cases, we insert dummy entries to ensure that the final data
208 will be bracketed by [0, 1].
209 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
210
211 Thus colorCount (the caller's value, and fColorCount (our value) may
212 differ by up to 2. In the above example:
213 colorCount = 2
214 fColorCount = 4
215 */
216 fColorCount = colorCount;
217 // check if we need to add in dummy start and/or end position/colors
218 bool dummyFirst = false;
219 bool dummyLast = false;
220 if (pos) {
221 dummyFirst = pos[0] != 0;
222 dummyLast = pos[colorCount - 1] != SK_Scalar1;
223 fColorCount += dummyFirst + dummyLast;
224 }
225
226 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000227 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000228 fOrigColors = reinterpret_cast<SkColor*>(
229 sk_malloc_throw(size * fColorCount));
230 }
231 else {
232 fOrigColors = fStorage;
233 }
234
235 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236 {
reed@android.com41bccf52009-04-03 13:33:51 +0000237 SkColor* origColors = fOrigColors;
238 if (dummyFirst) {
239 *origColors++ = colors[0];
240 }
241 memcpy(origColors, colors, colorCount * sizeof(SkColor));
242 if (dummyLast) {
243 origColors += colorCount;
244 *origColors = colors[colorCount - 1];
245 }
246 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000247
reed@android.com1c12abe2009-07-02 15:01:02 +0000248 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000249 if (fColorCount > 2) {
250 Rec* recs = fRecs;
251 recs->fPos = 0;
252 // recs->fScale = 0; // unused;
253 recs += 1;
254 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255 /* We need to convert the user's array of relative positions into
256 fixed-point positions and scale factors. We need these results
257 to be strictly monotonic (no two values equal or out of order).
258 Hence this complex loop that just jams a zero for the scale
259 value if it sees a segment out of order, and it assures that
260 we start at 0 and end at 1.0
261 */
262 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000263 int startIndex = dummyFirst ? 0 : 1;
264 int count = colorCount + dummyLast;
265 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266 // force the last value to be 1.0
267 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000268 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000269 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000270 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000271 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000272 }
reed@android.com41bccf52009-04-03 13:33:51 +0000273 // pin curr withing range
274 if (curr < 0) {
275 curr = 0;
276 } else if (curr > SK_Fixed1) {
277 curr = SK_Fixed1;
278 }
279 recs->fPos = curr;
280 if (curr > prev) {
281 recs->fScale = (1 << 24) / (curr - prev);
282 } else {
283 recs->fScale = 0; // ignore this segment
284 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285 // get ready for the next value
286 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000287 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000288 }
reed@android.com41bccf52009-04-03 13:33:51 +0000289 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000290 SkFixed dp = SK_Fixed1 / (colorCount - 1);
291 SkFixed p = dp;
292 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000293 for (int i = 1; i < colorCount; i++) {
294 recs->fPos = p;
295 recs->fScale = scale;
296 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 p += dp;
298 }
299 }
300 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000301 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302}
303
304Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000305 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306 fCacheAlpha = 256;
307
308 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
309
310 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000311 fCache32 = NULL;
312 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313
reed@android.com41bccf52009-04-03 13:33:51 +0000314 int colorCount = fColorCount = buffer.readU32();
315 if (colorCount > kColorStorageCount) {
316 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
317 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
318 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000320 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322
323 fTileMode = (TileMode)buffer.readU8();
324 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000325 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 if (colorCount > 2) {
327 Rec* recs = fRecs;
328 recs[0].fPos = 0;
329 for (int i = 1; i < colorCount; i++) {
330 recs[i].fPos = buffer.readS32();
331 recs[i].fScale = buffer.readU32();
332 }
333 }
334 buffer.read(&fPtsToUnit, sizeof(SkMatrix));
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000335 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336}
337
reed@android.com41bccf52009-04-03 13:33:51 +0000338Gradient_Shader::~Gradient_Shader() {
339 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000341 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000342 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000343 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000345 }
reed@google.com82065d62011-02-07 15:30:46 +0000346 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347}
348
reed@android.com41bccf52009-04-03 13:33:51 +0000349void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350 this->INHERITED::flatten(buffer);
351 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000352 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000353 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
354 buffer.write8(fTileMode);
355 if (fColorCount > 2) {
356 Rec* recs = fRecs;
357 for (int i = 1; i < fColorCount; i++) {
358 buffer.write32(recs[i].fPos);
359 buffer.write32(recs[i].fScale);
360 }
361 }
362 buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
363}
364
365bool Gradient_Shader::setContext(const SkBitmap& device,
366 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000367 const SkMatrix& matrix) {
368 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000370 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371
372 const SkMatrix& inverse = this->getTotalInverse();
373
374 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
375 return false;
376 }
377
378 fDstToIndexProc = fDstToIndex.getMapXYProc();
379 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
380
381 // now convert our colors in to PMColors
382 unsigned paintAlpha = this->getPaintAlpha();
383 unsigned colorAlpha = 0xFF;
384
reed@android.com3d06a8c2009-07-07 18:19:59 +0000385 // FIXME: record colorAlpha in constructor, since this is not affected
386 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000387 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000388 SkColor src = fOrigColors[i];
389 unsigned sa = SkColorGetA(src);
390 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391 }
392
393 fFlags = this->INHERITED::getFlags();
394 if ((colorAlpha & paintAlpha) == 0xFF) {
395 fFlags |= kOpaqueAlpha_Flag;
396 }
397 // we can do span16 as long as our individual colors are opaque,
398 // regardless of the paint's alpha
399 if (0xFF == colorAlpha) {
400 fFlags |= kHasSpan16_Flag;
401 }
402
403 // if the new alpha differs from the previous time we were called, inval our cache
404 // this will trigger the cache to be rebuilt.
405 // we don't care about the first time, since the cache ptrs will already be NULL
406 if (fCacheAlpha != paintAlpha) {
407 fCache16 = NULL; // inval the cache
408 fCache32 = NULL; // inval the cache
409 fCacheAlpha = paintAlpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000410 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000411 if (fCache32PixelRef) {
412 fCache32PixelRef->notifyPixelsChanged();
413 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000414 }
415 return true;
416}
417
reed@android.com41bccf52009-04-03 13:33:51 +0000418static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419 SkASSERT(a == SkToU8(a));
420 SkASSERT(b == SkToU8(b));
421 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000422 return a + ((b - a) * scale >> 8);
423}
424
reed@android.com41bccf52009-04-03 13:33:51 +0000425static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
426 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000427#if 0
428 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
429 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
430 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
431 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
432
433 return SkPackARGB32(a, r, g, b);
434#else
435 int otherBlend = 256 - blend;
436
437#if 0
438 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
439 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
440 SkASSERT((t0 & t1) == 0);
441 return t0 | t1;
442#else
443 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
444 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
445#endif
446
447#endif
448}
449
450#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
451
reed@android.com41bccf52009-04-03 13:33:51 +0000452/** We take the original colors, not our premultiplied PMColors, since we can
453 build a 16bit table as long as the original colors are opaque, even if the
454 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000455*/
reed@android.com512a8762009-12-14 15:25:36 +0000456void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
457 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000458 SkASSERT(count > 1);
459 SkASSERT(SkColorGetA(c0) == 0xFF);
460 SkASSERT(SkColorGetA(c1) == 0xFF);
461
462 SkFixed r = SkColorGetR(c0);
463 SkFixed g = SkColorGetG(c0);
464 SkFixed b = SkColorGetB(c0);
465
466 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
467 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
468 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
469
470 r = SkIntToFixed(r) + 0x8000;
471 g = SkIntToFixed(g) + 0x8000;
472 b = SkIntToFixed(b) + 0x8000;
473
474 do {
475 unsigned rr = r >> 16;
476 unsigned gg = g >> 16;
477 unsigned bb = b >> 16;
478 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000479 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000480 cache += 1;
481 r += dr;
482 g += dg;
483 b += db;
484 } while (--count != 0);
485}
486
reed@google.com55b8e8c2011-01-13 16:22:35 +0000487/*
488 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
489 * semantics of how we 2x2 dither 32->16
490 */
491static inline U8CPU dither_fixed_to_8(SkFixed n) {
492 n >>= 8;
493 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
494}
495
496/*
497 * For dithering with premultiply, we want to ceiling the alpha component,
498 * to ensure that it is always >= any color component.
499 */
500static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
501 n >>= 8;
502 return ((n << 1) - (n | (n >> 8))) >> 8;
503}
504
505void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
506 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000507 SkASSERT(count > 1);
508
reed@android.com1c12abe2009-07-02 15:01:02 +0000509 // need to apply paintAlpha to our two endpoints
510 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
511 SkFixed da;
512 {
513 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
514 da = SkIntToFixed(tmp - a) / (count - 1);
515 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000516
reed@android.com1c12abe2009-07-02 15:01:02 +0000517 SkFixed r = SkColorGetR(c0);
518 SkFixed g = SkColorGetG(c0);
519 SkFixed b = SkColorGetB(c0);
520 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
521 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
522 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000523
524 a = SkIntToFixed(a) + 0x8000;
525 r = SkIntToFixed(r) + 0x8000;
526 g = SkIntToFixed(g) + 0x8000;
527 b = SkIntToFixed(b) + 0x8000;
528
529 do {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000530 cache[0] = SkPreMultiplyARGB(a >> 16, r >> 16, g >> 16, b >> 16);
531 cache[kCache32Count] = SkPreMultiplyARGB(dither_ceil_fixed_to_8(a),
532 dither_fixed_to_8(r),
533 dither_fixed_to_8(g),
534 dither_fixed_to_8(b));
535 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000536 a += da;
537 r += dr;
538 g += dg;
539 b += db;
540 } while (--count != 0);
541}
542
reed@android.com41bccf52009-04-03 13:33:51 +0000543static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000544 SkASSERT((unsigned)x <= SK_Fixed1);
545 return x - (x >> 16);
546}
547
reed@android.com200645d2009-12-14 16:41:57 +0000548static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000549 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000550 if (6 == bits) {
551 return (x << 10) | (x << 4) | (x >> 2);
552 }
553 if (8 == bits) {
554 return (x << 8) | x;
555 }
556 sk_throw();
557 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000558}
559
reed@google.com7c2f27d2011-03-07 19:29:00 +0000560const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000561 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000562 // double the count for dither entries
563 const int entryCount = kCache16Count * 2;
564 const size_t allocSize = sizeof(uint16_t) * entryCount;
565
reed@android.com3c9b2a42009-08-27 19:28:37 +0000566 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000567 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000568 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000569 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000570 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000571 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000572 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573 Rec* rec = fRecs;
574 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000575 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000576 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577 SkASSERT(nextIndex < kCache16Count);
578
579 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000580 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581 prevIndex = nextIndex;
582 }
583 SkASSERT(prevIndex == kCache16Count - 1);
584 }
585
reed@android.com41bccf52009-04-03 13:33:51 +0000586 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000587 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588 uint16_t* linear = fCache16; // just computed linear data
589 uint16_t* mapped = fCache16Storage; // storage for mapped data
590 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000591 for (int i = 0; i < kCache16Count; i++) {
592 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000593 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000594 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000595 }
596 sk_free(fCache16);
597 fCache16 = fCache16Storage;
598 }
599 }
600 return fCache16;
601}
602
reed@google.com7c2f27d2011-03-07 19:29:00 +0000603const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000604 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000605 // double the count for dither entries
606 const int entryCount = kCache32Count * 2;
607 const size_t allocSize = sizeof(SkPMColor) * entryCount;
608
reed@google.comdc731fd2010-12-23 15:19:47 +0000609 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000610 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
611 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000612 }
613 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000614 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000615 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
616 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000617 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000618 Rec* rec = fRecs;
619 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000620 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000621 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
622 SkASSERT(nextIndex < kCache32Count);
623
624 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000625 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
626 fOrigColors[i],
627 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 prevIndex = nextIndex;
629 }
630 SkASSERT(prevIndex == kCache32Count - 1);
631 }
632
reed@android.com41bccf52009-04-03 13:33:51 +0000633 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000634 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000635 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000636 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000637 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000639 for (int i = 0; i < kCache32Count; i++) {
640 int index = map->mapUnit16((i << 8) | i) >> 8;
641 mapped[i] = linear[index];
642 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000643 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000644 fCache32PixelRef->unref();
645 fCache32PixelRef = newPR;
646 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 }
648 }
649 return fCache32;
650}
651
reed@google.comdc731fd2010-12-23 15:19:47 +0000652/*
653 * Because our caller might rebuild the same (logically the same) gradient
654 * over and over, we'd like to return exactly the same "bitmap" if possible,
655 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
656 * To do that, we maintain a private cache of built-bitmaps, based on our
657 * colors and positions. Note: we don't try to flatten the fMapper, so if one
658 * is present, we skip the cache for now.
659 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000660void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.comdc731fd2010-12-23 15:19:47 +0000661 // don't have a way to put the mapper into our cache-key yet
662 if (fMapper) {
663 // force our cahce32pixelref to be built
664 (void)this->getCache32();
665 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
666 bitmap->setPixelRef(fCache32PixelRef);
667 return;
668 }
669
670 // build our key: [numColors + colors[] + {positions[]} ]
671 int count = 1 + fColorCount;
672 if (fColorCount > 2) {
673 count += fColorCount - 1; // fRecs[].fPos
674 }
675
676 SkAutoSTMalloc<16, int32_t> storage(count);
677 int32_t* buffer = storage.get();
678
679 *buffer++ = fColorCount;
680 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
681 buffer += fColorCount;
682 if (fColorCount > 2) {
683 for (int i = 1; i < fColorCount; i++) {
684 *buffer++ = fRecs[i].fPos;
685 }
686 }
687 SkASSERT(buffer - storage.get() == count);
688
689 ///////////////////////////////////
690
691 static SkMutex gMutex;
692 static SkBitmapCache* gCache;
693 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
694 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
695 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000696
reed@google.comdc731fd2010-12-23 15:19:47 +0000697 if (NULL == gCache) {
698 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
699 }
700 size_t size = count * sizeof(int32_t);
701
702 if (!gCache->find(storage.get(), size, bitmap)) {
703 // force our cahce32pixelref to be built
704 (void)this->getCache32();
705 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
706 bitmap->setPixelRef(fCache32PixelRef);
707
708 gCache->add(storage.get(), size, *bitmap);
709 }
710}
711
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000712void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
713 if (info) {
714 if (info->fColorCount >= fColorCount) {
715 if (info->fColors) {
716 memcpy(info->fColors, fOrigColors,
717 fColorCount * sizeof(SkColor));
718 }
719 if (info->fColorOffsets) {
720 if (fColorCount == 2) {
721 info->fColorOffsets[0] = 0;
722 info->fColorOffsets[1] = SK_Scalar1;
723 } else if (fColorCount > 2) {
724 for (int i = 0; i < fColorCount; i++)
725 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
726 }
727 }
728 }
729 info->fColorCount = fColorCount;
730 info->fTileMode = fTileMode;
731 }
732}
733
reed@google.com61eb0402011-04-15 12:11:12 +0000734///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000735
reed@android.com41bccf52009-04-03 13:33:51 +0000736static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737 SkVector vec = pts[1] - pts[0];
738 SkScalar mag = vec.length();
739 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
740
741 vec.scale(inv);
742 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
743 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
744 matrix->postScale(inv, inv);
745}
746
747///////////////////////////////////////////////////////////////////////////////
748
749class Linear_Gradient : public Gradient_Shader {
750public:
751 Linear_Gradient(const SkPoint pts[2],
752 const SkColor colors[], const SkScalar pos[], int colorCount,
753 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000754 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
755 fStart(pts[0]),
756 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000757 {
758 pts_to_unit_matrix(pts, &fPtsToUnit);
759 }
reed@android.com9b46e772009-06-05 12:24:41 +0000760
reed@android.com5119bdb2009-06-12 21:27:03 +0000761 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
763 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000764 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*,
reed@google.com7c2f27d2011-03-07 19:29:00 +0000765 TileMode*, SkScalar* twoPointRadialParams) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000766 virtual GradientType asAGradient(GradientInfo* info) const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767
reed@google.com55b8e8c2011-01-13 16:22:35 +0000768 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769 return SkNEW_ARGS(Linear_Gradient, (buffer));
770 }
771
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000772 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
773 this->INHERITED::flatten(buffer);
774 buffer.writeScalar(fStart.fX);
775 buffer.writeScalar(fStart.fY);
776 buffer.writeScalar(fEnd.fX);
777 buffer.writeScalar(fEnd.fY);
778 }
779
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000781 Linear_Gradient(SkFlattenableReadBuffer& buffer)
782 : Gradient_Shader(buffer),
783 fStart(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
784 fEnd(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
785 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786 virtual Factory getFactory() { return CreateProc; }
787
788private:
789 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000790 const SkPoint fStart;
791 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792};
793
reed@android.com5119bdb2009-06-12 21:27:03 +0000794bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
795 const SkMatrix& matrix) {
796 if (!this->INHERITED::setContext(device, paint, matrix)) {
797 return false;
798 }
799
800 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
801 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000802 fFlags |= SkShader::kConstInY32_Flag;
803 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
804 // only claim this if we do have a 16bit mode (i.e. none of our
805 // colors have alpha), and if we are not dithering (which obviously
806 // is not const in Y).
807 fFlags |= SkShader::kConstInY16_Flag;
808 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000809 }
810 return true;
811}
812
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@google.com61eb0402011-04-15 12:11:12 +0000814static inline bool no_need_for_clamp(int fx, int dx, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 SkASSERT(count > 0);
816 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
817}
818
reed@google.com5eb158d2011-04-15 15:50:34 +0000819#include "SkClampRange.h"
820
821#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000822 do { \
823 unsigned fi = fx >> 8; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000824 SkASSERT(fi <= 0xFF); \
825 fx += dx; \
826 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000827 toggle ^= TOGGLE_MASK; \
828 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000829
830
reed@google.com61eb0402011-04-15 12:11:12 +0000831void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832 SkASSERT(count > 0);
833
834 SkPoint srcPt;
835 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
836 TileProc proc = fTileProc;
837 const SkPMColor* cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000838#ifdef USE_DITHER_32BIT_GRADIENT
839 int toggle = ((x ^ y) & 1) << kCache32Bits;
840 const int TOGGLE_MASK = (1 << kCache32Bits);
841#else
842 int toggle = 0;
843 const int TOGGLE_MASK = 0;
844#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845
reed@android.comc552a432009-06-12 20:02:50 +0000846 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000847 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
848 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
850
reed@android.comc552a432009-06-12 20:02:50 +0000851 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852 SkFixed dxStorage[1];
853 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
854 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000855 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
857 dx = SkScalarToFixed(fDstToIndex.getScaleX());
858 }
859
reed@android.comc552a432009-06-12 20:02:50 +0000860 if (SkFixedNearlyZero(dx)) {
861 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862 unsigned fi = proc(fx);
863 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000864 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000866 } else if (proc == clamp_tileproc) {
reed@google.com17705072011-04-18 12:43:32 +0000867#ifdef SK_ENABLE_FAST_LINEAR_GRADIENTS
reed@google.com5eb158d2011-04-15 15:50:34 +0000868 SkClampRange range;
869 range.init(fx, dx, count, 0, 0xFF);
870
871 if ((count = range.fCount0) > 0) {
872 sk_memset32_dither(dstC,
873 cache[toggle + range.fV0],
874 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
875 count);
876 dstC += count;
877 }
878 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +0000879 int unroll = count >> 3;
880 fx = range.fFx1;
881 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000882 NO_CHECK_ITER; NO_CHECK_ITER;
883 NO_CHECK_ITER; NO_CHECK_ITER;
884 NO_CHECK_ITER; NO_CHECK_ITER;
885 NO_CHECK_ITER; NO_CHECK_ITER;
886 }
887 if ((count &= 7) > 0) {
888 do {
889 NO_CHECK_ITER;
890 } while (--count != 0);
891 }
892 }
893 if ((count = range.fCount2) > 0) {
894 sk_memset32_dither(dstC,
895 cache[toggle + range.fV1],
896 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
897 count);
898 }
reed@google.com17705072011-04-18 12:43:32 +0000899#else
900 do {
901 unsigned fi = SkClampMax(fx >> 8, 0xFF);
902 SkASSERT(fi <= 0xFF);
903 fx += dx;
904 *dstC++ = cache[toggle + fi];
905 toggle ^= TOGGLE_MASK;
906 } while (--count != 0);
reed@google.com5eb158d2011-04-15 15:50:34 +0000907#endif
reed@android.comc552a432009-06-12 20:02:50 +0000908 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000909 do {
910 unsigned fi = mirror_8bits(fx >> 8);
911 SkASSERT(fi <= 0xFF);
912 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000913 *dstC++ = cache[toggle + fi];
914 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000915 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000916 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000917 SkASSERT(proc == repeat_tileproc);
918 do {
919 unsigned fi = repeat_8bits(fx >> 8);
920 SkASSERT(fi <= 0xFF);
921 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000922 *dstC++ = cache[toggle + fi];
923 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000924 } while (--count != 0);
925 }
reed@android.comc552a432009-06-12 20:02:50 +0000926 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000927 SkScalar dstX = SkIntToScalar(x);
928 SkScalar dstY = SkIntToScalar(y);
929 do {
930 dstProc(fDstToIndex, dstX, dstY, &srcPt);
931 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
932 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000933 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
934 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000935 dstX += SK_Scalar1;
936 } while (--count != 0);
937 }
938}
939
reed@google.com55b8e8c2011-01-13 16:22:35 +0000940SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000941 SkMatrix* matrix,
942 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000943 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000944 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000945 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946 }
947 if (matrix) {
948 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
949 matrix->preConcat(fPtsToUnit);
950 }
951 if (xy) {
952 xy[0] = fTileMode;
953 xy[1] = kClamp_TileMode;
954 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000955 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000956}
957
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000958SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
959 if (info) {
960 commonAsAGradient(info);
961 info->fPoint[0] = fStart;
962 info->fPoint[1] = fEnd;
963 }
964 return kLinear_GradientType;
965}
966
reed@android.com3c9b2a42009-08-27 19:28:37 +0000967static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
968 int count) {
969 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000970 *dst++ = value;
971 count -= 1;
972 SkTSwap(value, other);
973 }
974
975 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000976
reed@android.com3c9b2a42009-08-27 19:28:37 +0000977 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000979 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000981
reed@google.com5eb158d2011-04-15 15:50:34 +0000982#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +0000983 do { \
984 unsigned fi = fx >> kCache16Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000985 SkASSERT(fi <= kCache16Mask); \
986 fx += dx; \
987 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000988 toggle ^= TOGGLE_MASK; \
989 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000990
991
reed@google.com61eb0402011-04-15 12:11:12 +0000992void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993 SkASSERT(count > 0);
994
995 SkPoint srcPt;
996 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
997 TileProc proc = fTileProc;
998 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000999 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@google.com5eb158d2011-04-15 15:50:34 +00001000 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001002 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001003 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1004 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1006
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001007 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008 SkFixed dxStorage[1];
1009 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1010 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001011 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1013 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1014 }
1015
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001016 if (SkFixedNearlyZero(dx)) {
1017 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +00001018 unsigned fi = proc(fx) >> kCache16Shift;
1019 SkASSERT(fi <= kCache16Mask);
reed@google.com5eb158d2011-04-15 15:50:34 +00001020 dither_memset16(dstC, cache[toggle + fi],
1021 cache[(toggle ^ TOGGLE_MASK) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001022 } else if (proc == clamp_tileproc) {
reed@google.com17705072011-04-18 12:43:32 +00001023#ifdef SK_ENABLE_FAST_LINEAR_GRADIENTS
reed@google.com5eb158d2011-04-15 15:50:34 +00001024 SkClampRange range;
1025 range.init(fx, dx, count, 0, kCache16Mask);
1026
1027 if ((count = range.fCount0) > 0) {
1028 dither_memset16(dstC,
1029 cache[toggle + range.fV0],
1030 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
1031 count);
1032 dstC += count;
1033 }
1034 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +00001035 int unroll = count >> 3;
1036 fx = range.fFx1;
1037 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001038 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1039 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1040 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1041 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1042 }
1043 if ((count &= 7) > 0) {
1044 do {
1045 NO_CHECK_ITER_16;
1046 } while (--count != 0);
1047 }
1048 }
1049 if ((count = range.fCount2) > 0) {
1050 dither_memset16(dstC,
1051 cache[toggle + range.fV1],
1052 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
1053 count);
1054 }
reed@google.com17705072011-04-18 12:43:32 +00001055#else
1056 do {
1057 unsigned fi = SkClampMax(fx >> kCache16Shift, kCache16Mask);
1058 SkASSERT(fi <= kCache16Mask);
1059 fx += dx;
1060 *dstC++ = cache[toggle + fi];
1061 toggle ^= TOGGLE_MASK;
1062 } while (--count != 0);
reed@google.com5eb158d2011-04-15 15:50:34 +00001063#endif
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001064 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 do {
reed@android.com200645d2009-12-14 16:41:57 +00001066 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001067 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001068 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001070 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001071 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001072 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001073 SkASSERT(proc == repeat_tileproc);
1074 do {
reed@android.com200645d2009-12-14 16:41:57 +00001075 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001076 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001077 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001078 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001079 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001080 } while (--count != 0);
1081 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001082 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001083 SkScalar dstX = SkIntToScalar(x);
1084 SkScalar dstY = SkIntToScalar(y);
1085 do {
1086 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1087 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1088 SkASSERT(fi <= 0xFFFF);
1089
reed@android.com512a8762009-12-14 15:25:36 +00001090 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001091 *dstC++ = cache[toggle + index];
reed@google.com5eb158d2011-04-15 15:50:34 +00001092 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001093
1094 dstX += SK_Scalar1;
1095 } while (--count != 0);
1096 }
1097}
1098
1099///////////////////////////////////////////////////////////////////////////////
1100
1101#define kSQRT_TABLE_BITS 11
1102#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1103
1104#include "SkRadialGradient_Table.h"
1105
1106#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1107
1108#include <stdio.h>
1109
reed@google.com61eb0402011-04-15 12:11:12 +00001110void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1112
1113 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1114 SkASSERT(file);
1115 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1116
reed@google.com61eb0402011-04-15 12:11:12 +00001117 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1118 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001120 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001121
1122 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1123
1124 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001125 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001126 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001127 }
1128 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001129 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001130 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001131 }
1132 ::fprintf(file, "};\n");
1133 ::fclose(file);
1134}
1135
1136#endif
1137
1138
reed@google.com61eb0402011-04-15 12:11:12 +00001139static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1140 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001141 SkScalar inv = SkScalarInvert(radius);
1142
1143 matrix->setTranslate(-center.fX, -center.fY);
1144 matrix->postScale(inv, inv);
1145}
1146
1147class Radial_Gradient : public Gradient_Shader {
1148public:
1149 Radial_Gradient(const SkPoint& center, SkScalar radius,
1150 const SkColor colors[], const SkScalar pos[], int colorCount,
1151 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001152 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1153 fCenter(center),
1154 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155 {
1156 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1157 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1158
1159 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1160 }
reed@google.com61eb0402011-04-15 12:11:12 +00001161
1162 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001163 SkASSERT(count > 0);
1164
1165 SkPoint srcPt;
1166 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1167 TileProc proc = fTileProc;
1168 const SkPMColor* cache = this->getCache32();
1169
reed@google.com61eb0402011-04-15 12:11:12 +00001170 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001171 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1172 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1174 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1175
reed@google.com61eb0402011-04-15 12:11:12 +00001176 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001177 SkFixed storage[2];
1178 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1179 dx = storage[0];
1180 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00001181 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001182 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1183 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1184 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1185 }
1186
reed@google.com61eb0402011-04-15 12:11:12 +00001187 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 const uint8_t* sqrt_table = gSqrt8Table;
1189 fx >>= 1;
1190 dx >>= 1;
1191 fy >>= 1;
1192 dy >>= 1;
1193 do {
1194 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1195 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1196 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1197 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1198 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1199 fx += dx;
1200 fy += dy;
1201 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001202 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001204 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1205 if (magnitudeSquared < 0) // Overflow.
1206 magnitudeSquared = SK_FixedMax;
1207 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001208 unsigned fi = mirror_tileproc(dist);
1209 SkASSERT(fi <= 0xFFFF);
1210 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1211 fx += dx;
1212 fy += dy;
1213 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001214 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001215 SkASSERT(proc == repeat_tileproc);
1216 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001217 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1218 if (magnitudeSquared < 0) // Overflow.
1219 magnitudeSquared = SK_FixedMax;
1220 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221 unsigned fi = repeat_tileproc(dist);
1222 SkASSERT(fi <= 0xFFFF);
1223 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1224 fx += dx;
1225 fy += dy;
1226 } while (--count != 0);
1227 }
reed@google.com61eb0402011-04-15 12:11:12 +00001228 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229 SkScalar dstX = SkIntToScalar(x);
1230 SkScalar dstY = SkIntToScalar(y);
1231 do {
1232 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1233 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1234 SkASSERT(fi <= 0xFFFF);
1235 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1236 dstX += SK_Scalar1;
1237 } while (--count != 0);
1238 }
1239 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001240
1241 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242 SkASSERT(count > 0);
1243
1244 SkPoint srcPt;
1245 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1246 TileProc proc = fTileProc;
1247 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249
reed@android.com3c9b2a42009-08-27 19:28:37 +00001250 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001251 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1252 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1254 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1255
reed@android.com3c9b2a42009-08-27 19:28:37 +00001256 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257 SkFixed storage[2];
1258 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1259 dx = storage[0];
1260 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001261 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1263 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1264 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1265 }
1266
reed@android.com3c9b2a42009-08-27 19:28:37 +00001267 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001268 const uint8_t* sqrt_table = gSqrt8Table;
1269
1270 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1271 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1272 precision, but that appears to be visually OK. If we decide this is OK for
1273 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1274 to avoid having to do these extra shifts each time.
1275 */
1276 fx >>= 1;
1277 dx >>= 1;
1278 fy >>= 1;
1279 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001280 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 +00001281 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1282 fy *= fy;
1283 do {
1284 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1285 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1286 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1287 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1289 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001291 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 do {
1293 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1294 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1295 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1296 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1297 fx += dx;
1298 fy += dy;
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);
1302 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001303 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 do {
1305 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1306 unsigned fi = mirror_tileproc(dist);
1307 SkASSERT(fi <= 0xFFFF);
1308 fx += dx;
1309 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1311 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001313 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314 SkASSERT(proc == repeat_tileproc);
1315 do {
1316 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1317 unsigned fi = repeat_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);
1324 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001325 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001326 SkScalar dstX = SkIntToScalar(x);
1327 SkScalar dstY = SkIntToScalar(y);
1328 do {
1329 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1330 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1331 SkASSERT(fi <= 0xFFFF);
1332
1333 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334 *dstC++ = cache[toggle + index];
1335 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336
1337 dstX += SK_Scalar1;
1338 } while (--count != 0);
1339 }
1340 }
1341
reed@google.com55b8e8c2011-01-13 16:22:35 +00001342 virtual BitmapType asABitmap(SkBitmap* bitmap,
1343 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001344 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001345 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001346 if (bitmap) {
1347 this->commonAsABitmap(bitmap);
1348 }
1349 if (matrix) {
1350 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1351 matrix->preConcat(fPtsToUnit);
1352 }
1353 if (xy) {
1354 xy[0] = fTileMode;
1355 xy[1] = kClamp_TileMode;
1356 }
1357 return kRadial_BitmapType;
1358 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001359 virtual GradientType asAGradient(GradientInfo* info) const {
1360 if (info) {
1361 commonAsAGradient(info);
1362 info->fPoint[0] = fCenter;
1363 info->fRadius[0] = fRadius;
1364 }
1365 return kRadial_GradientType;
1366 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001367
1368 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369 return SkNEW_ARGS(Radial_Gradient, (buffer));
1370 }
1371
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001372 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1373 this->INHERITED::flatten(buffer);
1374 buffer.writeScalar(fCenter.fX);
1375 buffer.writeScalar(fCenter.fY);
1376 buffer.writeScalar(fRadius);
1377 }
1378
reed@android.com8a1c16f2008-12-17 15:59:43 +00001379protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001380 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1381 : Gradient_Shader(buffer),
1382 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1383 fRadius(buffer.readScalar()) {
1384 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 virtual Factory getFactory() { return CreateProc; }
1386
1387private:
1388 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001389 const SkPoint fCenter;
1390 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001391};
1392
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001393/* Two-point radial gradients are specified by two circles, each with a center
1394 point and radius. The gradient can be considered to be a series of
1395 concentric circles, with the color interpolated from the start circle
1396 (at t=0) to the end circle (at t=1).
1397
1398 For each point (x, y) in the span, we want to find the
1399 interpolated circle that intersects that point. The center
1400 of the desired circle (Cx, Cy) falls at some distance t
1401 along the line segment between the start point (Sx, Sy) and
1402 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001403
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001404 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1405 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001406
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001407 The radius of the desired circle (r) is also a linear interpolation t
1408 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001409
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001410 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001411
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001412 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001413
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001414 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001415
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001416 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001417
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001418 (x - ((1 - t) * Sx + t * Ex))^2
1419 + (y - ((1 - t) * Sy + t * Ey))^2
1420 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001421
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001422 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001423
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001424 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1425 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1426 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001427
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001428 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1429
1430 [Dx^2 + Dy^2 - Dr^2)] * t^2
1431 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1432 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001433
1434 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001435 possible circles on which the point may fall. Solving for t yields
1436 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001437
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001438 If a<0, the start circle is entirely contained in the
1439 end circle, and one of the roots will be <0 or >1 (off the line
1440 segment). If a>0, the start circle falls at least partially
1441 outside the end circle (or vice versa), and the gradient
1442 defines a "tube" where a point may be on one circle (on the
1443 inside of the tube) or the other (outside of the tube). We choose
1444 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001445
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001446 In order to keep the math to within the limits of fixed point,
1447 we divide the entire quadratic by Dr^2, and replace
1448 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001449
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001450 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1451 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1452 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001453
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001454 (x' and y' are computed by appending the subtract and scale to the
1455 fDstToIndex matrix in the constructor).
1456
1457 Since the 'A' component of the quadratic is independent of x' and y', it
1458 is precomputed in the constructor. Since the 'B' component is linear in
1459 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001460 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001461 a perspective projection), it must be computed in the loop.
1462
1463*/
1464
reed@google.come61414c2011-04-15 18:14:16 +00001465#ifdef SK_USE_SLOW_2POINT_RADIAL_GRADIENT
1466static inline SkFixed two_point_radial(SkFixed b, SkFixed fx, SkFixed fy, SkFixed sr2d2, SkFixed foura, SkFixed oneOverTwoA, bool posRoot) {
1467 SkFixed c = SkFixedSquare(fx) + SkFixedSquare(fy) - sr2d2;
1468 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1469 if (discrim < 0) {
1470 discrim = -discrim;
1471 }
1472 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1473 if (posRoot) {
1474 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1475 } else {
1476 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1477 }
1478}
1479#else
reed@google.com84e9c082011-04-13 17:44:24 +00001480static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1481 SkScalar sr2d2, SkScalar foura,
1482 SkScalar oneOverTwoA, bool posRoot) {
1483 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
1484 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001485 if (discrim < 0) {
1486 discrim = -discrim;
1487 }
reed@google.com84e9c082011-04-13 17:44:24 +00001488 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1489 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001490 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001491 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001492 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001493 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001494 }
reed@google.com84e9c082011-04-13 17:44:24 +00001495 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001496}
reed@google.come61414c2011-04-15 18:14:16 +00001497#endif
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001498
1499class Two_Point_Radial_Gradient : public Gradient_Shader {
1500public:
1501 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1502 const SkPoint& end, SkScalar endRadius,
1503 const SkColor colors[], const SkScalar pos[],
1504 int colorCount, SkShader::TileMode mode,
1505 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001506 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1507 fCenter1(start),
1508 fCenter2(end),
1509 fRadius1(startRadius),
1510 fRadius2(endRadius) {
1511 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001512 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001513
1514 virtual BitmapType asABitmap(SkBitmap* bitmap,
1515 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001516 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001517 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001518 if (bitmap) {
1519 this->commonAsABitmap(bitmap);
1520 }
1521 SkScalar diffL = 0; // just to avoid gcc warning
1522 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001523 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001524 SkScalarSquare(fDiff.fY));
1525 }
1526 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001527 if (diffL) {
1528 SkScalar invDiffL = SkScalarInvert(diffL);
1529 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1530 SkScalarMul(invDiffL, fDiff.fX));
1531 } else {
1532 matrix->reset();
1533 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001534 matrix->preConcat(fPtsToUnit);
1535 }
1536 if (xy) {
1537 xy[0] = fTileMode;
1538 xy[1] = kClamp_TileMode;
1539 }
1540 if (NULL != twoPointRadialParams) {
1541 twoPointRadialParams[0] = diffL;
1542 twoPointRadialParams[1] = fStartRadius;
1543 twoPointRadialParams[2] = fDiffRadius;
1544 }
1545 return kTwoPointRadial_BitmapType;
1546 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001547
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001548 virtual GradientType asAGradient(GradientInfo* info) const {
1549 if (info) {
1550 commonAsAGradient(info);
1551 info->fPoint[0] = fCenter1;
1552 info->fPoint[1] = fCenter2;
1553 info->fRadius[0] = fRadius1;
1554 info->fRadius[1] = fRadius2;
1555 }
1556 return kRadial2_GradientType;
1557 }
1558
reed@google.come61414c2011-04-15 18:14:16 +00001559#ifdef SK_USE_SLOW_2POINT_RADIAL_GRADIENT
1560 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1561 {
1562 SkASSERT(count > 0);
1563
1564 // Zero difference between radii: fill with transparent black.
1565 if (fDiffRadius == 0) {
1566 sk_bzero(dstC, count * sizeof(*dstC));
1567 return;
1568 }
1569 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1570 TileProc proc = fTileProc;
1571 const SkPMColor* cache = this->getCache32();
1572 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1573 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1574 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1575 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1576 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1577 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1578 bool posRoot = fDiffRadius < 0;
1579 if (fDstToIndexClass != kPerspective_MatrixClass)
1580 {
1581 SkPoint srcPt;
1582 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1583 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1584 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1585 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1586
1587 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1588 {
1589 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1590 }
1591 else
1592 {
1593 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1594 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1595 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1596 }
1597 SkFixed b = (SkFixedMul(diffx, fx) +
1598 SkFixedMul(diffy, fy) - startRadius) << 1;
1599 SkFixed db = (SkFixedMul(diffx, dx) +
1600 SkFixedMul(diffy, dy)) << 1;
1601 if (proc == clamp_tileproc)
1602 {
1603 for (; count > 0; --count) {
1604 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1605 SkFixed index = SkClampMax(t, 0xFFFF);
1606 SkASSERT(index <= 0xFFFF);
1607 *dstC++ = cache[index >> (16 - kCache32Bits)];
1608 fx += dx;
1609 fy += dy;
1610 b += db;
1611 }
1612 }
1613 else if (proc == mirror_tileproc)
1614 {
1615 for (; count > 0; --count) {
1616 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1617 SkFixed index = mirror_tileproc(t);
1618 SkASSERT(index <= 0xFFFF);
1619 *dstC++ = cache[index >> (16 - kCache32Bits)];
1620 fx += dx;
1621 fy += dy;
1622 b += db;
1623 }
1624 }
1625 else
1626 {
1627 SkASSERT(proc == repeat_tileproc);
1628 for (; count > 0; --count) {
1629 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1630 SkFixed index = repeat_tileproc(t);
1631 SkASSERT(index <= 0xFFFF);
1632 *dstC++ = cache[index >> (16 - kCache32Bits)];
1633 fx += dx;
1634 fy += dy;
1635 b += db;
1636 }
1637 }
1638 }
1639 else // perspective case
1640 {
1641 SkScalar dstX = SkIntToScalar(x);
1642 SkScalar dstY = SkIntToScalar(y);
1643 for (; count > 0; --count) {
1644 SkPoint srcPt;
1645 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1646 SkFixed fx = SkScalarToFixed(srcPt.fX);
1647 SkFixed fy = SkScalarToFixed(srcPt.fY);
1648 SkFixed b = (SkFixedMul(diffx, fx) +
1649 SkFixedMul(diffy, fy) - startRadius) << 1;
1650 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1651 SkFixed index = proc(t);
1652 SkASSERT(index <= 0xFFFF);
1653 *dstC++ = cache[index >> (16 - kCache32Bits)];
1654 dstX += SK_Scalar1;
1655 }
1656 }
1657 }
1658#else
reed@google.com61eb0402011-04-15 12:11:12 +00001659 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001660 SkASSERT(count > 0);
1661
1662 // Zero difference between radii: fill with transparent black.
1663 if (fDiffRadius == 0) {
1664 sk_bzero(dstC, count * sizeof(*dstC));
1665 return;
1666 }
1667 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1668 TileProc proc = fTileProc;
1669 const SkPMColor* cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001670
1671 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001672 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001673 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001674 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001675 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1676 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001677 SkScalar dx, fx = srcPt.fX;
1678 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001679
reed@google.com61eb0402011-04-15 12:11:12 +00001680 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001681 SkFixed fixedX, fixedY;
1682 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1683 dx = SkFixedToScalar(fixedX);
1684 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001685 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001686 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001687 dx = fDstToIndex.getScaleX();
1688 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001689 }
reed@google.com84e9c082011-04-13 17:44:24 +00001690 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1691 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1692 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1693 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001694 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001695 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001696 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001697 SkFixed index = SkClampMax(t, 0xFFFF);
1698 SkASSERT(index <= 0xFFFF);
1699 *dstC++ = cache[index >> (16 - kCache32Bits)];
1700 fx += dx;
1701 fy += dy;
1702 b += db;
1703 }
reed@google.com61eb0402011-04-15 12:11:12 +00001704 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001705 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001706 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001707 SkFixed index = mirror_tileproc(t);
1708 SkASSERT(index <= 0xFFFF);
1709 *dstC++ = cache[index >> (16 - kCache32Bits)];
1710 fx += dx;
1711 fy += dy;
1712 b += db;
1713 }
reed@google.com61eb0402011-04-15 12:11:12 +00001714 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001715 SkASSERT(proc == repeat_tileproc);
1716 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 = repeat_tileproc(t);
1719 SkASSERT(index <= 0xFFFF);
1720 *dstC++ = cache[index >> (16 - kCache32Bits)];
1721 fx += dx;
1722 fy += dy;
1723 b += db;
1724 }
1725 }
reed@google.com61eb0402011-04-15 12:11:12 +00001726 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001727 SkScalar dstX = SkIntToScalar(x);
1728 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001729 for (; count > 0; --count) {
1730 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001731 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001732 SkScalar fx = srcPt.fX;
1733 SkScalar fy = srcPt.fY;
1734 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1735 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1736 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001737 SkFixed index = proc(t);
1738 SkASSERT(index <= 0xFFFF);
1739 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001740 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001741 }
1742 }
1743 }
reed@google.come61414c2011-04-15 18:14:16 +00001744#endif
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001745
reed@android.com6c59a172009-09-22 20:24:05 +00001746 virtual bool setContext(const SkBitmap& device,
1747 const SkPaint& paint,
1748 const SkMatrix& matrix) {
1749 if (!this->INHERITED::setContext(device, paint, matrix)) {
1750 return false;
1751 }
1752
1753 // we don't have a span16 proc
1754 fFlags &= ~kHasSpan16_Flag;
1755 return true;
1756 }
1757
reed@google.com55b8e8c2011-01-13 16:22:35 +00001758 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001759 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1760 }
1761
reed@android.combcfc7332009-11-10 16:19:39 +00001762 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1763 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001764 buffer.writeScalar(fCenter1.fX);
1765 buffer.writeScalar(fCenter1.fY);
1766 buffer.writeScalar(fCenter2.fX);
1767 buffer.writeScalar(fCenter2.fY);
1768 buffer.writeScalar(fRadius1);
1769 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001770 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001771
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001772protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001773 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001774 : Gradient_Shader(buffer),
1775 fCenter1(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1776 fCenter2(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1777 fRadius1(buffer.readScalar()),
1778 fRadius2(buffer.readScalar()) {
1779 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001780 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001781 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001782
1783private:
1784 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001785 const SkPoint fCenter1;
1786 const SkPoint fCenter2;
1787 const SkScalar fRadius1;
1788 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001789 SkPoint fDiff;
1790 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001791
1792 void init() {
1793 fDiff = fCenter1 - fCenter2;
1794 fDiffRadius = fRadius2 - fRadius1;
1795 SkScalar inv = SkScalarInvert(fDiffRadius);
1796 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1797 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1798 fStartRadius = SkScalarMul(fRadius1, inv);
1799 fSr2D2 = SkScalarSquare(fStartRadius);
1800 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1801 fOneOverTwoA = SkScalarInvert(fA * 2);
1802
1803 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1804 fPtsToUnit.postScale(inv, inv);
1805 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001806};
1807
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808///////////////////////////////////////////////////////////////////////////////
1809
1810class Sweep_Gradient : public Gradient_Shader {
1811public:
1812 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1813 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001814 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1815 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 {
1817 fPtsToUnit.setTranslate(-cx, -cy);
1818 }
1819 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1820 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001821
1822 virtual BitmapType asABitmap(SkBitmap* bitmap,
1823 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001824 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001825 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001826 if (bitmap) {
1827 this->commonAsABitmap(bitmap);
1828 }
1829 if (matrix) {
1830 *matrix = fPtsToUnit;
1831 }
1832 if (xy) {
1833 xy[0] = fTileMode;
1834 xy[1] = kClamp_TileMode;
1835 }
1836 return kSweep_BitmapType;
1837 }
1838
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001839 virtual GradientType asAGradient(GradientInfo* info) const {
1840 if (info) {
1841 commonAsAGradient(info);
1842 info->fPoint[0] = fCenter;
1843 }
1844 return kSweep_GradientType;
1845 }
1846
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1848 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1849 }
1850
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001851 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1852 this->INHERITED::flatten(buffer);
1853 buffer.writeScalar(fCenter.fX);
1854 buffer.writeScalar(fCenter.fY);
1855 }
1856
reed@android.com8a1c16f2008-12-17 15:59:43 +00001857protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001858 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1859 : Gradient_Shader(buffer),
1860 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
1861 }
1862
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863 virtual Factory getFactory() { return CreateProc; }
1864
1865private:
1866 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001867 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868};
1869
1870#ifdef COMPUTE_SWEEP_TABLE
1871#define PI 3.14159265
1872static bool gSweepTableReady;
1873static uint8_t gSweepTable[65];
1874
1875/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1876 We scale the results to [0..32]
1877*/
reed@google.com61eb0402011-04-15 12:11:12 +00001878static const uint8_t* build_sweep_table() {
1879 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001880 const int N = 65;
1881 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001882
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883 for (int i = 0; i < N; i++)
1884 {
1885 double arg = i / DENOM;
1886 double v = atan(arg);
1887 int iv = (int)round(v * DENOM * 2 / PI);
1888// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1889 printf("%d, ", iv);
1890 gSweepTable[i] = iv;
1891 }
1892 gSweepTableReady = true;
1893 }
1894 return gSweepTable;
1895}
1896#else
1897static const uint8_t gSweepTable[] = {
1898 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1899 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1900 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1901 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1902 32
1903};
1904static const uint8_t* build_sweep_table() { return gSweepTable; }
1905#endif
1906
1907// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1908// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1909// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1910
1911//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001912static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 SkASSERT(numer <= denom);
1914 SkASSERT(numer > 0);
1915 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001916
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 int nbits = SkCLZ(numer);
1918 int dbits = SkCLZ(denom);
1919 int bits = 6 - nbits + dbits;
1920 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001921
reed@google.com61eb0402011-04-15 12:11:12 +00001922 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001923 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001924 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925
1926 denom <<= dbits - 1;
1927 numer <<= nbits - 1;
1928
1929 unsigned result = 0;
1930
1931 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001932 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001934 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001936 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001937
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001939 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001940 // make room for the rest of the answer bits
1941 result <<= bits;
1942 switch (bits) {
1943 case 6:
1944 if ((numer = (numer << 1) - denom) >= 0)
1945 result |= 32;
1946 else
1947 numer += denom;
1948 case 5:
1949 if ((numer = (numer << 1) - denom) >= 0)
1950 result |= 16;
1951 else
1952 numer += denom;
1953 case 4:
1954 if ((numer = (numer << 1) - denom) >= 0)
1955 result |= 8;
1956 else
1957 numer += denom;
1958 case 3:
1959 if ((numer = (numer << 1) - denom) >= 0)
1960 result |= 4;
1961 else
1962 numer += denom;
1963 case 2:
1964 if ((numer = (numer << 1) - denom) >= 0)
1965 result |= 2;
1966 else
1967 numer += denom;
1968 case 1:
1969 default: // not strictly need, but makes GCC make better ARM code
1970 if ((numer = (numer << 1) - denom) >= 0)
1971 result |= 1;
1972 else
1973 numer += denom;
1974 }
1975 }
1976 return result;
1977}
1978
1979// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001980static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981#ifdef SK_DEBUG
1982 {
1983 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001984 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 gOnce = true;
1986 SkASSERT(div_64(55, 55) == 64);
1987 SkASSERT(div_64(128, 256) == 32);
1988 SkASSERT(div_64(2326528, 4685824) == 31);
1989 SkASSERT(div_64(753664, 5210112) == 9);
1990 SkASSERT(div_64(229376, 4882432) == 3);
1991 SkASSERT(div_64(2, 64) == 2);
1992 SkASSERT(div_64(1, 64) == 1);
1993 // test that we handle underflow correctly
1994 SkASSERT(div_64(12345, 0x54321234) == 0);
1995 }
1996 }
1997#endif
1998
1999 SkASSERT(y > 0 && x > 0);
2000 const uint8_t* table = build_sweep_table();
2001
2002 unsigned result;
2003 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002004 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 // first part of the atan(v) = PI/2 - atan(1/v) identity
2006 // since our div_64 and table want v <= 1, where v = y/x
2007 SkTSwap<SkFixed>(x, y);
2008 }
2009
2010 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002011
reed@android.com8a1c16f2008-12-17 15:59:43 +00002012#ifdef SK_DEBUG
2013 {
2014 unsigned result2 = SkDivBits(y, x, 6);
2015 SkASSERT(result2 == result ||
2016 (result == 1 && result2 == 0));
2017 }
2018#endif
2019
2020 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2021 result = table[result];
2022
reed@google.com61eb0402011-04-15 12:11:12 +00002023 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 // complete the atan(v) = PI/2 - atan(1/v) identity
2025 result = 64 - result;
2026 // pin to 63
2027 result -= result >> 6;
2028 }
2029
2030 SkASSERT(result <= 63);
2031 return result;
2032}
2033
2034// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com61eb0402011-04-15 12:11:12 +00002035static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2036 if (x == 0) {
2037 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002038 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002039 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002040 return y < 0 ? 192 : 64;
2041 }
reed@google.com61eb0402011-04-15 12:11:12 +00002042 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002043 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002044 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002045
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046 /* Find the right quadrant for x,y
2047 Since atan_0_90 only handles the first quadrant, we rotate x,y
2048 appropriately before calling it, and then add the right amount
2049 to account for the real quadrant.
2050 quadrant 0 : add 0 | x > 0 && y > 0
2051 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2052 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2053 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002054
reed@android.com8a1c16f2008-12-17 15:59:43 +00002055 map x<0 to (1 << 6)
2056 map y<0 to (3 << 6)
2057 add = map_x ^ map_y
2058 */
2059 int xsign = x >> 31;
2060 int ysign = y >> 31;
2061 int add = ((-xsign) ^ (ysign & 3)) << 6;
2062
2063#ifdef SK_DEBUG
2064 if (0 == add)
2065 SkASSERT(x > 0 && y > 0);
2066 else if (64 == add)
2067 SkASSERT(x < 0 && y > 0);
2068 else if (128 == add)
2069 SkASSERT(x < 0 && y < 0);
2070 else if (192 == add)
2071 SkASSERT(x > 0 && y < 0);
2072 else
2073 SkASSERT(!"bad value for add");
2074#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002075
reed@android.com8a1c16f2008-12-17 15:59:43 +00002076 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2077 where we need to rotate x,y by 90 or -90
2078 */
2079 x = (x ^ xsign) - xsign;
2080 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002081 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002082 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002083 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002084
2085 unsigned result = add + atan_0_90(y, x);
2086 SkASSERT(result < 256);
2087 return result;
2088}
2089
reed@google.com61eb0402011-04-15 12:11:12 +00002090void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002091 SkMatrix::MapXYProc proc = fDstToIndexProc;
2092 const SkMatrix& matrix = fDstToIndex;
2093 const SkPMColor* cache = this->getCache32();
2094 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002095
reed@google.com61eb0402011-04-15 12:11:12 +00002096 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002097 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2098 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2099 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2100 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002101
reed@google.com61eb0402011-04-15 12:11:12 +00002102 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103 SkFixed storage[2];
2104 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2105 &storage[0], &storage[1]);
2106 dx = storage[0];
2107 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002108 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002109 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2110 dx = SkScalarToFixed(matrix.getScaleX());
2111 dy = SkScalarToFixed(matrix.getSkewY());
2112 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002113
reed@google.com61eb0402011-04-15 12:11:12 +00002114 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115 *dstC++ = cache[SkATan2_255(fy, fx)];
2116 fx += dx;
2117 fy += dy;
2118 }
reed@google.com61eb0402011-04-15 12:11:12 +00002119 } else { // perspective case
2120 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2122 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002123
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2125 SkScalarToFixed(srcPt.fX));
2126 *dstC++ = cache[index];
2127 }
2128 }
2129}
2130
reed@google.com61eb0402011-04-15 12:11:12 +00002131void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 SkMatrix::MapXYProc proc = fDstToIndexProc;
2133 const SkMatrix& matrix = fDstToIndex;
2134 const uint16_t* cache = this->getCache16();
2135 int toggle = ((x ^ y) & 1) << kCache16Bits;
2136 SkPoint srcPt;
2137
reed@google.com61eb0402011-04-15 12:11:12 +00002138 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2140 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2141 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2142 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002143
reed@google.com61eb0402011-04-15 12:11:12 +00002144 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 SkFixed storage[2];
2146 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2147 &storage[0], &storage[1]);
2148 dx = storage[0];
2149 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002150 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2152 dx = SkScalarToFixed(matrix.getScaleX());
2153 dy = SkScalarToFixed(matrix.getSkewY());
2154 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002155
reed@google.com61eb0402011-04-15 12:11:12 +00002156 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2158 *dstC++ = cache[toggle + index];
2159 toggle ^= (1 << kCache16Bits);
2160 fx += dx;
2161 fy += dy;
2162 }
reed@google.com61eb0402011-04-15 12:11:12 +00002163 } else { // perspective case
2164 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2166 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002167
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2169 SkScalarToFixed(srcPt.fX));
2170 index >>= (8 - kCache16Bits);
2171 *dstC++ = cache[toggle + index];
2172 toggle ^= (1 << kCache16Bits);
2173 }
2174 }
2175}
2176
reed@google.com61eb0402011-04-15 12:11:12 +00002177///////////////////////////////////////////////////////////////////////////////
2178///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179
2180// assumes colors is SkColor* and pos is SkScalar*
2181#define EXPAND_1_COLOR(count) \
2182 SkColor tmp[2]; \
2183 do { \
2184 if (1 == count) { \
2185 tmp[0] = tmp[1] = colors[0]; \
2186 colors = tmp; \
2187 pos = NULL; \
2188 count = 2; \
2189 } \
2190 } while (0)
2191
reed@google.com61eb0402011-04-15 12:11:12 +00002192SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2193 const SkColor colors[],
2194 const SkScalar pos[], int colorCount,
2195 SkShader::TileMode mode,
2196 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197 if (NULL == pts || NULL == colors || colorCount < 1) {
2198 return NULL;
2199 }
2200 EXPAND_1_COLOR(colorCount);
2201
reed@android.comab840b82009-07-01 17:00:03 +00002202 return SkNEW_ARGS(Linear_Gradient,
2203 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002204}
2205
reed@google.com61eb0402011-04-15 12:11:12 +00002206SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2207 const SkColor colors[],
2208 const SkScalar pos[], int colorCount,
2209 SkShader::TileMode mode,
2210 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002211 if (radius <= 0 || NULL == colors || colorCount < 1) {
2212 return NULL;
2213 }
2214 EXPAND_1_COLOR(colorCount);
2215
reed@android.comab840b82009-07-01 17:00:03 +00002216 return SkNEW_ARGS(Radial_Gradient,
2217 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002218}
2219
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002220SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2221 SkScalar startRadius,
2222 const SkPoint& end,
2223 SkScalar endRadius,
2224 const SkColor colors[],
2225 const SkScalar pos[],
2226 int colorCount,
2227 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002228 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002229 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2230 return NULL;
2231 }
2232 EXPAND_1_COLOR(colorCount);
2233
2234 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002235 (start, startRadius, end, endRadius, colors, pos,
2236 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002237}
2238
reed@android.com8a1c16f2008-12-17 15:59:43 +00002239SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2240 const SkColor colors[],
2241 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002242 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002243 if (NULL == colors || count < 1) {
2244 return NULL;
2245 }
2246 EXPAND_1_COLOR(count);
2247
2248 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2249}
2250
2251static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2252 Linear_Gradient::CreateProc);
2253
2254static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2255 Radial_Gradient::CreateProc);
2256
2257static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2258 Sweep_Gradient::CreateProc);
2259