blob: b0e85f93e4a256daffec9c2224ea5e6d54475a8d [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.com17705072011-04-18 12:43:32 +000030#define SK_ENABLE_FAST_LINEAR_GRADIENTS
31
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 \
822 fi = fx >> 8; \
823 SkASSERT(fi <= 0xFF); \
824 fx += dx; \
825 *dstC++ = cache[toggle + fi]; \
826 toggle ^= TOGGLE_MASK
827
828
reed@google.com61eb0402011-04-15 12:11:12 +0000829void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830 SkASSERT(count > 0);
831
832 SkPoint srcPt;
833 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
834 TileProc proc = fTileProc;
835 const SkPMColor* cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000836#ifdef USE_DITHER_32BIT_GRADIENT
837 int toggle = ((x ^ y) & 1) << kCache32Bits;
838 const int TOGGLE_MASK = (1 << kCache32Bits);
839#else
840 int toggle = 0;
841 const int TOGGLE_MASK = 0;
842#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843
reed@android.comc552a432009-06-12 20:02:50 +0000844 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000845 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
846 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000847 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
848
reed@android.comc552a432009-06-12 20:02:50 +0000849 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 SkFixed dxStorage[1];
851 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
852 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000853 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
855 dx = SkScalarToFixed(fDstToIndex.getScaleX());
856 }
857
reed@android.comc552a432009-06-12 20:02:50 +0000858 if (SkFixedNearlyZero(dx)) {
859 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 unsigned fi = proc(fx);
861 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000862 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000864 } else if (proc == clamp_tileproc) {
reed@google.com17705072011-04-18 12:43:32 +0000865#ifdef SK_ENABLE_FAST_LINEAR_GRADIENTS
reed@google.com5eb158d2011-04-15 15:50:34 +0000866 SkClampRange range;
867 range.init(fx, dx, count, 0, 0xFF);
868
869 if ((count = range.fCount0) > 0) {
870 sk_memset32_dither(dstC,
871 cache[toggle + range.fV0],
872 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
873 count);
874 dstC += count;
875 }
876 if ((count = range.fCount1) > 0) {
877 unsigned fi;
878 int i, unroll = count >> 3;
879 for (i = 0; i < unroll; i++) {
880 NO_CHECK_ITER; NO_CHECK_ITER;
881 NO_CHECK_ITER; NO_CHECK_ITER;
882 NO_CHECK_ITER; NO_CHECK_ITER;
883 NO_CHECK_ITER; NO_CHECK_ITER;
884 }
885 if ((count &= 7) > 0) {
886 do {
887 NO_CHECK_ITER;
888 } while (--count != 0);
889 }
890 }
891 if ((count = range.fCount2) > 0) {
892 sk_memset32_dither(dstC,
893 cache[toggle + range.fV1],
894 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
895 count);
896 }
reed@google.com17705072011-04-18 12:43:32 +0000897#else
898 do {
899 unsigned fi = SkClampMax(fx >> 8, 0xFF);
900 SkASSERT(fi <= 0xFF);
901 fx += dx;
902 *dstC++ = cache[toggle + fi];
903 toggle ^= TOGGLE_MASK;
904 } while (--count != 0);
reed@google.com5eb158d2011-04-15 15:50:34 +0000905#endif
reed@android.comc552a432009-06-12 20:02:50 +0000906 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000907 do {
908 unsigned fi = mirror_8bits(fx >> 8);
909 SkASSERT(fi <= 0xFF);
910 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000911 *dstC++ = cache[toggle + fi];
912 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000913 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000914 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000915 SkASSERT(proc == repeat_tileproc);
916 do {
917 unsigned fi = repeat_8bits(fx >> 8);
918 SkASSERT(fi <= 0xFF);
919 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000920 *dstC++ = cache[toggle + fi];
921 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000922 } while (--count != 0);
923 }
reed@android.comc552a432009-06-12 20:02:50 +0000924 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000925 SkScalar dstX = SkIntToScalar(x);
926 SkScalar dstY = SkIntToScalar(y);
927 do {
928 dstProc(fDstToIndex, dstX, dstY, &srcPt);
929 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
930 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000931 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
932 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000933 dstX += SK_Scalar1;
934 } while (--count != 0);
935 }
936}
937
reed@google.com55b8e8c2011-01-13 16:22:35 +0000938SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000939 SkMatrix* matrix,
940 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000941 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000942 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000943 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000944 }
945 if (matrix) {
946 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
947 matrix->preConcat(fPtsToUnit);
948 }
949 if (xy) {
950 xy[0] = fTileMode;
951 xy[1] = kClamp_TileMode;
952 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000953 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954}
955
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000956SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
957 if (info) {
958 commonAsAGradient(info);
959 info->fPoint[0] = fStart;
960 info->fPoint[1] = fEnd;
961 }
962 return kLinear_GradientType;
963}
964
reed@android.com3c9b2a42009-08-27 19:28:37 +0000965static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
966 int count) {
967 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000968 *dst++ = value;
969 count -= 1;
970 SkTSwap(value, other);
971 }
972
973 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000974
reed@android.com3c9b2a42009-08-27 19:28:37 +0000975 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000977 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979
reed@google.com5eb158d2011-04-15 15:50:34 +0000980#define NO_CHECK_ITER_16 \
981 fi = fx >> kCache16Shift; \
982 SkASSERT(fi <= kCache16Mask); \
983 fx += dx; \
984 *dstC++ = cache[toggle + fi]; \
985 toggle ^= TOGGLE_MASK
986
987
reed@google.com61eb0402011-04-15 12:11:12 +0000988void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989 SkASSERT(count > 0);
990
991 SkPoint srcPt;
992 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
993 TileProc proc = fTileProc;
994 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@google.com5eb158d2011-04-15 15:50:34 +0000996 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000998 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000999 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1000 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1002
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001003 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 SkFixed dxStorage[1];
1005 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1006 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001007 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1009 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1010 }
1011
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001012 if (SkFixedNearlyZero(dx)) {
1013 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +00001014 unsigned fi = proc(fx) >> kCache16Shift;
1015 SkASSERT(fi <= kCache16Mask);
reed@google.com5eb158d2011-04-15 15:50:34 +00001016 dither_memset16(dstC, cache[toggle + fi],
1017 cache[(toggle ^ TOGGLE_MASK) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001018 } else if (proc == clamp_tileproc) {
reed@google.com17705072011-04-18 12:43:32 +00001019#ifdef SK_ENABLE_FAST_LINEAR_GRADIENTS
reed@google.com5eb158d2011-04-15 15:50:34 +00001020 SkClampRange range;
1021 range.init(fx, dx, count, 0, kCache16Mask);
1022
1023 if ((count = range.fCount0) > 0) {
1024 dither_memset16(dstC,
1025 cache[toggle + range.fV0],
1026 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
1027 count);
1028 dstC += count;
1029 }
1030 if ((count = range.fCount1) > 0) {
1031 unsigned fi;
1032 int i, unroll = count >> 3;
1033 for (i = 0; i < unroll; i++) {
1034 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1035 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1036 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1037 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1038 }
1039 if ((count &= 7) > 0) {
1040 do {
1041 NO_CHECK_ITER_16;
1042 } while (--count != 0);
1043 }
1044 }
1045 if ((count = range.fCount2) > 0) {
1046 dither_memset16(dstC,
1047 cache[toggle + range.fV1],
1048 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
1049 count);
1050 }
reed@google.com17705072011-04-18 12:43:32 +00001051#else
1052 do {
1053 unsigned fi = SkClampMax(fx >> kCache16Shift, kCache16Mask);
1054 SkASSERT(fi <= kCache16Mask);
1055 fx += dx;
1056 *dstC++ = cache[toggle + fi];
1057 toggle ^= TOGGLE_MASK;
1058 } while (--count != 0);
reed@google.com5eb158d2011-04-15 15:50:34 +00001059#endif
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001060 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061 do {
reed@android.com200645d2009-12-14 16:41:57 +00001062 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001063 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001064 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001066 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001067 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001068 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 SkASSERT(proc == repeat_tileproc);
1070 do {
reed@android.com200645d2009-12-14 16:41:57 +00001071 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001072 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001073 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001075 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001076 } while (--count != 0);
1077 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001078 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001079 SkScalar dstX = SkIntToScalar(x);
1080 SkScalar dstY = SkIntToScalar(y);
1081 do {
1082 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1083 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1084 SkASSERT(fi <= 0xFFFF);
1085
reed@android.com512a8762009-12-14 15:25:36 +00001086 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001087 *dstC++ = cache[toggle + index];
reed@google.com5eb158d2011-04-15 15:50:34 +00001088 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001089
1090 dstX += SK_Scalar1;
1091 } while (--count != 0);
1092 }
1093}
1094
1095///////////////////////////////////////////////////////////////////////////////
1096
1097#define kSQRT_TABLE_BITS 11
1098#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1099
1100#include "SkRadialGradient_Table.h"
1101
1102#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1103
1104#include <stdio.h>
1105
reed@google.com61eb0402011-04-15 12:11:12 +00001106void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001107 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1108
1109 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1110 SkASSERT(file);
1111 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1112
reed@google.com61eb0402011-04-15 12:11:12 +00001113 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1114 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001115 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001116 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001117
1118 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1119
1120 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001121 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001123 }
1124 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001125 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001126 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127 }
1128 ::fprintf(file, "};\n");
1129 ::fclose(file);
1130}
1131
1132#endif
1133
1134
reed@google.com61eb0402011-04-15 12:11:12 +00001135static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1136 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001137 SkScalar inv = SkScalarInvert(radius);
1138
1139 matrix->setTranslate(-center.fX, -center.fY);
1140 matrix->postScale(inv, inv);
1141}
1142
1143class Radial_Gradient : public Gradient_Shader {
1144public:
1145 Radial_Gradient(const SkPoint& center, SkScalar radius,
1146 const SkColor colors[], const SkScalar pos[], int colorCount,
1147 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001148 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1149 fCenter(center),
1150 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001151 {
1152 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1153 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1154
1155 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1156 }
reed@google.com61eb0402011-04-15 12:11:12 +00001157
1158 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001159 SkASSERT(count > 0);
1160
1161 SkPoint srcPt;
1162 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1163 TileProc proc = fTileProc;
1164 const SkPMColor* cache = this->getCache32();
1165
reed@google.com61eb0402011-04-15 12:11:12 +00001166 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001167 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1168 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001169 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1170 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1171
reed@google.com61eb0402011-04-15 12:11:12 +00001172 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 SkFixed storage[2];
1174 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1175 dx = storage[0];
1176 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00001177 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001178 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1179 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1180 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1181 }
1182
reed@google.com61eb0402011-04-15 12:11:12 +00001183 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001184 const uint8_t* sqrt_table = gSqrt8Table;
1185 fx >>= 1;
1186 dx >>= 1;
1187 fy >>= 1;
1188 dy >>= 1;
1189 do {
1190 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1191 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1192 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1193 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1194 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1195 fx += dx;
1196 fy += dy;
1197 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001198 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001200 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1201 if (magnitudeSquared < 0) // Overflow.
1202 magnitudeSquared = SK_FixedMax;
1203 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 unsigned fi = mirror_tileproc(dist);
1205 SkASSERT(fi <= 0xFFFF);
1206 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1207 fx += dx;
1208 fy += dy;
1209 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001210 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211 SkASSERT(proc == repeat_tileproc);
1212 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001213 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1214 if (magnitudeSquared < 0) // Overflow.
1215 magnitudeSquared = SK_FixedMax;
1216 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217 unsigned fi = repeat_tileproc(dist);
1218 SkASSERT(fi <= 0xFFFF);
1219 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1220 fx += dx;
1221 fy += dy;
1222 } while (--count != 0);
1223 }
reed@google.com61eb0402011-04-15 12:11:12 +00001224 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225 SkScalar dstX = SkIntToScalar(x);
1226 SkScalar dstY = SkIntToScalar(y);
1227 do {
1228 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1229 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1230 SkASSERT(fi <= 0xFFFF);
1231 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1232 dstX += SK_Scalar1;
1233 } while (--count != 0);
1234 }
1235 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001236
1237 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238 SkASSERT(count > 0);
1239
1240 SkPoint srcPt;
1241 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1242 TileProc proc = fTileProc;
1243 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245
reed@android.com3c9b2a42009-08-27 19:28:37 +00001246 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001247 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1248 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1250 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1251
reed@android.com3c9b2a42009-08-27 19:28:37 +00001252 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 SkFixed storage[2];
1254 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1255 dx = storage[0];
1256 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001257 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1259 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1260 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1261 }
1262
reed@android.com3c9b2a42009-08-27 19:28:37 +00001263 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 const uint8_t* sqrt_table = gSqrt8Table;
1265
1266 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1267 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1268 precision, but that appears to be visually OK. If we decide this is OK for
1269 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1270 to avoid having to do these extra shifts each time.
1271 */
1272 fx >>= 1;
1273 dx >>= 1;
1274 fy >>= 1;
1275 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001276 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 +00001277 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1278 fy *= fy;
1279 do {
1280 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1281 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1282 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1283 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1285 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001287 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 do {
1289 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1290 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1291 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1292 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1293 fx += dx;
1294 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001295 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1296 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297 } while (--count != 0);
1298 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001299 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300 do {
1301 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1302 unsigned fi = mirror_tileproc(dist);
1303 SkASSERT(fi <= 0xFFFF);
1304 fx += dx;
1305 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1307 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001309 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310 SkASSERT(proc == repeat_tileproc);
1311 do {
1312 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1313 unsigned fi = repeat_tileproc(dist);
1314 SkASSERT(fi <= 0xFFFF);
1315 fx += dx;
1316 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001317 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1318 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 } while (--count != 0);
1320 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001321 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322 SkScalar dstX = SkIntToScalar(x);
1323 SkScalar dstY = SkIntToScalar(y);
1324 do {
1325 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1326 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1327 SkASSERT(fi <= 0xFFFF);
1328
1329 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 *dstC++ = cache[toggle + index];
1331 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001332
1333 dstX += SK_Scalar1;
1334 } while (--count != 0);
1335 }
1336 }
1337
reed@google.com55b8e8c2011-01-13 16:22:35 +00001338 virtual BitmapType asABitmap(SkBitmap* bitmap,
1339 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001340 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001341 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001342 if (bitmap) {
1343 this->commonAsABitmap(bitmap);
1344 }
1345 if (matrix) {
1346 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1347 matrix->preConcat(fPtsToUnit);
1348 }
1349 if (xy) {
1350 xy[0] = fTileMode;
1351 xy[1] = kClamp_TileMode;
1352 }
1353 return kRadial_BitmapType;
1354 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001355 virtual GradientType asAGradient(GradientInfo* info) const {
1356 if (info) {
1357 commonAsAGradient(info);
1358 info->fPoint[0] = fCenter;
1359 info->fRadius[0] = fRadius;
1360 }
1361 return kRadial_GradientType;
1362 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001363
1364 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365 return SkNEW_ARGS(Radial_Gradient, (buffer));
1366 }
1367
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001368 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1369 this->INHERITED::flatten(buffer);
1370 buffer.writeScalar(fCenter.fX);
1371 buffer.writeScalar(fCenter.fY);
1372 buffer.writeScalar(fRadius);
1373 }
1374
reed@android.com8a1c16f2008-12-17 15:59:43 +00001375protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001376 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1377 : Gradient_Shader(buffer),
1378 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1379 fRadius(buffer.readScalar()) {
1380 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001381 virtual Factory getFactory() { return CreateProc; }
1382
1383private:
1384 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001385 const SkPoint fCenter;
1386 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001387};
1388
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001389/* Two-point radial gradients are specified by two circles, each with a center
1390 point and radius. The gradient can be considered to be a series of
1391 concentric circles, with the color interpolated from the start circle
1392 (at t=0) to the end circle (at t=1).
1393
1394 For each point (x, y) in the span, we want to find the
1395 interpolated circle that intersects that point. The center
1396 of the desired circle (Cx, Cy) falls at some distance t
1397 along the line segment between the start point (Sx, Sy) and
1398 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001399
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001400 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1401 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001402
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001403 The radius of the desired circle (r) is also a linear interpolation t
1404 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001405
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001406 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001407
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001408 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001409
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001410 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001411
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001412 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001413
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001414 (x - ((1 - t) * Sx + t * Ex))^2
1415 + (y - ((1 - t) * Sy + t * Ey))^2
1416 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001417
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001418 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001419
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001420 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1421 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1422 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001423
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001424 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1425
1426 [Dx^2 + Dy^2 - Dr^2)] * t^2
1427 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1428 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001429
1430 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001431 possible circles on which the point may fall. Solving for t yields
1432 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001433
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001434 If a<0, the start circle is entirely contained in the
1435 end circle, and one of the roots will be <0 or >1 (off the line
1436 segment). If a>0, the start circle falls at least partially
1437 outside the end circle (or vice versa), and the gradient
1438 defines a "tube" where a point may be on one circle (on the
1439 inside of the tube) or the other (outside of the tube). We choose
1440 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001441
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001442 In order to keep the math to within the limits of fixed point,
1443 we divide the entire quadratic by Dr^2, and replace
1444 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001445
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001446 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1447 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1448 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001449
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001450 (x' and y' are computed by appending the subtract and scale to the
1451 fDstToIndex matrix in the constructor).
1452
1453 Since the 'A' component of the quadratic is independent of x' and y', it
1454 is precomputed in the constructor. Since the 'B' component is linear in
1455 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001456 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001457 a perspective projection), it must be computed in the loop.
1458
1459*/
1460
reed@google.come61414c2011-04-15 18:14:16 +00001461#ifdef SK_USE_SLOW_2POINT_RADIAL_GRADIENT
1462static inline SkFixed two_point_radial(SkFixed b, SkFixed fx, SkFixed fy, SkFixed sr2d2, SkFixed foura, SkFixed oneOverTwoA, bool posRoot) {
1463 SkFixed c = SkFixedSquare(fx) + SkFixedSquare(fy) - sr2d2;
1464 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1465 if (discrim < 0) {
1466 discrim = -discrim;
1467 }
1468 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1469 if (posRoot) {
1470 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1471 } else {
1472 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1473 }
1474}
1475#else
reed@google.com84e9c082011-04-13 17:44:24 +00001476static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1477 SkScalar sr2d2, SkScalar foura,
1478 SkScalar oneOverTwoA, bool posRoot) {
1479 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
1480 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001481 if (discrim < 0) {
1482 discrim = -discrim;
1483 }
reed@google.com84e9c082011-04-13 17:44:24 +00001484 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1485 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001486 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001487 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001488 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001489 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001490 }
reed@google.com84e9c082011-04-13 17:44:24 +00001491 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001492}
reed@google.come61414c2011-04-15 18:14:16 +00001493#endif
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001494
1495class Two_Point_Radial_Gradient : public Gradient_Shader {
1496public:
1497 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1498 const SkPoint& end, SkScalar endRadius,
1499 const SkColor colors[], const SkScalar pos[],
1500 int colorCount, SkShader::TileMode mode,
1501 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001502 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1503 fCenter1(start),
1504 fCenter2(end),
1505 fRadius1(startRadius),
1506 fRadius2(endRadius) {
1507 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001508 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001509
1510 virtual BitmapType asABitmap(SkBitmap* bitmap,
1511 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001512 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001513 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001514 if (bitmap) {
1515 this->commonAsABitmap(bitmap);
1516 }
1517 SkScalar diffL = 0; // just to avoid gcc warning
1518 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001519 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001520 SkScalarSquare(fDiff.fY));
1521 }
1522 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001523 if (diffL) {
1524 SkScalar invDiffL = SkScalarInvert(diffL);
1525 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1526 SkScalarMul(invDiffL, fDiff.fX));
1527 } else {
1528 matrix->reset();
1529 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001530 matrix->preConcat(fPtsToUnit);
1531 }
1532 if (xy) {
1533 xy[0] = fTileMode;
1534 xy[1] = kClamp_TileMode;
1535 }
1536 if (NULL != twoPointRadialParams) {
1537 twoPointRadialParams[0] = diffL;
1538 twoPointRadialParams[1] = fStartRadius;
1539 twoPointRadialParams[2] = fDiffRadius;
1540 }
1541 return kTwoPointRadial_BitmapType;
1542 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001543
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001544 virtual GradientType asAGradient(GradientInfo* info) const {
1545 if (info) {
1546 commonAsAGradient(info);
1547 info->fPoint[0] = fCenter1;
1548 info->fPoint[1] = fCenter2;
1549 info->fRadius[0] = fRadius1;
1550 info->fRadius[1] = fRadius2;
1551 }
1552 return kRadial2_GradientType;
1553 }
1554
reed@google.come61414c2011-04-15 18:14:16 +00001555#ifdef SK_USE_SLOW_2POINT_RADIAL_GRADIENT
1556 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1557 {
1558 SkASSERT(count > 0);
1559
1560 // Zero difference between radii: fill with transparent black.
1561 if (fDiffRadius == 0) {
1562 sk_bzero(dstC, count * sizeof(*dstC));
1563 return;
1564 }
1565 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1566 TileProc proc = fTileProc;
1567 const SkPMColor* cache = this->getCache32();
1568 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1569 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1570 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1571 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1572 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1573 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1574 bool posRoot = fDiffRadius < 0;
1575 if (fDstToIndexClass != kPerspective_MatrixClass)
1576 {
1577 SkPoint srcPt;
1578 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1579 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1580 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1581 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1582
1583 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1584 {
1585 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1586 }
1587 else
1588 {
1589 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1590 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1591 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1592 }
1593 SkFixed b = (SkFixedMul(diffx, fx) +
1594 SkFixedMul(diffy, fy) - startRadius) << 1;
1595 SkFixed db = (SkFixedMul(diffx, dx) +
1596 SkFixedMul(diffy, dy)) << 1;
1597 if (proc == clamp_tileproc)
1598 {
1599 for (; count > 0; --count) {
1600 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1601 SkFixed index = SkClampMax(t, 0xFFFF);
1602 SkASSERT(index <= 0xFFFF);
1603 *dstC++ = cache[index >> (16 - kCache32Bits)];
1604 fx += dx;
1605 fy += dy;
1606 b += db;
1607 }
1608 }
1609 else if (proc == mirror_tileproc)
1610 {
1611 for (; count > 0; --count) {
1612 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1613 SkFixed index = mirror_tileproc(t);
1614 SkASSERT(index <= 0xFFFF);
1615 *dstC++ = cache[index >> (16 - kCache32Bits)];
1616 fx += dx;
1617 fy += dy;
1618 b += db;
1619 }
1620 }
1621 else
1622 {
1623 SkASSERT(proc == repeat_tileproc);
1624 for (; count > 0; --count) {
1625 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1626 SkFixed index = repeat_tileproc(t);
1627 SkASSERT(index <= 0xFFFF);
1628 *dstC++ = cache[index >> (16 - kCache32Bits)];
1629 fx += dx;
1630 fy += dy;
1631 b += db;
1632 }
1633 }
1634 }
1635 else // perspective case
1636 {
1637 SkScalar dstX = SkIntToScalar(x);
1638 SkScalar dstY = SkIntToScalar(y);
1639 for (; count > 0; --count) {
1640 SkPoint srcPt;
1641 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1642 SkFixed fx = SkScalarToFixed(srcPt.fX);
1643 SkFixed fy = SkScalarToFixed(srcPt.fY);
1644 SkFixed b = (SkFixedMul(diffx, fx) +
1645 SkFixedMul(diffy, fy) - startRadius) << 1;
1646 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1647 SkFixed index = proc(t);
1648 SkASSERT(index <= 0xFFFF);
1649 *dstC++ = cache[index >> (16 - kCache32Bits)];
1650 dstX += SK_Scalar1;
1651 }
1652 }
1653 }
1654#else
reed@google.com61eb0402011-04-15 12:11:12 +00001655 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001656 SkASSERT(count > 0);
1657
1658 // Zero difference between radii: fill with transparent black.
1659 if (fDiffRadius == 0) {
1660 sk_bzero(dstC, count * sizeof(*dstC));
1661 return;
1662 }
1663 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1664 TileProc proc = fTileProc;
1665 const SkPMColor* cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001666
1667 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001668 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001669 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001670 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001671 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1672 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001673 SkScalar dx, fx = srcPt.fX;
1674 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001675
reed@google.com61eb0402011-04-15 12:11:12 +00001676 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001677 SkFixed fixedX, fixedY;
1678 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1679 dx = SkFixedToScalar(fixedX);
1680 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001681 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001682 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001683 dx = fDstToIndex.getScaleX();
1684 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001685 }
reed@google.com84e9c082011-04-13 17:44:24 +00001686 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1687 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1688 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1689 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001690 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001691 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001692 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001693 SkFixed index = SkClampMax(t, 0xFFFF);
1694 SkASSERT(index <= 0xFFFF);
1695 *dstC++ = cache[index >> (16 - kCache32Bits)];
1696 fx += dx;
1697 fy += dy;
1698 b += db;
1699 }
reed@google.com61eb0402011-04-15 12:11:12 +00001700 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001701 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001702 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001703 SkFixed index = mirror_tileproc(t);
1704 SkASSERT(index <= 0xFFFF);
1705 *dstC++ = cache[index >> (16 - kCache32Bits)];
1706 fx += dx;
1707 fy += dy;
1708 b += db;
1709 }
reed@google.com61eb0402011-04-15 12:11:12 +00001710 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001711 SkASSERT(proc == repeat_tileproc);
1712 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001713 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001714 SkFixed index = repeat_tileproc(t);
1715 SkASSERT(index <= 0xFFFF);
1716 *dstC++ = cache[index >> (16 - kCache32Bits)];
1717 fx += dx;
1718 fy += dy;
1719 b += db;
1720 }
1721 }
reed@google.com61eb0402011-04-15 12:11:12 +00001722 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001723 SkScalar dstX = SkIntToScalar(x);
1724 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001725 for (; count > 0; --count) {
1726 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001727 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001728 SkScalar fx = srcPt.fX;
1729 SkScalar fy = srcPt.fY;
1730 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1731 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1732 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001733 SkFixed index = proc(t);
1734 SkASSERT(index <= 0xFFFF);
1735 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001736 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001737 }
1738 }
1739 }
reed@google.come61414c2011-04-15 18:14:16 +00001740#endif
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001741
reed@android.com6c59a172009-09-22 20:24:05 +00001742 virtual bool setContext(const SkBitmap& device,
1743 const SkPaint& paint,
1744 const SkMatrix& matrix) {
1745 if (!this->INHERITED::setContext(device, paint, matrix)) {
1746 return false;
1747 }
1748
1749 // we don't have a span16 proc
1750 fFlags &= ~kHasSpan16_Flag;
1751 return true;
1752 }
1753
reed@google.com55b8e8c2011-01-13 16:22:35 +00001754 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001755 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1756 }
1757
reed@android.combcfc7332009-11-10 16:19:39 +00001758 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1759 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001760 buffer.writeScalar(fCenter1.fX);
1761 buffer.writeScalar(fCenter1.fY);
1762 buffer.writeScalar(fCenter2.fX);
1763 buffer.writeScalar(fCenter2.fY);
1764 buffer.writeScalar(fRadius1);
1765 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001766 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001767
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001768protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001769 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001770 : Gradient_Shader(buffer),
1771 fCenter1(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1772 fCenter2(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1773 fRadius1(buffer.readScalar()),
1774 fRadius2(buffer.readScalar()) {
1775 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001776 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001777 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001778
1779private:
1780 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001781 const SkPoint fCenter1;
1782 const SkPoint fCenter2;
1783 const SkScalar fRadius1;
1784 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001785 SkPoint fDiff;
1786 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001787
1788 void init() {
1789 fDiff = fCenter1 - fCenter2;
1790 fDiffRadius = fRadius2 - fRadius1;
1791 SkScalar inv = SkScalarInvert(fDiffRadius);
1792 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1793 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1794 fStartRadius = SkScalarMul(fRadius1, inv);
1795 fSr2D2 = SkScalarSquare(fStartRadius);
1796 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1797 fOneOverTwoA = SkScalarInvert(fA * 2);
1798
1799 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1800 fPtsToUnit.postScale(inv, inv);
1801 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001802};
1803
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804///////////////////////////////////////////////////////////////////////////////
1805
1806class Sweep_Gradient : public Gradient_Shader {
1807public:
1808 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1809 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001810 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1811 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812 {
1813 fPtsToUnit.setTranslate(-cx, -cy);
1814 }
1815 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1816 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001817
1818 virtual BitmapType asABitmap(SkBitmap* bitmap,
1819 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001820 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001821 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001822 if (bitmap) {
1823 this->commonAsABitmap(bitmap);
1824 }
1825 if (matrix) {
1826 *matrix = fPtsToUnit;
1827 }
1828 if (xy) {
1829 xy[0] = fTileMode;
1830 xy[1] = kClamp_TileMode;
1831 }
1832 return kSweep_BitmapType;
1833 }
1834
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001835 virtual GradientType asAGradient(GradientInfo* info) const {
1836 if (info) {
1837 commonAsAGradient(info);
1838 info->fPoint[0] = fCenter;
1839 }
1840 return kSweep_GradientType;
1841 }
1842
reed@android.com8a1c16f2008-12-17 15:59:43 +00001843 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1844 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1845 }
1846
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001847 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1848 this->INHERITED::flatten(buffer);
1849 buffer.writeScalar(fCenter.fX);
1850 buffer.writeScalar(fCenter.fY);
1851 }
1852
reed@android.com8a1c16f2008-12-17 15:59:43 +00001853protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001854 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1855 : Gradient_Shader(buffer),
1856 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
1857 }
1858
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859 virtual Factory getFactory() { return CreateProc; }
1860
1861private:
1862 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001863 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864};
1865
1866#ifdef COMPUTE_SWEEP_TABLE
1867#define PI 3.14159265
1868static bool gSweepTableReady;
1869static uint8_t gSweepTable[65];
1870
1871/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1872 We scale the results to [0..32]
1873*/
reed@google.com61eb0402011-04-15 12:11:12 +00001874static const uint8_t* build_sweep_table() {
1875 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001876 const int N = 65;
1877 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001878
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 for (int i = 0; i < N; i++)
1880 {
1881 double arg = i / DENOM;
1882 double v = atan(arg);
1883 int iv = (int)round(v * DENOM * 2 / PI);
1884// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1885 printf("%d, ", iv);
1886 gSweepTable[i] = iv;
1887 }
1888 gSweepTableReady = true;
1889 }
1890 return gSweepTable;
1891}
1892#else
1893static const uint8_t gSweepTable[] = {
1894 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1895 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1896 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1897 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1898 32
1899};
1900static const uint8_t* build_sweep_table() { return gSweepTable; }
1901#endif
1902
1903// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1904// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1905// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1906
1907//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001908static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001909 SkASSERT(numer <= denom);
1910 SkASSERT(numer > 0);
1911 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001912
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 int nbits = SkCLZ(numer);
1914 int dbits = SkCLZ(denom);
1915 int bits = 6 - nbits + dbits;
1916 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001917
reed@google.com61eb0402011-04-15 12:11:12 +00001918 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001920 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921
1922 denom <<= dbits - 1;
1923 numer <<= nbits - 1;
1924
1925 unsigned result = 0;
1926
1927 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001928 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001929 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001930 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001932 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001933
reed@android.com8a1c16f2008-12-17 15:59:43 +00001934 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001935 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936 // make room for the rest of the answer bits
1937 result <<= bits;
1938 switch (bits) {
1939 case 6:
1940 if ((numer = (numer << 1) - denom) >= 0)
1941 result |= 32;
1942 else
1943 numer += denom;
1944 case 5:
1945 if ((numer = (numer << 1) - denom) >= 0)
1946 result |= 16;
1947 else
1948 numer += denom;
1949 case 4:
1950 if ((numer = (numer << 1) - denom) >= 0)
1951 result |= 8;
1952 else
1953 numer += denom;
1954 case 3:
1955 if ((numer = (numer << 1) - denom) >= 0)
1956 result |= 4;
1957 else
1958 numer += denom;
1959 case 2:
1960 if ((numer = (numer << 1) - denom) >= 0)
1961 result |= 2;
1962 else
1963 numer += denom;
1964 case 1:
1965 default: // not strictly need, but makes GCC make better ARM code
1966 if ((numer = (numer << 1) - denom) >= 0)
1967 result |= 1;
1968 else
1969 numer += denom;
1970 }
1971 }
1972 return result;
1973}
1974
1975// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001976static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977#ifdef SK_DEBUG
1978 {
1979 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001980 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 gOnce = true;
1982 SkASSERT(div_64(55, 55) == 64);
1983 SkASSERT(div_64(128, 256) == 32);
1984 SkASSERT(div_64(2326528, 4685824) == 31);
1985 SkASSERT(div_64(753664, 5210112) == 9);
1986 SkASSERT(div_64(229376, 4882432) == 3);
1987 SkASSERT(div_64(2, 64) == 2);
1988 SkASSERT(div_64(1, 64) == 1);
1989 // test that we handle underflow correctly
1990 SkASSERT(div_64(12345, 0x54321234) == 0);
1991 }
1992 }
1993#endif
1994
1995 SkASSERT(y > 0 && x > 0);
1996 const uint8_t* table = build_sweep_table();
1997
1998 unsigned result;
1999 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00002000 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001 // first part of the atan(v) = PI/2 - atan(1/v) identity
2002 // since our div_64 and table want v <= 1, where v = y/x
2003 SkTSwap<SkFixed>(x, y);
2004 }
2005
2006 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002007
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008#ifdef SK_DEBUG
2009 {
2010 unsigned result2 = SkDivBits(y, x, 6);
2011 SkASSERT(result2 == result ||
2012 (result == 1 && result2 == 0));
2013 }
2014#endif
2015
2016 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
2017 result = table[result];
2018
reed@google.com61eb0402011-04-15 12:11:12 +00002019 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020 // complete the atan(v) = PI/2 - atan(1/v) identity
2021 result = 64 - result;
2022 // pin to 63
2023 result -= result >> 6;
2024 }
2025
2026 SkASSERT(result <= 63);
2027 return result;
2028}
2029
2030// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com61eb0402011-04-15 12:11:12 +00002031static unsigned SkATan2_255(SkFixed y, SkFixed x) {
2032 if (x == 0) {
2033 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002035 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 return y < 0 ? 192 : 64;
2037 }
reed@google.com61eb0402011-04-15 12:11:12 +00002038 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00002040 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002041
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 /* Find the right quadrant for x,y
2043 Since atan_0_90 only handles the first quadrant, we rotate x,y
2044 appropriately before calling it, and then add the right amount
2045 to account for the real quadrant.
2046 quadrant 0 : add 0 | x > 0 && y > 0
2047 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
2048 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
2049 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00002050
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 map x<0 to (1 << 6)
2052 map y<0 to (3 << 6)
2053 add = map_x ^ map_y
2054 */
2055 int xsign = x >> 31;
2056 int ysign = y >> 31;
2057 int add = ((-xsign) ^ (ysign & 3)) << 6;
2058
2059#ifdef SK_DEBUG
2060 if (0 == add)
2061 SkASSERT(x > 0 && y > 0);
2062 else if (64 == add)
2063 SkASSERT(x < 0 && y > 0);
2064 else if (128 == add)
2065 SkASSERT(x < 0 && y < 0);
2066 else if (192 == add)
2067 SkASSERT(x > 0 && y < 0);
2068 else
2069 SkASSERT(!"bad value for add");
2070#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00002071
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
2073 where we need to rotate x,y by 90 or -90
2074 */
2075 x = (x ^ xsign) - xsign;
2076 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00002077 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00002078 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00002079 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002080
2081 unsigned result = add + atan_0_90(y, x);
2082 SkASSERT(result < 256);
2083 return result;
2084}
2085
reed@google.com61eb0402011-04-15 12:11:12 +00002086void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087 SkMatrix::MapXYProc proc = fDstToIndexProc;
2088 const SkMatrix& matrix = fDstToIndex;
2089 const SkPMColor* cache = this->getCache32();
2090 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00002091
reed@google.com61eb0402011-04-15 12:11:12 +00002092 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2094 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2095 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2096 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002097
reed@google.com61eb0402011-04-15 12:11:12 +00002098 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099 SkFixed storage[2];
2100 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2101 &storage[0], &storage[1]);
2102 dx = storage[0];
2103 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002104 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2106 dx = SkScalarToFixed(matrix.getScaleX());
2107 dy = SkScalarToFixed(matrix.getSkewY());
2108 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002109
reed@google.com61eb0402011-04-15 12:11:12 +00002110 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002111 *dstC++ = cache[SkATan2_255(fy, fx)];
2112 fx += dx;
2113 fy += dy;
2114 }
reed@google.com61eb0402011-04-15 12:11:12 +00002115 } else { // perspective case
2116 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002117 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2118 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002119
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2121 SkScalarToFixed(srcPt.fX));
2122 *dstC++ = cache[index];
2123 }
2124 }
2125}
2126
reed@google.com61eb0402011-04-15 12:11:12 +00002127void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002128 SkMatrix::MapXYProc proc = fDstToIndexProc;
2129 const SkMatrix& matrix = fDstToIndex;
2130 const uint16_t* cache = this->getCache16();
2131 int toggle = ((x ^ y) & 1) << kCache16Bits;
2132 SkPoint srcPt;
2133
reed@google.com61eb0402011-04-15 12:11:12 +00002134 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2136 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2137 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2138 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002139
reed@google.com61eb0402011-04-15 12:11:12 +00002140 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 SkFixed storage[2];
2142 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2143 &storage[0], &storage[1]);
2144 dx = storage[0];
2145 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002146 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2148 dx = SkScalarToFixed(matrix.getScaleX());
2149 dy = SkScalarToFixed(matrix.getSkewY());
2150 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002151
reed@google.com61eb0402011-04-15 12:11:12 +00002152 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2154 *dstC++ = cache[toggle + index];
2155 toggle ^= (1 << kCache16Bits);
2156 fx += dx;
2157 fy += dy;
2158 }
reed@google.com61eb0402011-04-15 12:11:12 +00002159 } else { // perspective case
2160 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002161 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2162 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002163
reed@android.com8a1c16f2008-12-17 15:59:43 +00002164 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2165 SkScalarToFixed(srcPt.fX));
2166 index >>= (8 - kCache16Bits);
2167 *dstC++ = cache[toggle + index];
2168 toggle ^= (1 << kCache16Bits);
2169 }
2170 }
2171}
2172
reed@google.com61eb0402011-04-15 12:11:12 +00002173///////////////////////////////////////////////////////////////////////////////
2174///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175
2176// assumes colors is SkColor* and pos is SkScalar*
2177#define EXPAND_1_COLOR(count) \
2178 SkColor tmp[2]; \
2179 do { \
2180 if (1 == count) { \
2181 tmp[0] = tmp[1] = colors[0]; \
2182 colors = tmp; \
2183 pos = NULL; \
2184 count = 2; \
2185 } \
2186 } while (0)
2187
reed@google.com61eb0402011-04-15 12:11:12 +00002188SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2189 const SkColor colors[],
2190 const SkScalar pos[], int colorCount,
2191 SkShader::TileMode mode,
2192 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193 if (NULL == pts || NULL == colors || colorCount < 1) {
2194 return NULL;
2195 }
2196 EXPAND_1_COLOR(colorCount);
2197
reed@android.comab840b82009-07-01 17:00:03 +00002198 return SkNEW_ARGS(Linear_Gradient,
2199 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002200}
2201
reed@google.com61eb0402011-04-15 12:11:12 +00002202SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2203 const SkColor colors[],
2204 const SkScalar pos[], int colorCount,
2205 SkShader::TileMode mode,
2206 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002207 if (radius <= 0 || NULL == colors || colorCount < 1) {
2208 return NULL;
2209 }
2210 EXPAND_1_COLOR(colorCount);
2211
reed@android.comab840b82009-07-01 17:00:03 +00002212 return SkNEW_ARGS(Radial_Gradient,
2213 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002214}
2215
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002216SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2217 SkScalar startRadius,
2218 const SkPoint& end,
2219 SkScalar endRadius,
2220 const SkColor colors[],
2221 const SkScalar pos[],
2222 int colorCount,
2223 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002224 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002225 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2226 return NULL;
2227 }
2228 EXPAND_1_COLOR(colorCount);
2229
2230 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002231 (start, startRadius, end, endRadius, colors, pos,
2232 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002233}
2234
reed@android.com8a1c16f2008-12-17 15:59:43 +00002235SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2236 const SkColor colors[],
2237 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002238 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002239 if (NULL == colors || count < 1) {
2240 return NULL;
2241 }
2242 EXPAND_1_COLOR(count);
2243
2244 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2245}
2246
2247static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2248 Linear_Gradient::CreateProc);
2249
2250static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2251 Radial_Gradient::CreateProc);
2252
2253static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2254 Sweep_Gradient::CreateProc);
2255