blob: 64a78c7f0e32a82e3c75149f744a852c3306c260 [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@android.com8a1c16f2008-12-17 15:59:43 +000030///////////////////////////////////////////////////////////////////////////
31
32typedef SkFixed (*TileProc)(SkFixed);
33
reed@android.com41bccf52009-04-03 13:33:51 +000034static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000035 return SkClampMax(x, 0xFFFF);
36}
37
reed@android.com41bccf52009-04-03 13:33:51 +000038static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000039 return x & 0xFFFF;
40}
41
reed@android.com41bccf52009-04-03 13:33:51 +000042static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000043 int s = x << 15 >> 31;
44 return (x ^ s) & 0xFFFF;
45}
46
47static const TileProc gTileProcs[] = {
48 clamp_tileproc,
49 repeat_tileproc,
50 mirror_tileproc
51};
52
53//////////////////////////////////////////////////////////////////////////////
54
reed@android.com200645d2009-12-14 16:41:57 +000055static inline int repeat_bits(int x, const int bits) {
56 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000057}
58
reed@android.com200645d2009-12-14 16:41:57 +000059static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000060#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000061 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000062 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000063 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000064#else
reed@android.com200645d2009-12-14 16:41:57 +000065 int s = x << (31 - bits) >> 31;
66 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000067#endif
68}
69
reed@android.com41bccf52009-04-03 13:33:51 +000070static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000071 return x & 0xFF;
72}
73
reed@android.com41bccf52009-04-03 13:33:51 +000074static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000075#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000076 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +000078 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 return x & 255;
80#else
81 int s = x << 23 >> 31;
82 return (x ^ s) & 0xFF;
83#endif
84}
85
86//////////////////////////////////////////////////////////////////////////////
87
88class Gradient_Shader : public SkShader {
89public:
90 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +000091 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +000092 virtual ~Gradient_Shader();
93
94 // overrides
95 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
96 virtual uint32_t getFlags() { return fFlags; }
97
98protected:
99 Gradient_Shader(SkFlattenableReadBuffer& );
100 SkUnitMapper* fMapper;
101 SkMatrix fPtsToUnit; // set by subclass
102 SkMatrix fDstToIndex;
103 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 TileMode fTileMode;
105 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000106 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 uint8_t fDstToIndexClass;
108 uint8_t fFlags;
109
110 struct Rec {
111 SkFixed fPos; // 0...1
112 uint32_t fScale; // (1 << 24) / range
113 };
114 Rec* fRecs;
115
116 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000117 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000119 kCache16Mask = kCache16Count - 1,
120 kCache16Shift = 16 - kCache16Bits,
121
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 kCache32Bits = 8, // pretty much should always be 8
123 kCache32Count = 1 << kCache32Bits
124 };
125 virtual void flatten(SkFlattenableWriteBuffer& );
126 const uint16_t* getCache16();
127 const SkPMColor* getCache32();
128
reed@google.comdc731fd2010-12-23 15:19:47 +0000129 SkMallocPixelRef* fCache32PixelRef;
130
131 void commonAsABitmap(SkBitmap*);
reed@android.com9b46e772009-06-05 12:24:41 +0000132
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133private:
134 enum {
135 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
136
reed@android.com1c12abe2009-07-02 15:01:02 +0000137 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000138 };
139 SkColor fStorage[(kStorageSize + 3) >> 2];
140 SkColor* fOrigColors;
141
142 uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
143 SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
144
145 uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
reed@android.com8a1c16f2008-12-17 15:59:43 +0000146 unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
147
reed@android.com512a8762009-12-14 15:25:36 +0000148 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000149 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
150 U8CPU alpha);
reed@android.com512a8762009-12-14 15:25:36 +0000151
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152 typedef SkShader INHERITED;
153};
154
reed@android.com41bccf52009-04-03 13:33:51 +0000155static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156 SkASSERT(x >= 0 && x <= SK_Scalar1);
157
158#ifdef SK_SCALAR_IS_FLOAT
159 return (unsigned)(x * 0xFFFF);
160#else
161 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
162#endif
163}
164
reed@android.com41bccf52009-04-03 13:33:51 +0000165Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
166 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167 SkASSERT(colorCount > 1);
168
169 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
170
171 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000172 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
175 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
176 fTileMode = mode;
177 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000178
reed@android.com41bccf52009-04-03 13:33:51 +0000179 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000180 fCache32 = NULL;
181 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182
reed@android.com41bccf52009-04-03 13:33:51 +0000183 /* Note: we let the caller skip the first and/or last position.
184 i.e. pos[0] = 0.3, pos[1] = 0.7
185 In these cases, we insert dummy entries to ensure that the final data
186 will be bracketed by [0, 1].
187 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
188
189 Thus colorCount (the caller's value, and fColorCount (our value) may
190 differ by up to 2. In the above example:
191 colorCount = 2
192 fColorCount = 4
193 */
194 fColorCount = colorCount;
195 // check if we need to add in dummy start and/or end position/colors
196 bool dummyFirst = false;
197 bool dummyLast = false;
198 if (pos) {
199 dummyFirst = pos[0] != 0;
200 dummyLast = pos[colorCount - 1] != SK_Scalar1;
201 fColorCount += dummyFirst + dummyLast;
202 }
203
204 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000205 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000206 fOrigColors = reinterpret_cast<SkColor*>(
207 sk_malloc_throw(size * fColorCount));
208 }
209 else {
210 fOrigColors = fStorage;
211 }
212
213 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 {
reed@android.com41bccf52009-04-03 13:33:51 +0000215 SkColor* origColors = fOrigColors;
216 if (dummyFirst) {
217 *origColors++ = colors[0];
218 }
219 memcpy(origColors, colors, colorCount * sizeof(SkColor));
220 if (dummyLast) {
221 origColors += colorCount;
222 *origColors = colors[colorCount - 1];
223 }
224 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225
reed@android.com1c12abe2009-07-02 15:01:02 +0000226 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000227 if (fColorCount > 2) {
228 Rec* recs = fRecs;
229 recs->fPos = 0;
230 // recs->fScale = 0; // unused;
231 recs += 1;
232 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000233 /* We need to convert the user's array of relative positions into
234 fixed-point positions and scale factors. We need these results
235 to be strictly monotonic (no two values equal or out of order).
236 Hence this complex loop that just jams a zero for the scale
237 value if it sees a segment out of order, and it assures that
238 we start at 0 and end at 1.0
239 */
240 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000241 int startIndex = dummyFirst ? 0 : 1;
242 int count = colorCount + dummyLast;
243 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244 // force the last value to be 1.0
245 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000246 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000247 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000248 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000249 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250 }
reed@android.com41bccf52009-04-03 13:33:51 +0000251 // pin curr withing range
252 if (curr < 0) {
253 curr = 0;
254 } else if (curr > SK_Fixed1) {
255 curr = SK_Fixed1;
256 }
257 recs->fPos = curr;
258 if (curr > prev) {
259 recs->fScale = (1 << 24) / (curr - prev);
260 } else {
261 recs->fScale = 0; // ignore this segment
262 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 // get ready for the next value
264 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000265 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266 }
reed@android.com41bccf52009-04-03 13:33:51 +0000267 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 SkFixed dp = SK_Fixed1 / (colorCount - 1);
269 SkFixed p = dp;
270 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000271 for (int i = 1; i < colorCount; i++) {
272 recs->fPos = p;
273 recs->fScale = scale;
274 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000275 p += dp;
276 }
277 }
278 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000279 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000280}
281
282Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000283 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000284 fCacheAlpha = 256;
285
286 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
287
288 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000289 fCache32 = NULL;
290 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000291
reed@android.com41bccf52009-04-03 13:33:51 +0000292 int colorCount = fColorCount = buffer.readU32();
293 if (colorCount > kColorStorageCount) {
294 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
295 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
296 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000298 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000300
301 fTileMode = (TileMode)buffer.readU8();
302 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000303 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 if (colorCount > 2) {
305 Rec* recs = fRecs;
306 recs[0].fPos = 0;
307 for (int i = 1; i < colorCount; i++) {
308 recs[i].fPos = buffer.readS32();
309 recs[i].fScale = buffer.readU32();
310 }
311 }
312 buffer.read(&fPtsToUnit, sizeof(SkMatrix));
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000313 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000314}
315
reed@android.com41bccf52009-04-03 13:33:51 +0000316Gradient_Shader::~Gradient_Shader() {
317 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000319 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000320 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000321 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000323 }
reed@google.com82065d62011-02-07 15:30:46 +0000324 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325}
326
reed@android.com41bccf52009-04-03 13:33:51 +0000327void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328 this->INHERITED::flatten(buffer);
329 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000330 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
332 buffer.write8(fTileMode);
333 if (fColorCount > 2) {
334 Rec* recs = fRecs;
335 for (int i = 1; i < fColorCount; i++) {
336 buffer.write32(recs[i].fPos);
337 buffer.write32(recs[i].fScale);
338 }
339 }
340 buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
341}
342
343bool Gradient_Shader::setContext(const SkBitmap& device,
344 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000345 const SkMatrix& matrix) {
346 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000348 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349
350 const SkMatrix& inverse = this->getTotalInverse();
351
352 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
353 return false;
354 }
355
356 fDstToIndexProc = fDstToIndex.getMapXYProc();
357 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
358
359 // now convert our colors in to PMColors
360 unsigned paintAlpha = this->getPaintAlpha();
361 unsigned colorAlpha = 0xFF;
362
reed@android.com3d06a8c2009-07-07 18:19:59 +0000363 // FIXME: record colorAlpha in constructor, since this is not affected
364 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000365 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366 SkColor src = fOrigColors[i];
367 unsigned sa = SkColorGetA(src);
368 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 }
370
371 fFlags = this->INHERITED::getFlags();
372 if ((colorAlpha & paintAlpha) == 0xFF) {
373 fFlags |= kOpaqueAlpha_Flag;
374 }
375 // we can do span16 as long as our individual colors are opaque,
376 // regardless of the paint's alpha
377 if (0xFF == colorAlpha) {
378 fFlags |= kHasSpan16_Flag;
379 }
380
381 // if the new alpha differs from the previous time we were called, inval our cache
382 // this will trigger the cache to be rebuilt.
383 // we don't care about the first time, since the cache ptrs will already be NULL
384 if (fCacheAlpha != paintAlpha) {
385 fCache16 = NULL; // inval the cache
386 fCache32 = NULL; // inval the cache
387 fCacheAlpha = paintAlpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000388 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000389 if (fCache32PixelRef) {
390 fCache32PixelRef->notifyPixelsChanged();
391 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000392 }
393 return true;
394}
395
reed@android.com41bccf52009-04-03 13:33:51 +0000396static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397 SkASSERT(a == SkToU8(a));
398 SkASSERT(b == SkToU8(b));
399 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000400 return a + ((b - a) * scale >> 8);
401}
402
reed@android.com41bccf52009-04-03 13:33:51 +0000403static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
404 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000405#if 0
406 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
407 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
408 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
409 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
410
411 return SkPackARGB32(a, r, g, b);
412#else
413 int otherBlend = 256 - blend;
414
415#if 0
416 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
417 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
418 SkASSERT((t0 & t1) == 0);
419 return t0 | t1;
420#else
421 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
422 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
423#endif
424
425#endif
426}
427
428#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
429
reed@android.com41bccf52009-04-03 13:33:51 +0000430/** We take the original colors, not our premultiplied PMColors, since we can
431 build a 16bit table as long as the original colors are opaque, even if the
432 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433*/
reed@android.com512a8762009-12-14 15:25:36 +0000434void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
435 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436 SkASSERT(count > 1);
437 SkASSERT(SkColorGetA(c0) == 0xFF);
438 SkASSERT(SkColorGetA(c1) == 0xFF);
439
440 SkFixed r = SkColorGetR(c0);
441 SkFixed g = SkColorGetG(c0);
442 SkFixed b = SkColorGetB(c0);
443
444 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
445 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
446 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
447
448 r = SkIntToFixed(r) + 0x8000;
449 g = SkIntToFixed(g) + 0x8000;
450 b = SkIntToFixed(b) + 0x8000;
451
452 do {
453 unsigned rr = r >> 16;
454 unsigned gg = g >> 16;
455 unsigned bb = b >> 16;
456 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000457 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000458 cache += 1;
459 r += dr;
460 g += dg;
461 b += db;
462 } while (--count != 0);
463}
464
reed@google.com55b8e8c2011-01-13 16:22:35 +0000465/*
466 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
467 * semantics of how we 2x2 dither 32->16
468 */
469static inline U8CPU dither_fixed_to_8(SkFixed n) {
470 n >>= 8;
471 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
472}
473
474/*
475 * For dithering with premultiply, we want to ceiling the alpha component,
476 * to ensure that it is always >= any color component.
477 */
478static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
479 n >>= 8;
480 return ((n << 1) - (n | (n >> 8))) >> 8;
481}
482
483void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
484 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000485 SkASSERT(count > 1);
486
reed@android.com1c12abe2009-07-02 15:01:02 +0000487 // need to apply paintAlpha to our two endpoints
488 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
489 SkFixed da;
490 {
491 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
492 da = SkIntToFixed(tmp - a) / (count - 1);
493 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000494
reed@android.com1c12abe2009-07-02 15:01:02 +0000495 SkFixed r = SkColorGetR(c0);
496 SkFixed g = SkColorGetG(c0);
497 SkFixed b = SkColorGetB(c0);
498 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
499 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
500 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000501
502 a = SkIntToFixed(a) + 0x8000;
503 r = SkIntToFixed(r) + 0x8000;
504 g = SkIntToFixed(g) + 0x8000;
505 b = SkIntToFixed(b) + 0x8000;
506
507 do {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000508 cache[0] = SkPreMultiplyARGB(a >> 16, r >> 16, g >> 16, b >> 16);
509 cache[kCache32Count] = SkPreMultiplyARGB(dither_ceil_fixed_to_8(a),
510 dither_fixed_to_8(r),
511 dither_fixed_to_8(g),
512 dither_fixed_to_8(b));
513 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000514 a += da;
515 r += dr;
516 g += dg;
517 b += db;
518 } while (--count != 0);
519}
520
reed@android.com41bccf52009-04-03 13:33:51 +0000521static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 SkASSERT((unsigned)x <= SK_Fixed1);
523 return x - (x >> 16);
524}
525
reed@android.com200645d2009-12-14 16:41:57 +0000526static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000527 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000528 if (6 == bits) {
529 return (x << 10) | (x << 4) | (x >> 2);
530 }
531 if (8 == bits) {
532 return (x << 8) | x;
533 }
534 sk_throw();
535 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000536}
537
reed@android.com41bccf52009-04-03 13:33:51 +0000538const uint16_t* Gradient_Shader::getCache16() {
539 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000540 // double the count for dither entries
541 const int entryCount = kCache16Count * 2;
542 const size_t allocSize = sizeof(uint16_t) * entryCount;
543
reed@android.com3c9b2a42009-08-27 19:28:37 +0000544 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000545 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000546 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000547 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000548 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000549 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000550 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000551 Rec* rec = fRecs;
552 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000553 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000554 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000555 SkASSERT(nextIndex < kCache16Count);
556
557 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000558 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000559 prevIndex = nextIndex;
560 }
561 SkASSERT(prevIndex == kCache16Count - 1);
562 }
563
reed@android.com41bccf52009-04-03 13:33:51 +0000564 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000565 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000566 uint16_t* linear = fCache16; // just computed linear data
567 uint16_t* mapped = fCache16Storage; // storage for mapped data
568 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000569 for (int i = 0; i < kCache16Count; i++) {
570 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000571 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000572 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573 }
574 sk_free(fCache16);
575 fCache16 = fCache16Storage;
576 }
577 }
578 return fCache16;
579}
580
reed@android.com41bccf52009-04-03 13:33:51 +0000581const SkPMColor* Gradient_Shader::getCache32() {
582 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000583 // double the count for dither entries
584 const int entryCount = kCache32Count * 2;
585 const size_t allocSize = sizeof(SkPMColor) * entryCount;
586
reed@google.comdc731fd2010-12-23 15:19:47 +0000587 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000588 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
589 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000590 }
591 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000592 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000593 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
594 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000595 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 Rec* rec = fRecs;
597 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000598 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
600 SkASSERT(nextIndex < kCache32Count);
601
602 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000603 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
604 fOrigColors[i],
605 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606 prevIndex = nextIndex;
607 }
608 SkASSERT(prevIndex == kCache32Count - 1);
609 }
610
reed@android.com41bccf52009-04-03 13:33:51 +0000611 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000612 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000613 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000614 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000615 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000617 for (int i = 0; i < kCache32Count; i++) {
618 int index = map->mapUnit16((i << 8) | i) >> 8;
619 mapped[i] = linear[index];
620 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000621 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000622 fCache32PixelRef->unref();
623 fCache32PixelRef = newPR;
624 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 }
626 }
627 return fCache32;
628}
629
reed@google.comdc731fd2010-12-23 15:19:47 +0000630/*
631 * Because our caller might rebuild the same (logically the same) gradient
632 * over and over, we'd like to return exactly the same "bitmap" if possible,
633 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
634 * To do that, we maintain a private cache of built-bitmaps, based on our
635 * colors and positions. Note: we don't try to flatten the fMapper, so if one
636 * is present, we skip the cache for now.
637 */
638void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) {
639 // don't have a way to put the mapper into our cache-key yet
640 if (fMapper) {
641 // force our cahce32pixelref to be built
642 (void)this->getCache32();
643 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
644 bitmap->setPixelRef(fCache32PixelRef);
645 return;
646 }
647
648 // build our key: [numColors + colors[] + {positions[]} ]
649 int count = 1 + fColorCount;
650 if (fColorCount > 2) {
651 count += fColorCount - 1; // fRecs[].fPos
652 }
653
654 SkAutoSTMalloc<16, int32_t> storage(count);
655 int32_t* buffer = storage.get();
656
657 *buffer++ = fColorCount;
658 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
659 buffer += fColorCount;
660 if (fColorCount > 2) {
661 for (int i = 1; i < fColorCount; i++) {
662 *buffer++ = fRecs[i].fPos;
663 }
664 }
665 SkASSERT(buffer - storage.get() == count);
666
667 ///////////////////////////////////
668
669 static SkMutex gMutex;
670 static SkBitmapCache* gCache;
671 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
672 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
673 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000674
reed@google.comdc731fd2010-12-23 15:19:47 +0000675 if (NULL == gCache) {
676 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
677 }
678 size_t size = count * sizeof(int32_t);
679
680 if (!gCache->find(storage.get(), size, bitmap)) {
681 // force our cahce32pixelref to be built
682 (void)this->getCache32();
683 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
684 bitmap->setPixelRef(fCache32PixelRef);
685
686 gCache->add(storage.get(), size, *bitmap);
687 }
688}
689
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690///////////////////////////////////////////////////////////////////////////
691
reed@android.com41bccf52009-04-03 13:33:51 +0000692static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693 SkVector vec = pts[1] - pts[0];
694 SkScalar mag = vec.length();
695 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
696
697 vec.scale(inv);
698 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
699 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
700 matrix->postScale(inv, inv);
701}
702
703///////////////////////////////////////////////////////////////////////////////
704
705class Linear_Gradient : public Gradient_Shader {
706public:
707 Linear_Gradient(const SkPoint pts[2],
708 const SkColor colors[], const SkScalar pos[], int colorCount,
709 SkShader::TileMode mode, SkUnitMapper* mapper)
710 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
711 {
712 pts_to_unit_matrix(pts, &fPtsToUnit);
713 }
reed@android.com9b46e772009-06-05 12:24:41 +0000714
reed@android.com5119bdb2009-06-12 21:27:03 +0000715 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
717 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000718 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*,
reed@google.comdc731fd2010-12-23 15:19:47 +0000719 TileMode*, SkScalar* twoPointRadialParams);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720
reed@google.com55b8e8c2011-01-13 16:22:35 +0000721 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 return SkNEW_ARGS(Linear_Gradient, (buffer));
723 }
724
725protected:
reed@google.comdc731fd2010-12-23 15:19:47 +0000726 Linear_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727 virtual Factory getFactory() { return CreateProc; }
728
729private:
730 typedef Gradient_Shader INHERITED;
731};
732
reed@android.com5119bdb2009-06-12 21:27:03 +0000733bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
734 const SkMatrix& matrix) {
735 if (!this->INHERITED::setContext(device, paint, matrix)) {
736 return false;
737 }
738
739 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
740 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000741 fFlags |= SkShader::kConstInY32_Flag;
742 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
743 // only claim this if we do have a 16bit mode (i.e. none of our
744 // colors have alpha), and if we are not dithering (which obviously
745 // is not const in Y).
746 fFlags |= SkShader::kConstInY16_Flag;
747 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000748 }
749 return true;
750}
751
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@android.comfc25abd2009-01-15 14:38:33 +0000753static inline bool no_need_for_clamp(int fx, int dx, int count)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000754{
755 SkASSERT(count > 0);
756 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
757}
758
759void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
760{
761 SkASSERT(count > 0);
762
763 SkPoint srcPt;
764 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
765 TileProc proc = fTileProc;
766 const SkPMColor* cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000767#ifdef USE_DITHER_32BIT_GRADIENT
768 int toggle = ((x ^ y) & 1) << kCache32Bits;
769 const int TOGGLE_MASK = (1 << kCache32Bits);
770#else
771 int toggle = 0;
772 const int TOGGLE_MASK = 0;
773#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774
reed@android.comc552a432009-06-12 20:02:50 +0000775 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000776 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
777 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
779
reed@android.comc552a432009-06-12 20:02:50 +0000780 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781 SkFixed dxStorage[1];
782 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
783 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000784 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
786 dx = SkScalarToFixed(fDstToIndex.getScaleX());
787 }
788
reed@android.comc552a432009-06-12 20:02:50 +0000789 if (SkFixedNearlyZero(dx)) {
790 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791 unsigned fi = proc(fx);
792 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000793 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000795 } else if (proc == clamp_tileproc) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000796 do {
797 unsigned fi = SkClampMax(fx >> 8, 0xFF);
798 SkASSERT(fi <= 0xFF);
799 fx += dx;
800 *dstC++ = cache[toggle + fi];
801 toggle ^= TOGGLE_MASK;
802 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000803 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 do {
805 unsigned fi = mirror_8bits(fx >> 8);
806 SkASSERT(fi <= 0xFF);
807 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000808 *dstC++ = cache[toggle + fi];
809 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000811 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 SkASSERT(proc == repeat_tileproc);
813 do {
814 unsigned fi = repeat_8bits(fx >> 8);
815 SkASSERT(fi <= 0xFF);
816 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000817 *dstC++ = cache[toggle + fi];
818 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819 } while (--count != 0);
820 }
reed@android.comc552a432009-06-12 20:02:50 +0000821 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 SkScalar dstX = SkIntToScalar(x);
823 SkScalar dstY = SkIntToScalar(y);
824 do {
825 dstProc(fDstToIndex, dstX, dstY, &srcPt);
826 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
827 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000828 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
829 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830 dstX += SK_Scalar1;
831 } while (--count != 0);
832 }
833}
834
reed@google.com55b8e8c2011-01-13 16:22:35 +0000835SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000836 SkMatrix* matrix,
837 TileMode xy[],
838 SkScalar* twoPointRadialParams) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000840 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 }
842 if (matrix) {
843 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
844 matrix->preConcat(fPtsToUnit);
845 }
846 if (xy) {
847 xy[0] = fTileMode;
848 xy[1] = kClamp_TileMode;
849 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000850 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851}
852
reed@android.com3c9b2a42009-08-27 19:28:37 +0000853static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
854 int count) {
855 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 *dst++ = value;
857 count -= 1;
858 SkTSwap(value, other);
859 }
860
861 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000862
reed@android.com3c9b2a42009-08-27 19:28:37 +0000863 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000865 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000866}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000867
868void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
869{
870 SkASSERT(count > 0);
871
872 SkPoint srcPt;
873 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
874 TileProc proc = fTileProc;
875 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000876 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000878 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000879 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
880 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
882
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000883 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000884 SkFixed dxStorage[1];
885 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
886 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000887 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
889 dx = SkScalarToFixed(fDstToIndex.getScaleX());
890 }
891
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000892 if (SkFixedNearlyZero(dx)) {
893 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +0000894 unsigned fi = proc(fx) >> kCache16Shift;
895 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000896 dither_memset16(dstC, cache[toggle + fi], cache[(toggle ^ (1 << kCache16Bits)) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000897 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000898 do {
reed@android.com512a8762009-12-14 15:25:36 +0000899 unsigned fi = SkClampMax(fx >> kCache16Shift, kCache16Mask);
900 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000901 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000902 *dstC++ = cache[toggle + fi];
903 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000905 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000906 do {
reed@android.com200645d2009-12-14 16:41:57 +0000907 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +0000908 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000909 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000910 *dstC++ = cache[toggle + fi];
911 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000912 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000913 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000914 SkASSERT(proc == repeat_tileproc);
915 do {
reed@android.com200645d2009-12-14 16:41:57 +0000916 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +0000917 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000918 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000919 *dstC++ = cache[toggle + fi];
920 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000921 } while (--count != 0);
922 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000923 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000924 SkScalar dstX = SkIntToScalar(x);
925 SkScalar dstY = SkIntToScalar(y);
926 do {
927 dstProc(fDstToIndex, dstX, dstY, &srcPt);
928 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
929 SkASSERT(fi <= 0xFFFF);
930
reed@android.com512a8762009-12-14 15:25:36 +0000931 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000932 *dstC++ = cache[toggle + index];
933 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000934
935 dstX += SK_Scalar1;
936 } while (--count != 0);
937 }
938}
939
940///////////////////////////////////////////////////////////////////////////////
941
942#define kSQRT_TABLE_BITS 11
943#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
944
945#include "SkRadialGradient_Table.h"
946
947#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
948
949#include <stdio.h>
950
951void SkRadialGradient_BuildTable()
952{
953 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
954
955 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
956 SkASSERT(file);
957 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
958
959 for (int i = 0; i < kSQRT_TABLE_SIZE; i++)
960 {
961 if ((i & 15) == 0)
962 ::fprintf(file, "\t");
963
964 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
965
966 ::fprintf(file, "0x%02X", value);
967 if (i < kSQRT_TABLE_SIZE-1)
968 ::fprintf(file, ", ");
969 if ((i & 15) == 15)
970 ::fprintf(file, "\n");
971 }
972 ::fprintf(file, "};\n");
973 ::fclose(file);
974}
975
976#endif
977
978
979static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius, SkMatrix* matrix)
980{
981 SkScalar inv = SkScalarInvert(radius);
982
983 matrix->setTranslate(-center.fX, -center.fY);
984 matrix->postScale(inv, inv);
985}
986
987class Radial_Gradient : public Gradient_Shader {
988public:
989 Radial_Gradient(const SkPoint& center, SkScalar radius,
990 const SkColor colors[], const SkScalar pos[], int colorCount,
991 SkShader::TileMode mode, SkUnitMapper* mapper)
992 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
993 {
994 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
995 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
996
997 rad_to_unit_matrix(center, radius, &fPtsToUnit);
998 }
999 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1000 {
1001 SkASSERT(count > 0);
1002
1003 SkPoint srcPt;
1004 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1005 TileProc proc = fTileProc;
1006 const SkPMColor* cache = this->getCache32();
1007
1008 if (fDstToIndexClass != kPerspective_MatrixClass)
1009 {
reed@google.comdc731fd2010-12-23 15:19:47 +00001010 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1011 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1013 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1014
1015 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1016 {
1017 SkFixed storage[2];
1018 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1019 dx = storage[0];
1020 dy = storage[1];
1021 }
1022 else
1023 {
1024 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1025 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1026 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1027 }
1028
1029 if (proc == clamp_tileproc)
1030 {
1031 const uint8_t* sqrt_table = gSqrt8Table;
1032 fx >>= 1;
1033 dx >>= 1;
1034 fy >>= 1;
1035 dy >>= 1;
1036 do {
1037 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1038 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1039 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1040 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1041 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1042 fx += dx;
1043 fy += dy;
1044 } while (--count != 0);
1045 }
1046 else if (proc == mirror_tileproc)
1047 {
1048 do {
1049 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1050 unsigned fi = mirror_tileproc(dist);
1051 SkASSERT(fi <= 0xFFFF);
1052 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1053 fx += dx;
1054 fy += dy;
1055 } while (--count != 0);
1056 }
1057 else
1058 {
1059 SkASSERT(proc == repeat_tileproc);
1060 do {
1061 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1062 unsigned fi = repeat_tileproc(dist);
1063 SkASSERT(fi <= 0xFFFF);
1064 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1065 fx += dx;
1066 fy += dy;
1067 } while (--count != 0);
1068 }
1069 }
1070 else // perspective case
1071 {
1072 SkScalar dstX = SkIntToScalar(x);
1073 SkScalar dstY = SkIntToScalar(y);
1074 do {
1075 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1076 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1077 SkASSERT(fi <= 0xFFFF);
1078 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1079 dstX += SK_Scalar1;
1080 } while (--count != 0);
1081 }
1082 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001083
1084 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085 SkASSERT(count > 0);
1086
1087 SkPoint srcPt;
1088 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1089 TileProc proc = fTileProc;
1090 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001091 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001092
reed@android.com3c9b2a42009-08-27 19:28:37 +00001093 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001094 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1095 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001096 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1097 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1098
reed@android.com3c9b2a42009-08-27 19:28:37 +00001099 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001100 SkFixed storage[2];
1101 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1102 dx = storage[0];
1103 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001104 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001105 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1106 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1107 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1108 }
1109
reed@android.com3c9b2a42009-08-27 19:28:37 +00001110 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 const uint8_t* sqrt_table = gSqrt8Table;
1112
1113 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1114 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1115 precision, but that appears to be visually OK. If we decide this is OK for
1116 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1117 to avoid having to do these extra shifts each time.
1118 */
1119 fx >>= 1;
1120 dx >>= 1;
1121 fy >>= 1;
1122 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001123 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 +00001124 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1125 fy *= fy;
1126 do {
1127 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1128 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1129 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1130 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001131 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1132 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001133 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001134 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001135 do {
1136 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1137 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1138 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1139 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1140 fx += dx;
1141 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001142 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1143 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001144 } while (--count != 0);
1145 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001146 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001147 do {
1148 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1149 unsigned fi = mirror_tileproc(dist);
1150 SkASSERT(fi <= 0xFFFF);
1151 fx += dx;
1152 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001153 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1154 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001156 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001157 SkASSERT(proc == repeat_tileproc);
1158 do {
1159 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1160 unsigned fi = repeat_tileproc(dist);
1161 SkASSERT(fi <= 0xFFFF);
1162 fx += dx;
1163 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001164 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1165 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001166 } while (--count != 0);
1167 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001168 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001169 SkScalar dstX = SkIntToScalar(x);
1170 SkScalar dstY = SkIntToScalar(y);
1171 do {
1172 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1173 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1174 SkASSERT(fi <= 0xFFFF);
1175
1176 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001177 *dstC++ = cache[toggle + index];
1178 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001179
1180 dstX += SK_Scalar1;
1181 } while (--count != 0);
1182 }
1183 }
1184
reed@google.com55b8e8c2011-01-13 16:22:35 +00001185 virtual BitmapType asABitmap(SkBitmap* bitmap,
1186 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001187 TileMode* xy,
1188 SkScalar* twoPointRadialParams) {
1189 if (bitmap) {
1190 this->commonAsABitmap(bitmap);
1191 }
1192 if (matrix) {
1193 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1194 matrix->preConcat(fPtsToUnit);
1195 }
1196 if (xy) {
1197 xy[0] = fTileMode;
1198 xy[1] = kClamp_TileMode;
1199 }
1200 return kRadial_BitmapType;
1201 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001202
1203 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 return SkNEW_ARGS(Radial_Gradient, (buffer));
1205 }
1206
1207protected:
1208 Radial_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {};
1209 virtual Factory getFactory() { return CreateProc; }
1210
1211private:
1212 typedef Gradient_Shader INHERITED;
1213};
1214
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001215/* Two-point radial gradients are specified by two circles, each with a center
1216 point and radius. The gradient can be considered to be a series of
1217 concentric circles, with the color interpolated from the start circle
1218 (at t=0) to the end circle (at t=1).
1219
1220 For each point (x, y) in the span, we want to find the
1221 interpolated circle that intersects that point. The center
1222 of the desired circle (Cx, Cy) falls at some distance t
1223 along the line segment between the start point (Sx, Sy) and
1224 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001225
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001226 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1227 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001228
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001229 The radius of the desired circle (r) is also a linear interpolation t
1230 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001231
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001232 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001233
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001234 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001235
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001236 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001237
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001238 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001239
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001240 (x - ((1 - t) * Sx + t * Ex))^2
1241 + (y - ((1 - t) * Sy + t * Ey))^2
1242 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001243
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001244 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001245
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001246 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1247 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1248 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001249
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001250 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1251
1252 [Dx^2 + Dy^2 - Dr^2)] * t^2
1253 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1254 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001255
1256 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001257 possible circles on which the point may fall. Solving for t yields
1258 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001259
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001260 If a<0, the start circle is entirely contained in the
1261 end circle, and one of the roots will be <0 or >1 (off the line
1262 segment). If a>0, the start circle falls at least partially
1263 outside the end circle (or vice versa), and the gradient
1264 defines a "tube" where a point may be on one circle (on the
1265 inside of the tube) or the other (outside of the tube). We choose
1266 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001267
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001268 In order to keep the math to within the limits of fixed point,
1269 we divide the entire quadratic by Dr^2, and replace
1270 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001271
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001272 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1273 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1274 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001275
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001276 (x' and y' are computed by appending the subtract and scale to the
1277 fDstToIndex matrix in the constructor).
1278
1279 Since the 'A' component of the quadratic is independent of x' and y', it
1280 is precomputed in the constructor. Since the 'B' component is linear in
1281 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001282 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001283 a perspective projection), it must be computed in the loop.
1284
1285*/
1286
1287static inline SkFixed two_point_radial(SkFixed b, SkFixed fx, SkFixed fy, SkFixed sr2d2, SkFixed foura, SkFixed oneOverTwoA, bool posRoot) {
1288 SkFixed c = SkFixedSquare(fx) + SkFixedSquare(fy) - sr2d2;
1289 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1290 if (discrim < 0) {
1291 discrim = -discrim;
1292 }
1293 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1294 if (posRoot) {
1295 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1296 } else {
1297 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1298 }
1299}
1300
1301class Two_Point_Radial_Gradient : public Gradient_Shader {
1302public:
1303 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1304 const SkPoint& end, SkScalar endRadius,
1305 const SkColor colors[], const SkScalar pos[],
1306 int colorCount, SkShader::TileMode mode,
1307 SkUnitMapper* mapper)
1308 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
1309 {
1310 fDiff = start - end;
1311 fDiffRadius = endRadius - startRadius;
1312 SkScalar inv = SkScalarInvert(fDiffRadius);
1313 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1314 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1315 fStartRadius = SkScalarMul(startRadius, inv);
1316 fSr2D2 = SkScalarSquare(fStartRadius);
1317 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1318 fOneOverTwoA = SkScalarInvert(fA * 2);
1319
1320 fPtsToUnit.setTranslate(-start.fX, -start.fY);
1321 fPtsToUnit.postScale(inv, inv);
1322 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001323
1324 virtual BitmapType asABitmap(SkBitmap* bitmap,
1325 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001326 TileMode* xy,
1327 SkScalar* twoPointRadialParams) {
1328 if (bitmap) {
1329 this->commonAsABitmap(bitmap);
1330 }
1331 SkScalar diffL = 0; // just to avoid gcc warning
1332 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001333 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001334 SkScalarSquare(fDiff.fY));
1335 }
1336 if (matrix) {
1337 SkScalar invDiffL = SkScalarInvert(diffL);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001338 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
reed@google.comdc731fd2010-12-23 15:19:47 +00001339 SkScalarMul(invDiffL, fDiff.fX));
1340 matrix->preConcat(fPtsToUnit);
1341 }
1342 if (xy) {
1343 xy[0] = fTileMode;
1344 xy[1] = kClamp_TileMode;
1345 }
1346 if (NULL != twoPointRadialParams) {
1347 twoPointRadialParams[0] = diffL;
1348 twoPointRadialParams[1] = fStartRadius;
1349 twoPointRadialParams[2] = fDiffRadius;
1350 }
1351 return kTwoPointRadial_BitmapType;
1352 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001353
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001354 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1355 {
1356 SkASSERT(count > 0);
1357
1358 // Zero difference between radii: fill with transparent black.
1359 if (fDiffRadius == 0) {
1360 sk_bzero(dstC, count * sizeof(*dstC));
1361 return;
1362 }
1363 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1364 TileProc proc = fTileProc;
1365 const SkPMColor* cache = this->getCache32();
1366 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1367 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1368 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1369 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1370 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1371 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1372 bool posRoot = fDiffRadius < 0;
1373 if (fDstToIndexClass != kPerspective_MatrixClass)
1374 {
1375 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001376 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1377 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001378 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1379 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1380
1381 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1382 {
1383 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1384 }
1385 else
1386 {
1387 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1388 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1389 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1390 }
1391 SkFixed b = (SkFixedMul(diffx, fx) +
1392 SkFixedMul(diffy, fy) - startRadius) << 1;
1393 SkFixed db = (SkFixedMul(diffx, dx) +
1394 SkFixedMul(diffy, dy)) << 1;
1395 if (proc == clamp_tileproc)
1396 {
1397 for (; count > 0; --count) {
1398 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1399 SkFixed index = SkClampMax(t, 0xFFFF);
1400 SkASSERT(index <= 0xFFFF);
1401 *dstC++ = cache[index >> (16 - kCache32Bits)];
1402 fx += dx;
1403 fy += dy;
1404 b += db;
1405 }
1406 }
1407 else if (proc == mirror_tileproc)
1408 {
1409 for (; count > 0; --count) {
1410 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1411 SkFixed index = mirror_tileproc(t);
1412 SkASSERT(index <= 0xFFFF);
1413 *dstC++ = cache[index >> (16 - kCache32Bits)];
1414 fx += dx;
1415 fy += dy;
1416 b += db;
1417 }
1418 }
1419 else
1420 {
1421 SkASSERT(proc == repeat_tileproc);
1422 for (; count > 0; --count) {
1423 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1424 SkFixed index = repeat_tileproc(t);
1425 SkASSERT(index <= 0xFFFF);
1426 *dstC++ = cache[index >> (16 - kCache32Bits)];
1427 fx += dx;
1428 fy += dy;
1429 b += db;
1430 }
1431 }
1432 }
1433 else // perspective case
1434 {
reed@android.com6c59a172009-09-22 20:24:05 +00001435 SkScalar dstX = SkIntToScalar(x);
1436 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001437 for (; count > 0; --count) {
1438 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001439 dstProc(fDstToIndex, dstX, dstY, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001440 SkFixed fx = SkScalarToFixed(srcPt.fX);
1441 SkFixed fy = SkScalarToFixed(srcPt.fY);
1442 SkFixed b = (SkFixedMul(diffx, fx) +
1443 SkFixedMul(diffy, fy) - startRadius) << 1;
1444 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1445 SkFixed index = proc(t);
1446 SkASSERT(index <= 0xFFFF);
1447 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001448 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001449 }
1450 }
1451 }
1452
reed@android.com6c59a172009-09-22 20:24:05 +00001453 virtual bool setContext(const SkBitmap& device,
1454 const SkPaint& paint,
1455 const SkMatrix& matrix) {
1456 if (!this->INHERITED::setContext(device, paint, matrix)) {
1457 return false;
1458 }
1459
1460 // we don't have a span16 proc
1461 fFlags &= ~kHasSpan16_Flag;
1462 return true;
1463 }
1464
reed@google.com55b8e8c2011-01-13 16:22:35 +00001465 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001466 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1467 }
1468
reed@android.combcfc7332009-11-10 16:19:39 +00001469 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1470 this->INHERITED::flatten(buffer);
1471 buffer.writeScalar(fDiff.fX);
1472 buffer.writeScalar(fDiff.fY);
1473 buffer.writeScalar(fStartRadius);
1474 buffer.writeScalar(fDiffRadius);
1475 buffer.writeScalar(fSr2D2);
1476 buffer.writeScalar(fA);
1477 buffer.writeScalar(fOneOverTwoA);
1478 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001479
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001480protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001481 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
1482 : Gradient_Shader(buffer) {
1483 fDiff.fX = buffer.readScalar();
1484 fDiff.fY = buffer.readScalar();
1485 fStartRadius = buffer.readScalar();
1486 fDiffRadius = buffer.readScalar();
1487 fSr2D2 = buffer.readScalar();
1488 fA = buffer.readScalar();
1489 fOneOverTwoA = buffer.readScalar();
1490 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001491 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001492
1493private:
1494 typedef Gradient_Shader INHERITED;
1495 SkPoint fDiff;
1496 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
1497};
1498
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499///////////////////////////////////////////////////////////////////////////////
1500
1501class Sweep_Gradient : public Gradient_Shader {
1502public:
1503 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1504 const SkScalar pos[], int count, SkUnitMapper* mapper)
1505 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper)
1506 {
1507 fPtsToUnit.setTranslate(-cx, -cy);
1508 }
1509 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1510 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001511
1512 virtual BitmapType asABitmap(SkBitmap* bitmap,
1513 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001514 TileMode* xy,
1515 SkScalar* twoPointRadialParams) {
1516 if (bitmap) {
1517 this->commonAsABitmap(bitmap);
1518 }
1519 if (matrix) {
1520 *matrix = fPtsToUnit;
1521 }
1522 if (xy) {
1523 xy[0] = fTileMode;
1524 xy[1] = kClamp_TileMode;
1525 }
1526 return kSweep_BitmapType;
1527 }
1528
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1530 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1531 }
1532
1533protected:
1534 Sweep_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535 virtual Factory getFactory() { return CreateProc; }
1536
1537private:
1538 typedef Gradient_Shader INHERITED;
1539};
1540
1541#ifdef COMPUTE_SWEEP_TABLE
1542#define PI 3.14159265
1543static bool gSweepTableReady;
1544static uint8_t gSweepTable[65];
1545
1546/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1547 We scale the results to [0..32]
1548*/
1549static const uint8_t* build_sweep_table()
1550{
1551 if (!gSweepTableReady)
1552 {
1553 const int N = 65;
1554 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001555
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556 for (int i = 0; i < N; i++)
1557 {
1558 double arg = i / DENOM;
1559 double v = atan(arg);
1560 int iv = (int)round(v * DENOM * 2 / PI);
1561// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1562 printf("%d, ", iv);
1563 gSweepTable[i] = iv;
1564 }
1565 gSweepTableReady = true;
1566 }
1567 return gSweepTable;
1568}
1569#else
1570static const uint8_t gSweepTable[] = {
1571 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1572 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1573 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1574 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1575 32
1576};
1577static const uint8_t* build_sweep_table() { return gSweepTable; }
1578#endif
1579
1580// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1581// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1582// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1583
1584//unsigned div_64(int numer, int denom);
1585static unsigned div_64(int numer, int denom)
1586{
1587 SkASSERT(numer <= denom);
1588 SkASSERT(numer > 0);
1589 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001590
reed@android.com8a1c16f2008-12-17 15:59:43 +00001591 int nbits = SkCLZ(numer);
1592 int dbits = SkCLZ(denom);
1593 int bits = 6 - nbits + dbits;
1594 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001595
reed@android.com8a1c16f2008-12-17 15:59:43 +00001596 if (bits < 0) // detect underflow
1597 return 0;
1598
1599 denom <<= dbits - 1;
1600 numer <<= nbits - 1;
1601
1602 unsigned result = 0;
1603
1604 // do the first one
1605 if ((numer -= denom) >= 0)
1606 result = 1;
1607 else
1608 numer += denom;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001609
reed@android.com8a1c16f2008-12-17 15:59:43 +00001610 // Now fall into our switch statement if there are more bits to compute
1611 if (bits > 0)
1612 {
1613 // make room for the rest of the answer bits
1614 result <<= bits;
1615 switch (bits) {
1616 case 6:
1617 if ((numer = (numer << 1) - denom) >= 0)
1618 result |= 32;
1619 else
1620 numer += denom;
1621 case 5:
1622 if ((numer = (numer << 1) - denom) >= 0)
1623 result |= 16;
1624 else
1625 numer += denom;
1626 case 4:
1627 if ((numer = (numer << 1) - denom) >= 0)
1628 result |= 8;
1629 else
1630 numer += denom;
1631 case 3:
1632 if ((numer = (numer << 1) - denom) >= 0)
1633 result |= 4;
1634 else
1635 numer += denom;
1636 case 2:
1637 if ((numer = (numer << 1) - denom) >= 0)
1638 result |= 2;
1639 else
1640 numer += denom;
1641 case 1:
1642 default: // not strictly need, but makes GCC make better ARM code
1643 if ((numer = (numer << 1) - denom) >= 0)
1644 result |= 1;
1645 else
1646 numer += denom;
1647 }
1648 }
1649 return result;
1650}
1651
1652// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1653static unsigned atan_0_90(SkFixed y, SkFixed x)
1654{
1655#ifdef SK_DEBUG
1656 {
1657 static bool gOnce;
1658 if (!gOnce)
1659 {
1660 gOnce = true;
1661 SkASSERT(div_64(55, 55) == 64);
1662 SkASSERT(div_64(128, 256) == 32);
1663 SkASSERT(div_64(2326528, 4685824) == 31);
1664 SkASSERT(div_64(753664, 5210112) == 9);
1665 SkASSERT(div_64(229376, 4882432) == 3);
1666 SkASSERT(div_64(2, 64) == 2);
1667 SkASSERT(div_64(1, 64) == 1);
1668 // test that we handle underflow correctly
1669 SkASSERT(div_64(12345, 0x54321234) == 0);
1670 }
1671 }
1672#endif
1673
1674 SkASSERT(y > 0 && x > 0);
1675 const uint8_t* table = build_sweep_table();
1676
1677 unsigned result;
1678 bool swap = (x < y);
1679 if (swap)
1680 {
1681 // first part of the atan(v) = PI/2 - atan(1/v) identity
1682 // since our div_64 and table want v <= 1, where v = y/x
1683 SkTSwap<SkFixed>(x, y);
1684 }
1685
1686 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001687
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688#ifdef SK_DEBUG
1689 {
1690 unsigned result2 = SkDivBits(y, x, 6);
1691 SkASSERT(result2 == result ||
1692 (result == 1 && result2 == 0));
1693 }
1694#endif
1695
1696 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1697 result = table[result];
1698
1699 if (swap)
1700 {
1701 // complete the atan(v) = PI/2 - atan(1/v) identity
1702 result = 64 - result;
1703 // pin to 63
1704 result -= result >> 6;
1705 }
1706
1707 SkASSERT(result <= 63);
1708 return result;
1709}
1710
1711// returns angle in a circle [0..2PI) -> [0..255]
1712static unsigned SkATan2_255(SkFixed y, SkFixed x)
1713{
1714 if (x == 0)
1715 {
1716 if (y == 0)
1717 return 0;
1718 return y < 0 ? 192 : 64;
1719 }
1720 if (y == 0)
1721 return x < 0 ? 128 : 0;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001722
reed@android.com8a1c16f2008-12-17 15:59:43 +00001723 /* Find the right quadrant for x,y
1724 Since atan_0_90 only handles the first quadrant, we rotate x,y
1725 appropriately before calling it, and then add the right amount
1726 to account for the real quadrant.
1727 quadrant 0 : add 0 | x > 0 && y > 0
1728 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1729 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1730 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001731
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732 map x<0 to (1 << 6)
1733 map y<0 to (3 << 6)
1734 add = map_x ^ map_y
1735 */
1736 int xsign = x >> 31;
1737 int ysign = y >> 31;
1738 int add = ((-xsign) ^ (ysign & 3)) << 6;
1739
1740#ifdef SK_DEBUG
1741 if (0 == add)
1742 SkASSERT(x > 0 && y > 0);
1743 else if (64 == add)
1744 SkASSERT(x < 0 && y > 0);
1745 else if (128 == add)
1746 SkASSERT(x < 0 && y < 0);
1747 else if (192 == add)
1748 SkASSERT(x > 0 && y < 0);
1749 else
1750 SkASSERT(!"bad value for add");
1751#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00001752
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1754 where we need to rotate x,y by 90 or -90
1755 */
1756 x = (x ^ xsign) - xsign;
1757 y = (y ^ ysign) - ysign;
1758 if (add & 64) // quads 1 or 3 need to swap x,y
1759 SkTSwap<SkFixed>(x, y);
1760
1761 unsigned result = add + atan_0_90(y, x);
1762 SkASSERT(result < 256);
1763 return result;
1764}
1765
1766void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1767{
1768 SkMatrix::MapXYProc proc = fDstToIndexProc;
1769 const SkMatrix& matrix = fDstToIndex;
1770 const SkPMColor* cache = this->getCache32();
1771 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001772
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 if (fDstToIndexClass != kPerspective_MatrixClass)
1774 {
1775 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1776 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1777 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1778 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001779
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1781 {
1782 SkFixed storage[2];
1783 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1784 &storage[0], &storage[1]);
1785 dx = storage[0];
1786 dy = storage[1];
1787 }
1788 else
1789 {
1790 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1791 dx = SkScalarToFixed(matrix.getScaleX());
1792 dy = SkScalarToFixed(matrix.getSkewY());
1793 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001794
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 for (; count > 0; --count)
1796 {
1797 *dstC++ = cache[SkATan2_255(fy, fx)];
1798 fx += dx;
1799 fy += dy;
1800 }
1801 }
1802 else // perspective case
1803 {
1804 for (int stop = x + count; x < stop; x++)
1805 {
1806 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1807 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001808
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1810 SkScalarToFixed(srcPt.fX));
1811 *dstC++ = cache[index];
1812 }
1813 }
1814}
1815
1816void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1817{
1818 SkMatrix::MapXYProc proc = fDstToIndexProc;
1819 const SkMatrix& matrix = fDstToIndex;
1820 const uint16_t* cache = this->getCache16();
1821 int toggle = ((x ^ y) & 1) << kCache16Bits;
1822 SkPoint srcPt;
1823
1824 if (fDstToIndexClass != kPerspective_MatrixClass)
1825 {
1826 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1827 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1828 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1829 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001830
reed@android.com8a1c16f2008-12-17 15:59:43 +00001831 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1832 {
1833 SkFixed storage[2];
1834 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1835 &storage[0], &storage[1]);
1836 dx = storage[0];
1837 dy = storage[1];
1838 }
1839 else
1840 {
1841 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1842 dx = SkScalarToFixed(matrix.getScaleX());
1843 dy = SkScalarToFixed(matrix.getSkewY());
1844 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001845
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 for (; count > 0; --count)
1847 {
1848 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1849 *dstC++ = cache[toggle + index];
1850 toggle ^= (1 << kCache16Bits);
1851 fx += dx;
1852 fy += dy;
1853 }
1854 }
1855 else // perspective case
1856 {
1857 for (int stop = x + count; x < stop; x++)
1858 {
1859 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1860 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001861
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1863 SkScalarToFixed(srcPt.fX));
1864 index >>= (8 - kCache16Bits);
1865 *dstC++ = cache[toggle + index];
1866 toggle ^= (1 << kCache16Bits);
1867 }
1868 }
1869}
1870
1871///////////////////////////////////////////////////////////////////////////
1872///////////////////////////////////////////////////////////////////////////
1873
1874// assumes colors is SkColor* and pos is SkScalar*
1875#define EXPAND_1_COLOR(count) \
1876 SkColor tmp[2]; \
1877 do { \
1878 if (1 == count) { \
1879 tmp[0] = tmp[1] = colors[0]; \
1880 colors = tmp; \
1881 pos = NULL; \
1882 count = 2; \
1883 } \
1884 } while (0)
1885
1886SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
1887 const SkColor colors[], const SkScalar pos[], int colorCount,
1888 SkShader::TileMode mode, SkUnitMapper* mapper)
1889{
1890 if (NULL == pts || NULL == colors || colorCount < 1) {
1891 return NULL;
1892 }
1893 EXPAND_1_COLOR(colorCount);
1894
reed@android.comab840b82009-07-01 17:00:03 +00001895 return SkNEW_ARGS(Linear_Gradient,
1896 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897}
1898
1899SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
1900 const SkColor colors[], const SkScalar pos[], int colorCount,
1901 SkShader::TileMode mode, SkUnitMapper* mapper)
1902{
1903 if (radius <= 0 || NULL == colors || colorCount < 1) {
1904 return NULL;
1905 }
1906 EXPAND_1_COLOR(colorCount);
1907
reed@android.comab840b82009-07-01 17:00:03 +00001908 return SkNEW_ARGS(Radial_Gradient,
1909 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910}
1911
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001912SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
1913 SkScalar startRadius,
1914 const SkPoint& end,
1915 SkScalar endRadius,
1916 const SkColor colors[],
1917 const SkScalar pos[],
1918 int colorCount,
1919 SkShader::TileMode mode,
1920 SkUnitMapper* mapper)
1921{
1922 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
1923 return NULL;
1924 }
1925 EXPAND_1_COLOR(colorCount);
1926
1927 return SkNEW_ARGS(Two_Point_Radial_Gradient,
1928 (start, startRadius, end, endRadius, colors, pos, colorCount, mode, mapper));
1929}
1930
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
1932 const SkColor colors[],
1933 const SkScalar pos[],
1934 int count, SkUnitMapper* mapper)
1935{
1936 if (NULL == colors || count < 1) {
1937 return NULL;
1938 }
1939 EXPAND_1_COLOR(count);
1940
1941 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
1942}
1943
1944static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
1945 Linear_Gradient::CreateProc);
1946
1947static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
1948 Radial_Gradient::CreateProc);
1949
1950static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
1951 Sweep_Gradient::CreateProc);
1952