blob: 3f3b85fccb0703bb6c4259005d43aa75e1e696d5 [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.com55b8e8c2011-01-13 16:22:35 +000026#define USE_DITHER_32BIT_GRADIENT
27
reed@android.com8a1c16f2008-12-17 15:59:43 +000028///////////////////////////////////////////////////////////////////////////
29
30typedef SkFixed (*TileProc)(SkFixed);
31
reed@android.com41bccf52009-04-03 13:33:51 +000032static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000033 return SkClampMax(x, 0xFFFF);
34}
35
reed@android.com41bccf52009-04-03 13:33:51 +000036static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000037 return x & 0xFFFF;
38}
39
reed@android.com41bccf52009-04-03 13:33:51 +000040static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000041 int s = x << 15 >> 31;
42 return (x ^ s) & 0xFFFF;
43}
44
45static const TileProc gTileProcs[] = {
46 clamp_tileproc,
47 repeat_tileproc,
48 mirror_tileproc
49};
50
51//////////////////////////////////////////////////////////////////////////////
52
reed@android.com200645d2009-12-14 16:41:57 +000053static inline int repeat_bits(int x, const int bits) {
54 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000055}
56
reed@android.com200645d2009-12-14 16:41:57 +000057static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000058#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000059 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000060 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000061 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000062#else
reed@android.com200645d2009-12-14 16:41:57 +000063 int s = x << (31 - bits) >> 31;
64 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000065#endif
66}
67
reed@android.com41bccf52009-04-03 13:33:51 +000068static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 return x & 0xFF;
70}
71
reed@android.com41bccf52009-04-03 13:33:51 +000072static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000073#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000074 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +000076 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 return x & 255;
78#else
79 int s = x << 23 >> 31;
80 return (x ^ s) & 0xFF;
81#endif
82}
83
84//////////////////////////////////////////////////////////////////////////////
85
86class Gradient_Shader : public SkShader {
87public:
88 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +000089 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +000090 virtual ~Gradient_Shader();
91
92 // overrides
93 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
94 virtual uint32_t getFlags() { return fFlags; }
95
96protected:
97 Gradient_Shader(SkFlattenableReadBuffer& );
98 SkUnitMapper* fMapper;
99 SkMatrix fPtsToUnit; // set by subclass
100 SkMatrix fDstToIndex;
101 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102 TileMode fTileMode;
103 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000104 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 uint8_t fDstToIndexClass;
106 uint8_t fFlags;
107
108 struct Rec {
109 SkFixed fPos; // 0...1
110 uint32_t fScale; // (1 << 24) / range
111 };
112 Rec* fRecs;
113
114 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000115 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000117 kCache16Mask = kCache16Count - 1,
118 kCache16Shift = 16 - kCache16Bits,
119
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120 kCache32Bits = 8, // pretty much should always be 8
121 kCache32Count = 1 << kCache32Bits
122 };
123 virtual void flatten(SkFlattenableWriteBuffer& );
124 const uint16_t* getCache16();
125 const SkPMColor* getCache32();
126
reed@google.comdc731fd2010-12-23 15:19:47 +0000127 SkMallocPixelRef* fCache32PixelRef;
128
129 void commonAsABitmap(SkBitmap*);
reed@android.com9b46e772009-06-05 12:24:41 +0000130
reed@android.com8a1c16f2008-12-17 15:59:43 +0000131private:
132 enum {
133 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
134
reed@android.com1c12abe2009-07-02 15:01:02 +0000135 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000136 };
137 SkColor fStorage[(kStorageSize + 3) >> 2];
138 SkColor* fOrigColors;
139
140 uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
141 SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
142
143 uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144 unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
145
reed@android.com512a8762009-12-14 15:25:36 +0000146 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000147 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
148 U8CPU alpha);
reed@android.com512a8762009-12-14 15:25:36 +0000149
reed@android.com8a1c16f2008-12-17 15:59:43 +0000150 typedef SkShader INHERITED;
151};
152
reed@android.com41bccf52009-04-03 13:33:51 +0000153static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154 SkASSERT(x >= 0 && x <= SK_Scalar1);
155
156#ifdef SK_SCALAR_IS_FLOAT
157 return (unsigned)(x * 0xFFFF);
158#else
159 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
160#endif
161}
162
reed@android.com41bccf52009-04-03 13:33:51 +0000163Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
164 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165 SkASSERT(colorCount > 1);
166
167 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
168
169 fMapper = mapper;
170 mapper->safeRef();
171
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
173 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
174 fTileMode = mode;
175 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000176
reed@android.com41bccf52009-04-03 13:33:51 +0000177 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000178 fCache32 = NULL;
179 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180
reed@android.com41bccf52009-04-03 13:33:51 +0000181 /* Note: we let the caller skip the first and/or last position.
182 i.e. pos[0] = 0.3, pos[1] = 0.7
183 In these cases, we insert dummy entries to ensure that the final data
184 will be bracketed by [0, 1].
185 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
186
187 Thus colorCount (the caller's value, and fColorCount (our value) may
188 differ by up to 2. In the above example:
189 colorCount = 2
190 fColorCount = 4
191 */
192 fColorCount = colorCount;
193 // check if we need to add in dummy start and/or end position/colors
194 bool dummyFirst = false;
195 bool dummyLast = false;
196 if (pos) {
197 dummyFirst = pos[0] != 0;
198 dummyLast = pos[colorCount - 1] != SK_Scalar1;
199 fColorCount += dummyFirst + dummyLast;
200 }
201
202 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000203 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000204 fOrigColors = reinterpret_cast<SkColor*>(
205 sk_malloc_throw(size * fColorCount));
206 }
207 else {
208 fOrigColors = fStorage;
209 }
210
211 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 {
reed@android.com41bccf52009-04-03 13:33:51 +0000213 SkColor* origColors = fOrigColors;
214 if (dummyFirst) {
215 *origColors++ = colors[0];
216 }
217 memcpy(origColors, colors, colorCount * sizeof(SkColor));
218 if (dummyLast) {
219 origColors += colorCount;
220 *origColors = colors[colorCount - 1];
221 }
222 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223
reed@android.com1c12abe2009-07-02 15:01:02 +0000224 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000225 if (fColorCount > 2) {
226 Rec* recs = fRecs;
227 recs->fPos = 0;
228 // recs->fScale = 0; // unused;
229 recs += 1;
230 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000231 /* We need to convert the user's array of relative positions into
232 fixed-point positions and scale factors. We need these results
233 to be strictly monotonic (no two values equal or out of order).
234 Hence this complex loop that just jams a zero for the scale
235 value if it sees a segment out of order, and it assures that
236 we start at 0 and end at 1.0
237 */
238 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000239 int startIndex = dummyFirst ? 0 : 1;
240 int count = colorCount + dummyLast;
241 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000242 // force the last value to be 1.0
243 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000244 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000246 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000247 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000248 }
reed@android.com41bccf52009-04-03 13:33:51 +0000249 // pin curr withing range
250 if (curr < 0) {
251 curr = 0;
252 } else if (curr > SK_Fixed1) {
253 curr = SK_Fixed1;
254 }
255 recs->fPos = curr;
256 if (curr > prev) {
257 recs->fScale = (1 << 24) / (curr - prev);
258 } else {
259 recs->fScale = 0; // ignore this segment
260 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000261 // get ready for the next value
262 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000263 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000264 }
reed@android.com41bccf52009-04-03 13:33:51 +0000265 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266 SkFixed dp = SK_Fixed1 / (colorCount - 1);
267 SkFixed p = dp;
268 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000269 for (int i = 1; i < colorCount; i++) {
270 recs->fPos = p;
271 recs->fScale = scale;
272 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000273 p += dp;
274 }
275 }
276 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000277 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000278}
279
280Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000281 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282 fCacheAlpha = 256;
283
284 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
285
286 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000287 fCache32 = NULL;
288 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289
reed@android.com41bccf52009-04-03 13:33:51 +0000290 int colorCount = fColorCount = buffer.readU32();
291 if (colorCount > kColorStorageCount) {
292 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
293 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
294 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000296 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000298
299 fTileMode = (TileMode)buffer.readU8();
300 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000301 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302 if (colorCount > 2) {
303 Rec* recs = fRecs;
304 recs[0].fPos = 0;
305 for (int i = 1; i < colorCount; i++) {
306 recs[i].fPos = buffer.readS32();
307 recs[i].fScale = buffer.readU32();
308 }
309 }
310 buffer.read(&fPtsToUnit, sizeof(SkMatrix));
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000311 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312}
313
reed@android.com41bccf52009-04-03 13:33:51 +0000314Gradient_Shader::~Gradient_Shader() {
315 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000316 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000317 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000318 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000319 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000321 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 fMapper->safeUnref();
323}
324
reed@android.com41bccf52009-04-03 13:33:51 +0000325void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 this->INHERITED::flatten(buffer);
327 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000328 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
330 buffer.write8(fTileMode);
331 if (fColorCount > 2) {
332 Rec* recs = fRecs;
333 for (int i = 1; i < fColorCount; i++) {
334 buffer.write32(recs[i].fPos);
335 buffer.write32(recs[i].fScale);
336 }
337 }
338 buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
339}
340
341bool Gradient_Shader::setContext(const SkBitmap& device,
342 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000343 const SkMatrix& matrix) {
344 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000345 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000346 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347
348 const SkMatrix& inverse = this->getTotalInverse();
349
350 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
351 return false;
352 }
353
354 fDstToIndexProc = fDstToIndex.getMapXYProc();
355 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
356
357 // now convert our colors in to PMColors
358 unsigned paintAlpha = this->getPaintAlpha();
359 unsigned colorAlpha = 0xFF;
360
reed@android.com3d06a8c2009-07-07 18:19:59 +0000361 // FIXME: record colorAlpha in constructor, since this is not affected
362 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000363 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000364 SkColor src = fOrigColors[i];
365 unsigned sa = SkColorGetA(src);
366 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367 }
368
369 fFlags = this->INHERITED::getFlags();
370 if ((colorAlpha & paintAlpha) == 0xFF) {
371 fFlags |= kOpaqueAlpha_Flag;
372 }
373 // we can do span16 as long as our individual colors are opaque,
374 // regardless of the paint's alpha
375 if (0xFF == colorAlpha) {
376 fFlags |= kHasSpan16_Flag;
377 }
378
379 // if the new alpha differs from the previous time we were called, inval our cache
380 // this will trigger the cache to be rebuilt.
381 // we don't care about the first time, since the cache ptrs will already be NULL
382 if (fCacheAlpha != paintAlpha) {
383 fCache16 = NULL; // inval the cache
384 fCache32 = NULL; // inval the cache
385 fCacheAlpha = paintAlpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000386 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000387 if (fCache32PixelRef) {
388 fCache32PixelRef->notifyPixelsChanged();
389 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000390 }
391 return true;
392}
393
reed@android.com41bccf52009-04-03 13:33:51 +0000394static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000395 SkASSERT(a == SkToU8(a));
396 SkASSERT(b == SkToU8(b));
397 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000398 return a + ((b - a) * scale >> 8);
399}
400
reed@android.com41bccf52009-04-03 13:33:51 +0000401static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
402 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000403#if 0
404 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
405 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
406 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
407 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
408
409 return SkPackARGB32(a, r, g, b);
410#else
411 int otherBlend = 256 - blend;
412
413#if 0
414 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
415 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
416 SkASSERT((t0 & t1) == 0);
417 return t0 | t1;
418#else
419 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
420 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
421#endif
422
423#endif
424}
425
426#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
427
reed@android.com41bccf52009-04-03 13:33:51 +0000428/** We take the original colors, not our premultiplied PMColors, since we can
429 build a 16bit table as long as the original colors are opaque, even if the
430 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431*/
reed@android.com512a8762009-12-14 15:25:36 +0000432void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
433 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434 SkASSERT(count > 1);
435 SkASSERT(SkColorGetA(c0) == 0xFF);
436 SkASSERT(SkColorGetA(c1) == 0xFF);
437
438 SkFixed r = SkColorGetR(c0);
439 SkFixed g = SkColorGetG(c0);
440 SkFixed b = SkColorGetB(c0);
441
442 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
443 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
444 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
445
446 r = SkIntToFixed(r) + 0x8000;
447 g = SkIntToFixed(g) + 0x8000;
448 b = SkIntToFixed(b) + 0x8000;
449
450 do {
451 unsigned rr = r >> 16;
452 unsigned gg = g >> 16;
453 unsigned bb = b >> 16;
454 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000455 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000456 cache += 1;
457 r += dr;
458 g += dg;
459 b += db;
460 } while (--count != 0);
461}
462
reed@google.com55b8e8c2011-01-13 16:22:35 +0000463/*
464 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
465 * semantics of how we 2x2 dither 32->16
466 */
467static inline U8CPU dither_fixed_to_8(SkFixed n) {
468 n >>= 8;
469 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
470}
471
472/*
473 * For dithering with premultiply, we want to ceiling the alpha component,
474 * to ensure that it is always >= any color component.
475 */
476static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
477 n >>= 8;
478 return ((n << 1) - (n | (n >> 8))) >> 8;
479}
480
481void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
482 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000483 SkASSERT(count > 1);
484
reed@android.com1c12abe2009-07-02 15:01:02 +0000485 // need to apply paintAlpha to our two endpoints
486 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
487 SkFixed da;
488 {
489 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
490 da = SkIntToFixed(tmp - a) / (count - 1);
491 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000492
reed@android.com1c12abe2009-07-02 15:01:02 +0000493 SkFixed r = SkColorGetR(c0);
494 SkFixed g = SkColorGetG(c0);
495 SkFixed b = SkColorGetB(c0);
496 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
497 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
498 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000499
500 a = SkIntToFixed(a) + 0x8000;
501 r = SkIntToFixed(r) + 0x8000;
502 g = SkIntToFixed(g) + 0x8000;
503 b = SkIntToFixed(b) + 0x8000;
504
505 do {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000506 cache[0] = SkPreMultiplyARGB(a >> 16, r >> 16, g >> 16, b >> 16);
507 cache[kCache32Count] = SkPreMultiplyARGB(dither_ceil_fixed_to_8(a),
508 dither_fixed_to_8(r),
509 dither_fixed_to_8(g),
510 dither_fixed_to_8(b));
511 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000512 a += da;
513 r += dr;
514 g += dg;
515 b += db;
516 } while (--count != 0);
517}
518
reed@android.com41bccf52009-04-03 13:33:51 +0000519static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000520 SkASSERT((unsigned)x <= SK_Fixed1);
521 return x - (x >> 16);
522}
523
reed@android.com200645d2009-12-14 16:41:57 +0000524static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000525 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000526 if (6 == bits) {
527 return (x << 10) | (x << 4) | (x >> 2);
528 }
529 if (8 == bits) {
530 return (x << 8) | x;
531 }
532 sk_throw();
533 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000534}
535
reed@android.com41bccf52009-04-03 13:33:51 +0000536const uint16_t* Gradient_Shader::getCache16() {
537 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000538 // double the count for dither entries
539 const int entryCount = kCache16Count * 2;
540 const size_t allocSize = sizeof(uint16_t) * entryCount;
541
reed@android.com3c9b2a42009-08-27 19:28:37 +0000542 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000543 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000544 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000545 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000546 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000547 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000548 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000549 Rec* rec = fRecs;
550 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000551 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000552 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000553 SkASSERT(nextIndex < kCache16Count);
554
555 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000556 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000557 prevIndex = nextIndex;
558 }
559 SkASSERT(prevIndex == kCache16Count - 1);
560 }
561
reed@android.com41bccf52009-04-03 13:33:51 +0000562 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000563 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000564 uint16_t* linear = fCache16; // just computed linear data
565 uint16_t* mapped = fCache16Storage; // storage for mapped data
566 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000567 for (int i = 0; i < kCache16Count; i++) {
568 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000569 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000570 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000571 }
572 sk_free(fCache16);
573 fCache16 = fCache16Storage;
574 }
575 }
576 return fCache16;
577}
578
reed@android.com41bccf52009-04-03 13:33:51 +0000579const SkPMColor* Gradient_Shader::getCache32() {
580 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000581 // double the count for dither entries
582 const int entryCount = kCache32Count * 2;
583 const size_t allocSize = sizeof(SkPMColor) * entryCount;
584
reed@google.comdc731fd2010-12-23 15:19:47 +0000585 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000586 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
587 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000588 }
589 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000590 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000591 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
592 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000593 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000594 Rec* rec = fRecs;
595 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000596 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
598 SkASSERT(nextIndex < kCache32Count);
599
600 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000601 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
602 fOrigColors[i],
603 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000604 prevIndex = nextIndex;
605 }
606 SkASSERT(prevIndex == kCache32Count - 1);
607 }
608
reed@android.com41bccf52009-04-03 13:33:51 +0000609 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000610 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000611 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000612 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000613 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000614 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000615 for (int i = 0; i < kCache32Count; i++) {
616 int index = map->mapUnit16((i << 8) | i) >> 8;
617 mapped[i] = linear[index];
618 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000619 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000620 fCache32PixelRef->unref();
621 fCache32PixelRef = newPR;
622 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000623 }
624 }
625 return fCache32;
626}
627
reed@google.comdc731fd2010-12-23 15:19:47 +0000628/*
629 * Because our caller might rebuild the same (logically the same) gradient
630 * over and over, we'd like to return exactly the same "bitmap" if possible,
631 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
632 * To do that, we maintain a private cache of built-bitmaps, based on our
633 * colors and positions. Note: we don't try to flatten the fMapper, so if one
634 * is present, we skip the cache for now.
635 */
636void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) {
637 // don't have a way to put the mapper into our cache-key yet
638 if (fMapper) {
639 // force our cahce32pixelref to be built
640 (void)this->getCache32();
641 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
642 bitmap->setPixelRef(fCache32PixelRef);
643 return;
644 }
645
646 // build our key: [numColors + colors[] + {positions[]} ]
647 int count = 1 + fColorCount;
648 if (fColorCount > 2) {
649 count += fColorCount - 1; // fRecs[].fPos
650 }
651
652 SkAutoSTMalloc<16, int32_t> storage(count);
653 int32_t* buffer = storage.get();
654
655 *buffer++ = fColorCount;
656 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
657 buffer += fColorCount;
658 if (fColorCount > 2) {
659 for (int i = 1; i < fColorCount; i++) {
660 *buffer++ = fRecs[i].fPos;
661 }
662 }
663 SkASSERT(buffer - storage.get() == count);
664
665 ///////////////////////////////////
666
667 static SkMutex gMutex;
668 static SkBitmapCache* gCache;
669 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
670 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
671 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000672
reed@google.comdc731fd2010-12-23 15:19:47 +0000673 if (NULL == gCache) {
674 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
675 }
676 size_t size = count * sizeof(int32_t);
677
678 if (!gCache->find(storage.get(), size, bitmap)) {
679 // force our cahce32pixelref to be built
680 (void)this->getCache32();
681 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
682 bitmap->setPixelRef(fCache32PixelRef);
683
684 gCache->add(storage.get(), size, *bitmap);
685 }
686}
687
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688///////////////////////////////////////////////////////////////////////////
689
reed@android.com41bccf52009-04-03 13:33:51 +0000690static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691 SkVector vec = pts[1] - pts[0];
692 SkScalar mag = vec.length();
693 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
694
695 vec.scale(inv);
696 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
697 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
698 matrix->postScale(inv, inv);
699}
700
701///////////////////////////////////////////////////////////////////////////////
702
703class Linear_Gradient : public Gradient_Shader {
704public:
705 Linear_Gradient(const SkPoint pts[2],
706 const SkColor colors[], const SkScalar pos[], int colorCount,
707 SkShader::TileMode mode, SkUnitMapper* mapper)
708 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
709 {
710 pts_to_unit_matrix(pts, &fPtsToUnit);
711 }
reed@android.com9b46e772009-06-05 12:24:41 +0000712
reed@android.com5119bdb2009-06-12 21:27:03 +0000713 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
715 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000716 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*,
reed@google.comdc731fd2010-12-23 15:19:47 +0000717 TileMode*, SkScalar* twoPointRadialParams);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718
reed@google.com55b8e8c2011-01-13 16:22:35 +0000719 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720 return SkNEW_ARGS(Linear_Gradient, (buffer));
721 }
722
723protected:
reed@google.comdc731fd2010-12-23 15:19:47 +0000724 Linear_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 virtual Factory getFactory() { return CreateProc; }
726
727private:
728 typedef Gradient_Shader INHERITED;
729};
730
reed@android.com5119bdb2009-06-12 21:27:03 +0000731bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
732 const SkMatrix& matrix) {
733 if (!this->INHERITED::setContext(device, paint, matrix)) {
734 return false;
735 }
736
737 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
738 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000739 fFlags |= SkShader::kConstInY32_Flag;
740 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
741 // only claim this if we do have a 16bit mode (i.e. none of our
742 // colors have alpha), and if we are not dithering (which obviously
743 // is not const in Y).
744 fFlags |= SkShader::kConstInY16_Flag;
745 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000746 }
747 return true;
748}
749
reed@android.com8a1c16f2008-12-17 15:59:43 +0000750// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@android.comfc25abd2009-01-15 14:38:33 +0000751static inline bool no_need_for_clamp(int fx, int dx, int count)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752{
753 SkASSERT(count > 0);
754 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
755}
756
757void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
758{
759 SkASSERT(count > 0);
760
761 SkPoint srcPt;
762 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
763 TileProc proc = fTileProc;
764 const SkPMColor* cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000765#ifdef USE_DITHER_32BIT_GRADIENT
766 int toggle = ((x ^ y) & 1) << kCache32Bits;
767 const int TOGGLE_MASK = (1 << kCache32Bits);
768#else
769 int toggle = 0;
770 const int TOGGLE_MASK = 0;
771#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772
reed@android.comc552a432009-06-12 20:02:50 +0000773 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000774 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
775 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
777
reed@android.comc552a432009-06-12 20:02:50 +0000778 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779 SkFixed dxStorage[1];
780 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
781 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000782 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
784 dx = SkScalarToFixed(fDstToIndex.getScaleX());
785 }
786
reed@android.comc552a432009-06-12 20:02:50 +0000787 if (SkFixedNearlyZero(dx)) {
788 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 unsigned fi = proc(fx);
790 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000791 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000793 } else if (proc == clamp_tileproc) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000794 do {
795 unsigned fi = SkClampMax(fx >> 8, 0xFF);
796 SkASSERT(fi <= 0xFF);
797 fx += dx;
798 *dstC++ = cache[toggle + fi];
799 toggle ^= TOGGLE_MASK;
800 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000801 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802 do {
803 unsigned fi = mirror_8bits(fx >> 8);
804 SkASSERT(fi <= 0xFF);
805 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000806 *dstC++ = cache[toggle + fi];
807 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000809 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810 SkASSERT(proc == repeat_tileproc);
811 do {
812 unsigned fi = repeat_8bits(fx >> 8);
813 SkASSERT(fi <= 0xFF);
814 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000815 *dstC++ = cache[toggle + fi];
816 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817 } while (--count != 0);
818 }
reed@android.comc552a432009-06-12 20:02:50 +0000819 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 SkScalar dstX = SkIntToScalar(x);
821 SkScalar dstY = SkIntToScalar(y);
822 do {
823 dstProc(fDstToIndex, dstX, dstY, &srcPt);
824 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
825 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000826 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
827 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828 dstX += SK_Scalar1;
829 } while (--count != 0);
830 }
831}
832
reed@google.com55b8e8c2011-01-13 16:22:35 +0000833SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000834 SkMatrix* matrix,
835 TileMode xy[],
836 SkScalar* twoPointRadialParams) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000838 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839 }
840 if (matrix) {
841 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
842 matrix->preConcat(fPtsToUnit);
843 }
844 if (xy) {
845 xy[0] = fTileMode;
846 xy[1] = kClamp_TileMode;
847 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000848 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849}
850
reed@android.com3c9b2a42009-08-27 19:28:37 +0000851static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
852 int count) {
853 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854 *dst++ = value;
855 count -= 1;
856 SkTSwap(value, other);
857 }
858
859 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000860
reed@android.com3c9b2a42009-08-27 19:28:37 +0000861 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000863 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865
866void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
867{
868 SkASSERT(count > 0);
869
870 SkPoint srcPt;
871 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
872 TileProc proc = fTileProc;
873 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000874 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000876 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000877 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
878 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
880
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000881 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882 SkFixed dxStorage[1];
883 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
884 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000885 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000886 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
887 dx = SkScalarToFixed(fDstToIndex.getScaleX());
888 }
889
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000890 if (SkFixedNearlyZero(dx)) {
891 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +0000892 unsigned fi = proc(fx) >> kCache16Shift;
893 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000894 dither_memset16(dstC, cache[toggle + fi], cache[(toggle ^ (1 << kCache16Bits)) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000895 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000896 do {
reed@android.com512a8762009-12-14 15:25:36 +0000897 unsigned fi = SkClampMax(fx >> kCache16Shift, kCache16Mask);
898 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000899 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000900 *dstC++ = cache[toggle + fi];
901 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000902 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000903 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 do {
reed@android.com200645d2009-12-14 16:41:57 +0000905 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +0000906 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000907 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908 *dstC++ = cache[toggle + fi];
909 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000910 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000911 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000912 SkASSERT(proc == repeat_tileproc);
913 do {
reed@android.com200645d2009-12-14 16:41:57 +0000914 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +0000915 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000916 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000917 *dstC++ = cache[toggle + fi];
918 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000919 } while (--count != 0);
920 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000921 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000922 SkScalar dstX = SkIntToScalar(x);
923 SkScalar dstY = SkIntToScalar(y);
924 do {
925 dstProc(fDstToIndex, dstX, dstY, &srcPt);
926 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
927 SkASSERT(fi <= 0xFFFF);
928
reed@android.com512a8762009-12-14 15:25:36 +0000929 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000930 *dstC++ = cache[toggle + index];
931 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000932
933 dstX += SK_Scalar1;
934 } while (--count != 0);
935 }
936}
937
938///////////////////////////////////////////////////////////////////////////////
939
940#define kSQRT_TABLE_BITS 11
941#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
942
943#include "SkRadialGradient_Table.h"
944
945#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
946
947#include <stdio.h>
948
949void SkRadialGradient_BuildTable()
950{
951 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
952
953 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
954 SkASSERT(file);
955 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
956
957 for (int i = 0; i < kSQRT_TABLE_SIZE; i++)
958 {
959 if ((i & 15) == 0)
960 ::fprintf(file, "\t");
961
962 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
963
964 ::fprintf(file, "0x%02X", value);
965 if (i < kSQRT_TABLE_SIZE-1)
966 ::fprintf(file, ", ");
967 if ((i & 15) == 15)
968 ::fprintf(file, "\n");
969 }
970 ::fprintf(file, "};\n");
971 ::fclose(file);
972}
973
974#endif
975
976
977static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius, SkMatrix* matrix)
978{
979 SkScalar inv = SkScalarInvert(radius);
980
981 matrix->setTranslate(-center.fX, -center.fY);
982 matrix->postScale(inv, inv);
983}
984
985class Radial_Gradient : public Gradient_Shader {
986public:
987 Radial_Gradient(const SkPoint& center, SkScalar radius,
988 const SkColor colors[], const SkScalar pos[], int colorCount,
989 SkShader::TileMode mode, SkUnitMapper* mapper)
990 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
991 {
992 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
993 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
994
995 rad_to_unit_matrix(center, radius, &fPtsToUnit);
996 }
997 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
998 {
999 SkASSERT(count > 0);
1000
1001 SkPoint srcPt;
1002 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1003 TileProc proc = fTileProc;
1004 const SkPMColor* cache = this->getCache32();
1005
1006 if (fDstToIndexClass != kPerspective_MatrixClass)
1007 {
reed@google.comdc731fd2010-12-23 15:19:47 +00001008 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1009 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1011 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1012
1013 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1014 {
1015 SkFixed storage[2];
1016 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1017 dx = storage[0];
1018 dy = storage[1];
1019 }
1020 else
1021 {
1022 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1023 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1024 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1025 }
1026
1027 if (proc == clamp_tileproc)
1028 {
1029 const uint8_t* sqrt_table = gSqrt8Table;
1030 fx >>= 1;
1031 dx >>= 1;
1032 fy >>= 1;
1033 dy >>= 1;
1034 do {
1035 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1036 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1037 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1038 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1039 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1040 fx += dx;
1041 fy += dy;
1042 } while (--count != 0);
1043 }
1044 else if (proc == mirror_tileproc)
1045 {
1046 do {
1047 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1048 unsigned fi = mirror_tileproc(dist);
1049 SkASSERT(fi <= 0xFFFF);
1050 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1051 fx += dx;
1052 fy += dy;
1053 } while (--count != 0);
1054 }
1055 else
1056 {
1057 SkASSERT(proc == repeat_tileproc);
1058 do {
1059 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1060 unsigned fi = repeat_tileproc(dist);
1061 SkASSERT(fi <= 0xFFFF);
1062 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1063 fx += dx;
1064 fy += dy;
1065 } while (--count != 0);
1066 }
1067 }
1068 else // perspective case
1069 {
1070 SkScalar dstX = SkIntToScalar(x);
1071 SkScalar dstY = SkIntToScalar(y);
1072 do {
1073 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1074 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1075 SkASSERT(fi <= 0xFFFF);
1076 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1077 dstX += SK_Scalar1;
1078 } while (--count != 0);
1079 }
1080 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001081
1082 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001083 SkASSERT(count > 0);
1084
1085 SkPoint srcPt;
1086 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1087 TileProc proc = fTileProc;
1088 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001089 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001090
reed@android.com3c9b2a42009-08-27 19:28:37 +00001091 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001092 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1093 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001094 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1095 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1096
reed@android.com3c9b2a42009-08-27 19:28:37 +00001097 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001098 SkFixed storage[2];
1099 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1100 dx = storage[0];
1101 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001102 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001103 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1104 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1105 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1106 }
1107
reed@android.com3c9b2a42009-08-27 19:28:37 +00001108 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001109 const uint8_t* sqrt_table = gSqrt8Table;
1110
1111 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1112 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1113 precision, but that appears to be visually OK. If we decide this is OK for
1114 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1115 to avoid having to do these extra shifts each time.
1116 */
1117 fx >>= 1;
1118 dx >>= 1;
1119 fy >>= 1;
1120 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001121 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 +00001122 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1123 fy *= fy;
1124 do {
1125 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1126 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1127 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1128 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001129 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1130 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001131 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001132 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001133 do {
1134 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1135 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1136 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1137 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1138 fx += dx;
1139 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001140 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1141 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001142 } while (--count != 0);
1143 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001144 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001145 do {
1146 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1147 unsigned fi = mirror_tileproc(dist);
1148 SkASSERT(fi <= 0xFFFF);
1149 fx += dx;
1150 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001151 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1152 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001153 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001154 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155 SkASSERT(proc == repeat_tileproc);
1156 do {
1157 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1158 unsigned fi = repeat_tileproc(dist);
1159 SkASSERT(fi <= 0xFFFF);
1160 fx += dx;
1161 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1163 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001164 } while (--count != 0);
1165 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001166 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001167 SkScalar dstX = SkIntToScalar(x);
1168 SkScalar dstY = SkIntToScalar(y);
1169 do {
1170 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1171 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1172 SkASSERT(fi <= 0xFFFF);
1173
1174 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001175 *dstC++ = cache[toggle + index];
1176 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001177
1178 dstX += SK_Scalar1;
1179 } while (--count != 0);
1180 }
1181 }
1182
reed@google.com55b8e8c2011-01-13 16:22:35 +00001183 virtual BitmapType asABitmap(SkBitmap* bitmap,
1184 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001185 TileMode* xy,
1186 SkScalar* twoPointRadialParams) {
1187 if (bitmap) {
1188 this->commonAsABitmap(bitmap);
1189 }
1190 if (matrix) {
1191 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1192 matrix->preConcat(fPtsToUnit);
1193 }
1194 if (xy) {
1195 xy[0] = fTileMode;
1196 xy[1] = kClamp_TileMode;
1197 }
1198 return kRadial_BitmapType;
1199 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001200
1201 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 return SkNEW_ARGS(Radial_Gradient, (buffer));
1203 }
1204
1205protected:
1206 Radial_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {};
1207 virtual Factory getFactory() { return CreateProc; }
1208
1209private:
1210 typedef Gradient_Shader INHERITED;
1211};
1212
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001213/* Two-point radial gradients are specified by two circles, each with a center
1214 point and radius. The gradient can be considered to be a series of
1215 concentric circles, with the color interpolated from the start circle
1216 (at t=0) to the end circle (at t=1).
1217
1218 For each point (x, y) in the span, we want to find the
1219 interpolated circle that intersects that point. The center
1220 of the desired circle (Cx, Cy) falls at some distance t
1221 along the line segment between the start point (Sx, Sy) and
1222 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001223
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001224 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1225 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001226
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001227 The radius of the desired circle (r) is also a linear interpolation t
1228 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001229
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001230 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001231
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001232 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001233
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001234 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001235
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001236 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001237
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001238 (x - ((1 - t) * Sx + t * Ex))^2
1239 + (y - ((1 - t) * Sy + t * Ey))^2
1240 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001241
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001242 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001243
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001244 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1245 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1246 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001247
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001248 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1249
1250 [Dx^2 + Dy^2 - Dr^2)] * t^2
1251 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1252 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001253
1254 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001255 possible circles on which the point may fall. Solving for t yields
1256 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001257
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001258 If a<0, the start circle is entirely contained in the
1259 end circle, and one of the roots will be <0 or >1 (off the line
1260 segment). If a>0, the start circle falls at least partially
1261 outside the end circle (or vice versa), and the gradient
1262 defines a "tube" where a point may be on one circle (on the
1263 inside of the tube) or the other (outside of the tube). We choose
1264 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001265
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001266 In order to keep the math to within the limits of fixed point,
1267 we divide the entire quadratic by Dr^2, and replace
1268 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001269
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001270 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1271 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1272 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001273
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001274 (x' and y' are computed by appending the subtract and scale to the
1275 fDstToIndex matrix in the constructor).
1276
1277 Since the 'A' component of the quadratic is independent of x' and y', it
1278 is precomputed in the constructor. Since the 'B' component is linear in
1279 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001280 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001281 a perspective projection), it must be computed in the loop.
1282
1283*/
1284
1285static inline SkFixed two_point_radial(SkFixed b, SkFixed fx, SkFixed fy, SkFixed sr2d2, SkFixed foura, SkFixed oneOverTwoA, bool posRoot) {
1286 SkFixed c = SkFixedSquare(fx) + SkFixedSquare(fy) - sr2d2;
1287 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1288 if (discrim < 0) {
1289 discrim = -discrim;
1290 }
1291 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1292 if (posRoot) {
1293 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1294 } else {
1295 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1296 }
1297}
1298
1299class Two_Point_Radial_Gradient : public Gradient_Shader {
1300public:
1301 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1302 const SkPoint& end, SkScalar endRadius,
1303 const SkColor colors[], const SkScalar pos[],
1304 int colorCount, SkShader::TileMode mode,
1305 SkUnitMapper* mapper)
1306 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
1307 {
1308 fDiff = start - end;
1309 fDiffRadius = endRadius - startRadius;
1310 SkScalar inv = SkScalarInvert(fDiffRadius);
1311 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1312 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1313 fStartRadius = SkScalarMul(startRadius, inv);
1314 fSr2D2 = SkScalarSquare(fStartRadius);
1315 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1316 fOneOverTwoA = SkScalarInvert(fA * 2);
1317
1318 fPtsToUnit.setTranslate(-start.fX, -start.fY);
1319 fPtsToUnit.postScale(inv, inv);
1320 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001321
1322 virtual BitmapType asABitmap(SkBitmap* bitmap,
1323 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001324 TileMode* xy,
1325 SkScalar* twoPointRadialParams) {
1326 if (bitmap) {
1327 this->commonAsABitmap(bitmap);
1328 }
1329 SkScalar diffL = 0; // just to avoid gcc warning
1330 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001331 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001332 SkScalarSquare(fDiff.fY));
1333 }
1334 if (matrix) {
1335 SkScalar invDiffL = SkScalarInvert(diffL);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001336 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
reed@google.comdc731fd2010-12-23 15:19:47 +00001337 SkScalarMul(invDiffL, fDiff.fX));
1338 matrix->preConcat(fPtsToUnit);
1339 }
1340 if (xy) {
1341 xy[0] = fTileMode;
1342 xy[1] = kClamp_TileMode;
1343 }
1344 if (NULL != twoPointRadialParams) {
1345 twoPointRadialParams[0] = diffL;
1346 twoPointRadialParams[1] = fStartRadius;
1347 twoPointRadialParams[2] = fDiffRadius;
1348 }
1349 return kTwoPointRadial_BitmapType;
1350 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001351
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001352 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1353 {
1354 SkASSERT(count > 0);
1355
1356 // Zero difference between radii: fill with transparent black.
1357 if (fDiffRadius == 0) {
1358 sk_bzero(dstC, count * sizeof(*dstC));
1359 return;
1360 }
1361 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1362 TileProc proc = fTileProc;
1363 const SkPMColor* cache = this->getCache32();
1364 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1365 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1366 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1367 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1368 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1369 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1370 bool posRoot = fDiffRadius < 0;
1371 if (fDstToIndexClass != kPerspective_MatrixClass)
1372 {
1373 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001374 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1375 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001376 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1377 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1378
1379 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1380 {
1381 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1382 }
1383 else
1384 {
1385 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1386 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1387 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1388 }
1389 SkFixed b = (SkFixedMul(diffx, fx) +
1390 SkFixedMul(diffy, fy) - startRadius) << 1;
1391 SkFixed db = (SkFixedMul(diffx, dx) +
1392 SkFixedMul(diffy, dy)) << 1;
1393 if (proc == clamp_tileproc)
1394 {
1395 for (; count > 0; --count) {
1396 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1397 SkFixed index = SkClampMax(t, 0xFFFF);
1398 SkASSERT(index <= 0xFFFF);
1399 *dstC++ = cache[index >> (16 - kCache32Bits)];
1400 fx += dx;
1401 fy += dy;
1402 b += db;
1403 }
1404 }
1405 else if (proc == mirror_tileproc)
1406 {
1407 for (; count > 0; --count) {
1408 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1409 SkFixed index = mirror_tileproc(t);
1410 SkASSERT(index <= 0xFFFF);
1411 *dstC++ = cache[index >> (16 - kCache32Bits)];
1412 fx += dx;
1413 fy += dy;
1414 b += db;
1415 }
1416 }
1417 else
1418 {
1419 SkASSERT(proc == repeat_tileproc);
1420 for (; count > 0; --count) {
1421 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1422 SkFixed index = repeat_tileproc(t);
1423 SkASSERT(index <= 0xFFFF);
1424 *dstC++ = cache[index >> (16 - kCache32Bits)];
1425 fx += dx;
1426 fy += dy;
1427 b += db;
1428 }
1429 }
1430 }
1431 else // perspective case
1432 {
reed@android.com6c59a172009-09-22 20:24:05 +00001433 SkScalar dstX = SkIntToScalar(x);
1434 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001435 for (; count > 0; --count) {
1436 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001437 dstProc(fDstToIndex, dstX, dstY, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001438 SkFixed fx = SkScalarToFixed(srcPt.fX);
1439 SkFixed fy = SkScalarToFixed(srcPt.fY);
1440 SkFixed b = (SkFixedMul(diffx, fx) +
1441 SkFixedMul(diffy, fy) - startRadius) << 1;
1442 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1443 SkFixed index = proc(t);
1444 SkASSERT(index <= 0xFFFF);
1445 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001446 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001447 }
1448 }
1449 }
1450
reed@android.com6c59a172009-09-22 20:24:05 +00001451 virtual bool setContext(const SkBitmap& device,
1452 const SkPaint& paint,
1453 const SkMatrix& matrix) {
1454 if (!this->INHERITED::setContext(device, paint, matrix)) {
1455 return false;
1456 }
1457
1458 // we don't have a span16 proc
1459 fFlags &= ~kHasSpan16_Flag;
1460 return true;
1461 }
1462
reed@google.com55b8e8c2011-01-13 16:22:35 +00001463 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001464 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1465 }
1466
reed@android.combcfc7332009-11-10 16:19:39 +00001467 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1468 this->INHERITED::flatten(buffer);
1469 buffer.writeScalar(fDiff.fX);
1470 buffer.writeScalar(fDiff.fY);
1471 buffer.writeScalar(fStartRadius);
1472 buffer.writeScalar(fDiffRadius);
1473 buffer.writeScalar(fSr2D2);
1474 buffer.writeScalar(fA);
1475 buffer.writeScalar(fOneOverTwoA);
1476 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001477
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001478protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001479 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
1480 : Gradient_Shader(buffer) {
1481 fDiff.fX = buffer.readScalar();
1482 fDiff.fY = buffer.readScalar();
1483 fStartRadius = buffer.readScalar();
1484 fDiffRadius = buffer.readScalar();
1485 fSr2D2 = buffer.readScalar();
1486 fA = buffer.readScalar();
1487 fOneOverTwoA = buffer.readScalar();
1488 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001489 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001490
1491private:
1492 typedef Gradient_Shader INHERITED;
1493 SkPoint fDiff;
1494 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
1495};
1496
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497///////////////////////////////////////////////////////////////////////////////
1498
1499class Sweep_Gradient : public Gradient_Shader {
1500public:
1501 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1502 const SkScalar pos[], int count, SkUnitMapper* mapper)
1503 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper)
1504 {
1505 fPtsToUnit.setTranslate(-cx, -cy);
1506 }
1507 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1508 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
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,
1513 SkScalar* twoPointRadialParams) {
1514 if (bitmap) {
1515 this->commonAsABitmap(bitmap);
1516 }
1517 if (matrix) {
1518 *matrix = fPtsToUnit;
1519 }
1520 if (xy) {
1521 xy[0] = fTileMode;
1522 xy[1] = kClamp_TileMode;
1523 }
1524 return kSweep_BitmapType;
1525 }
1526
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1528 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1529 }
1530
1531protected:
1532 Sweep_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 virtual Factory getFactory() { return CreateProc; }
1534
1535private:
1536 typedef Gradient_Shader INHERITED;
1537};
1538
1539#ifdef COMPUTE_SWEEP_TABLE
1540#define PI 3.14159265
1541static bool gSweepTableReady;
1542static uint8_t gSweepTable[65];
1543
1544/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1545 We scale the results to [0..32]
1546*/
1547static const uint8_t* build_sweep_table()
1548{
1549 if (!gSweepTableReady)
1550 {
1551 const int N = 65;
1552 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001553
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 for (int i = 0; i < N; i++)
1555 {
1556 double arg = i / DENOM;
1557 double v = atan(arg);
1558 int iv = (int)round(v * DENOM * 2 / PI);
1559// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1560 printf("%d, ", iv);
1561 gSweepTable[i] = iv;
1562 }
1563 gSweepTableReady = true;
1564 }
1565 return gSweepTable;
1566}
1567#else
1568static const uint8_t gSweepTable[] = {
1569 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1570 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1571 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1572 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1573 32
1574};
1575static const uint8_t* build_sweep_table() { return gSweepTable; }
1576#endif
1577
1578// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1579// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1580// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1581
1582//unsigned div_64(int numer, int denom);
1583static unsigned div_64(int numer, int denom)
1584{
1585 SkASSERT(numer <= denom);
1586 SkASSERT(numer > 0);
1587 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001588
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 int nbits = SkCLZ(numer);
1590 int dbits = SkCLZ(denom);
1591 int bits = 6 - nbits + dbits;
1592 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001593
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594 if (bits < 0) // detect underflow
1595 return 0;
1596
1597 denom <<= dbits - 1;
1598 numer <<= nbits - 1;
1599
1600 unsigned result = 0;
1601
1602 // do the first one
1603 if ((numer -= denom) >= 0)
1604 result = 1;
1605 else
1606 numer += denom;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001607
reed@android.com8a1c16f2008-12-17 15:59:43 +00001608 // Now fall into our switch statement if there are more bits to compute
1609 if (bits > 0)
1610 {
1611 // make room for the rest of the answer bits
1612 result <<= bits;
1613 switch (bits) {
1614 case 6:
1615 if ((numer = (numer << 1) - denom) >= 0)
1616 result |= 32;
1617 else
1618 numer += denom;
1619 case 5:
1620 if ((numer = (numer << 1) - denom) >= 0)
1621 result |= 16;
1622 else
1623 numer += denom;
1624 case 4:
1625 if ((numer = (numer << 1) - denom) >= 0)
1626 result |= 8;
1627 else
1628 numer += denom;
1629 case 3:
1630 if ((numer = (numer << 1) - denom) >= 0)
1631 result |= 4;
1632 else
1633 numer += denom;
1634 case 2:
1635 if ((numer = (numer << 1) - denom) >= 0)
1636 result |= 2;
1637 else
1638 numer += denom;
1639 case 1:
1640 default: // not strictly need, but makes GCC make better ARM code
1641 if ((numer = (numer << 1) - denom) >= 0)
1642 result |= 1;
1643 else
1644 numer += denom;
1645 }
1646 }
1647 return result;
1648}
1649
1650// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1651static unsigned atan_0_90(SkFixed y, SkFixed x)
1652{
1653#ifdef SK_DEBUG
1654 {
1655 static bool gOnce;
1656 if (!gOnce)
1657 {
1658 gOnce = true;
1659 SkASSERT(div_64(55, 55) == 64);
1660 SkASSERT(div_64(128, 256) == 32);
1661 SkASSERT(div_64(2326528, 4685824) == 31);
1662 SkASSERT(div_64(753664, 5210112) == 9);
1663 SkASSERT(div_64(229376, 4882432) == 3);
1664 SkASSERT(div_64(2, 64) == 2);
1665 SkASSERT(div_64(1, 64) == 1);
1666 // test that we handle underflow correctly
1667 SkASSERT(div_64(12345, 0x54321234) == 0);
1668 }
1669 }
1670#endif
1671
1672 SkASSERT(y > 0 && x > 0);
1673 const uint8_t* table = build_sweep_table();
1674
1675 unsigned result;
1676 bool swap = (x < y);
1677 if (swap)
1678 {
1679 // first part of the atan(v) = PI/2 - atan(1/v) identity
1680 // since our div_64 and table want v <= 1, where v = y/x
1681 SkTSwap<SkFixed>(x, y);
1682 }
1683
1684 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001685
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686#ifdef SK_DEBUG
1687 {
1688 unsigned result2 = SkDivBits(y, x, 6);
1689 SkASSERT(result2 == result ||
1690 (result == 1 && result2 == 0));
1691 }
1692#endif
1693
1694 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1695 result = table[result];
1696
1697 if (swap)
1698 {
1699 // complete the atan(v) = PI/2 - atan(1/v) identity
1700 result = 64 - result;
1701 // pin to 63
1702 result -= result >> 6;
1703 }
1704
1705 SkASSERT(result <= 63);
1706 return result;
1707}
1708
1709// returns angle in a circle [0..2PI) -> [0..255]
1710static unsigned SkATan2_255(SkFixed y, SkFixed x)
1711{
1712 if (x == 0)
1713 {
1714 if (y == 0)
1715 return 0;
1716 return y < 0 ? 192 : 64;
1717 }
1718 if (y == 0)
1719 return x < 0 ? 128 : 0;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001720
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721 /* Find the right quadrant for x,y
1722 Since atan_0_90 only handles the first quadrant, we rotate x,y
1723 appropriately before calling it, and then add the right amount
1724 to account for the real quadrant.
1725 quadrant 0 : add 0 | x > 0 && y > 0
1726 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1727 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1728 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001729
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 map x<0 to (1 << 6)
1731 map y<0 to (3 << 6)
1732 add = map_x ^ map_y
1733 */
1734 int xsign = x >> 31;
1735 int ysign = y >> 31;
1736 int add = ((-xsign) ^ (ysign & 3)) << 6;
1737
1738#ifdef SK_DEBUG
1739 if (0 == add)
1740 SkASSERT(x > 0 && y > 0);
1741 else if (64 == add)
1742 SkASSERT(x < 0 && y > 0);
1743 else if (128 == add)
1744 SkASSERT(x < 0 && y < 0);
1745 else if (192 == add)
1746 SkASSERT(x > 0 && y < 0);
1747 else
1748 SkASSERT(!"bad value for add");
1749#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00001750
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1752 where we need to rotate x,y by 90 or -90
1753 */
1754 x = (x ^ xsign) - xsign;
1755 y = (y ^ ysign) - ysign;
1756 if (add & 64) // quads 1 or 3 need to swap x,y
1757 SkTSwap<SkFixed>(x, y);
1758
1759 unsigned result = add + atan_0_90(y, x);
1760 SkASSERT(result < 256);
1761 return result;
1762}
1763
1764void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1765{
1766 SkMatrix::MapXYProc proc = fDstToIndexProc;
1767 const SkMatrix& matrix = fDstToIndex;
1768 const SkPMColor* cache = this->getCache32();
1769 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001770
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771 if (fDstToIndexClass != kPerspective_MatrixClass)
1772 {
1773 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1774 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1775 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1776 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001777
reed@android.com8a1c16f2008-12-17 15:59:43 +00001778 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1779 {
1780 SkFixed storage[2];
1781 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1782 &storage[0], &storage[1]);
1783 dx = storage[0];
1784 dy = storage[1];
1785 }
1786 else
1787 {
1788 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1789 dx = SkScalarToFixed(matrix.getScaleX());
1790 dy = SkScalarToFixed(matrix.getSkewY());
1791 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001792
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 for (; count > 0; --count)
1794 {
1795 *dstC++ = cache[SkATan2_255(fy, fx)];
1796 fx += dx;
1797 fy += dy;
1798 }
1799 }
1800 else // perspective case
1801 {
1802 for (int stop = x + count; x < stop; x++)
1803 {
1804 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1805 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001806
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1808 SkScalarToFixed(srcPt.fX));
1809 *dstC++ = cache[index];
1810 }
1811 }
1812}
1813
1814void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1815{
1816 SkMatrix::MapXYProc proc = fDstToIndexProc;
1817 const SkMatrix& matrix = fDstToIndex;
1818 const uint16_t* cache = this->getCache16();
1819 int toggle = ((x ^ y) & 1) << kCache16Bits;
1820 SkPoint srcPt;
1821
1822 if (fDstToIndexClass != kPerspective_MatrixClass)
1823 {
1824 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1825 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1826 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1827 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001828
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1830 {
1831 SkFixed storage[2];
1832 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1833 &storage[0], &storage[1]);
1834 dx = storage[0];
1835 dy = storage[1];
1836 }
1837 else
1838 {
1839 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1840 dx = SkScalarToFixed(matrix.getScaleX());
1841 dy = SkScalarToFixed(matrix.getSkewY());
1842 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001843
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 for (; count > 0; --count)
1845 {
1846 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1847 *dstC++ = cache[toggle + index];
1848 toggle ^= (1 << kCache16Bits);
1849 fx += dx;
1850 fy += dy;
1851 }
1852 }
1853 else // perspective case
1854 {
1855 for (int stop = x + count; x < stop; x++)
1856 {
1857 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1858 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001859
reed@android.com8a1c16f2008-12-17 15:59:43 +00001860 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1861 SkScalarToFixed(srcPt.fX));
1862 index >>= (8 - kCache16Bits);
1863 *dstC++ = cache[toggle + index];
1864 toggle ^= (1 << kCache16Bits);
1865 }
1866 }
1867}
1868
1869///////////////////////////////////////////////////////////////////////////
1870///////////////////////////////////////////////////////////////////////////
1871
1872// assumes colors is SkColor* and pos is SkScalar*
1873#define EXPAND_1_COLOR(count) \
1874 SkColor tmp[2]; \
1875 do { \
1876 if (1 == count) { \
1877 tmp[0] = tmp[1] = colors[0]; \
1878 colors = tmp; \
1879 pos = NULL; \
1880 count = 2; \
1881 } \
1882 } while (0)
1883
1884SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
1885 const SkColor colors[], const SkScalar pos[], int colorCount,
1886 SkShader::TileMode mode, SkUnitMapper* mapper)
1887{
1888 if (NULL == pts || NULL == colors || colorCount < 1) {
1889 return NULL;
1890 }
1891 EXPAND_1_COLOR(colorCount);
1892
reed@android.comab840b82009-07-01 17:00:03 +00001893 return SkNEW_ARGS(Linear_Gradient,
1894 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895}
1896
1897SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
1898 const SkColor colors[], const SkScalar pos[], int colorCount,
1899 SkShader::TileMode mode, SkUnitMapper* mapper)
1900{
1901 if (radius <= 0 || NULL == colors || colorCount < 1) {
1902 return NULL;
1903 }
1904 EXPAND_1_COLOR(colorCount);
1905
reed@android.comab840b82009-07-01 17:00:03 +00001906 return SkNEW_ARGS(Radial_Gradient,
1907 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908}
1909
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001910SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
1911 SkScalar startRadius,
1912 const SkPoint& end,
1913 SkScalar endRadius,
1914 const SkColor colors[],
1915 const SkScalar pos[],
1916 int colorCount,
1917 SkShader::TileMode mode,
1918 SkUnitMapper* mapper)
1919{
1920 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
1921 return NULL;
1922 }
1923 EXPAND_1_COLOR(colorCount);
1924
1925 return SkNEW_ARGS(Two_Point_Radial_Gradient,
1926 (start, startRadius, end, endRadius, colors, pos, colorCount, mode, mapper));
1927}
1928
reed@android.com8a1c16f2008-12-17 15:59:43 +00001929SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
1930 const SkColor colors[],
1931 const SkScalar pos[],
1932 int count, SkUnitMapper* mapper)
1933{
1934 if (NULL == colors || count < 1) {
1935 return NULL;
1936 }
1937 EXPAND_1_COLOR(count);
1938
1939 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
1940}
1941
1942static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
1943 Linear_Gradient::CreateProc);
1944
1945static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
1946 Radial_Gradient::CreateProc);
1947
1948static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
1949 Sweep_Gradient::CreateProc);
1950