blob: ea90e267eed0055cb4e321c87d828f2eae0f43ba [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/* libs/graphics/effects/SkGradientShader.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
reed@google.com55b8e8c2011-01-13 16:22:35 +00005** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
reed@android.com8a1c16f2008-12-17 15:59:43 +00008**
reed@google.com55b8e8c2011-01-13 16:22:35 +00009** http://www.apache.org/licenses/LICENSE-2.0
reed@android.com8a1c16f2008-12-17 15:59:43 +000010**
reed@google.com55b8e8c2011-01-13 16:22:35 +000011** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
reed@android.com8a1c16f2008-12-17 15:59:43 +000015** limitations under the License.
16*/
17
18#include "SkGradientShader.h"
19#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000020#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021#include "SkUnitMapper.h"
22#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000023#include "SkTemplates.h"
24#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000025
reed@google.com9c7443d2011-01-17 18:46:37 +000026#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
27 #define USE_DITHER_32BIT_GRADIENT
28#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +000029
reed@google.com5eb158d2011-04-15 15:50:34 +000030static void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
31 int count) {
32 if (count > 0) {
33 if (v0 == v1) {
34 sk_memset32(dst, v0, count);
35 } else {
36 int pairs = count >> 1;
37 for (int i = 0; i < pairs; i++) {
38 *dst++ = v0;
39 *dst++ = v1;
40 }
41 if (count & 1) {
42 *dst = v0;
43 }
44 }
45 }
46}
47
reed@google.com61eb0402011-04-15 12:11:12 +000048///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000049
50typedef SkFixed (*TileProc)(SkFixed);
51
reed@android.com41bccf52009-04-03 13:33:51 +000052static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000053 return SkClampMax(x, 0xFFFF);
54}
55
reed@android.com41bccf52009-04-03 13:33:51 +000056static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 return x & 0xFFFF;
58}
59
reed@android.com41bccf52009-04-03 13:33:51 +000060static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 int s = x << 15 >> 31;
62 return (x ^ s) & 0xFFFF;
63}
64
65static const TileProc gTileProcs[] = {
66 clamp_tileproc,
67 repeat_tileproc,
68 mirror_tileproc
69};
70
reed@google.com61eb0402011-04-15 12:11:12 +000071///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000072
reed@android.com200645d2009-12-14 16:41:57 +000073static inline int repeat_bits(int x, const int bits) {
74 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000075}
76
reed@android.com200645d2009-12-14 16:41:57 +000077static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000078#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000079 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000081 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082#else
reed@android.com200645d2009-12-14 16:41:57 +000083 int s = x << (31 - bits) >> 31;
84 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000085#endif
86}
87
reed@android.com41bccf52009-04-03 13:33:51 +000088static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 return x & 0xFF;
90}
91
reed@android.com41bccf52009-04-03 13:33:51 +000092static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000093#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000094 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +000096 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000097 return x & 255;
98#else
99 int s = x << 23 >> 31;
100 return (x ^ s) & 0xFF;
101#endif
102}
103
reed@google.com61eb0402011-04-15 12:11:12 +0000104///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105
106class Gradient_Shader : public SkShader {
107public:
108 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000109 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 virtual ~Gradient_Shader();
111
112 // overrides
113 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
114 virtual uint32_t getFlags() { return fFlags; }
115
116protected:
117 Gradient_Shader(SkFlattenableReadBuffer& );
118 SkUnitMapper* fMapper;
119 SkMatrix fPtsToUnit; // set by subclass
120 SkMatrix fDstToIndex;
121 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 TileMode fTileMode;
123 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000124 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 uint8_t fDstToIndexClass;
126 uint8_t fFlags;
127
128 struct Rec {
129 SkFixed fPos; // 0...1
130 uint32_t fScale; // (1 << 24) / range
131 };
132 Rec* fRecs;
133
134 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000135 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000136 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000137 kCache16Mask = kCache16Count - 1,
138 kCache16Shift = 16 - kCache16Bits,
139
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 kCache32Bits = 8, // pretty much should always be 8
141 kCache32Count = 1 << kCache32Bits
142 };
143 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000144 const uint16_t* getCache16() const;
145 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000146
reed@google.com7c2f27d2011-03-07 19:29:00 +0000147 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000148 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000149
reed@android.com8a1c16f2008-12-17 15:59:43 +0000150private:
151 enum {
152 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
153
reed@android.com1c12abe2009-07-02 15:01:02 +0000154 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155 };
156 SkColor fStorage[(kStorageSize + 3) >> 2];
157 SkColor* fOrigColors;
158
reed@google.com7c2f27d2011-03-07 19:29:00 +0000159 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
160 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161
reed@google.com7c2f27d2011-03-07 19:29:00 +0000162 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
163 mutable SkMallocPixelRef* fCache32PixelRef;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000164 unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
165
reed@android.com512a8762009-12-14 15:25:36 +0000166 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000167 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
168 U8CPU alpha);
reed@android.com512a8762009-12-14 15:25:36 +0000169
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170 typedef SkShader INHERITED;
171};
172
reed@android.com41bccf52009-04-03 13:33:51 +0000173static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 SkASSERT(x >= 0 && x <= SK_Scalar1);
175
176#ifdef SK_SCALAR_IS_FLOAT
177 return (unsigned)(x * 0xFFFF);
178#else
179 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
180#endif
181}
182
reed@android.com41bccf52009-04-03 13:33:51 +0000183Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
184 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185 SkASSERT(colorCount > 1);
186
187 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
188
189 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000190 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
193 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
194 fTileMode = mode;
195 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000196
reed@android.com41bccf52009-04-03 13:33:51 +0000197 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000198 fCache32 = NULL;
199 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200
reed@android.com41bccf52009-04-03 13:33:51 +0000201 /* Note: we let the caller skip the first and/or last position.
202 i.e. pos[0] = 0.3, pos[1] = 0.7
203 In these cases, we insert dummy entries to ensure that the final data
204 will be bracketed by [0, 1].
205 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
206
207 Thus colorCount (the caller's value, and fColorCount (our value) may
208 differ by up to 2. In the above example:
209 colorCount = 2
210 fColorCount = 4
211 */
212 fColorCount = colorCount;
213 // check if we need to add in dummy start and/or end position/colors
214 bool dummyFirst = false;
215 bool dummyLast = false;
216 if (pos) {
217 dummyFirst = pos[0] != 0;
218 dummyLast = pos[colorCount - 1] != SK_Scalar1;
219 fColorCount += dummyFirst + dummyLast;
220 }
221
222 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000223 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000224 fOrigColors = reinterpret_cast<SkColor*>(
225 sk_malloc_throw(size * fColorCount));
226 }
227 else {
228 fOrigColors = fStorage;
229 }
230
231 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000232 {
reed@android.com41bccf52009-04-03 13:33:51 +0000233 SkColor* origColors = fOrigColors;
234 if (dummyFirst) {
235 *origColors++ = colors[0];
236 }
237 memcpy(origColors, colors, colorCount * sizeof(SkColor));
238 if (dummyLast) {
239 origColors += colorCount;
240 *origColors = colors[colorCount - 1];
241 }
242 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243
reed@android.com1c12abe2009-07-02 15:01:02 +0000244 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000245 if (fColorCount > 2) {
246 Rec* recs = fRecs;
247 recs->fPos = 0;
248 // recs->fScale = 0; // unused;
249 recs += 1;
250 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000251 /* We need to convert the user's array of relative positions into
252 fixed-point positions and scale factors. We need these results
253 to be strictly monotonic (no two values equal or out of order).
254 Hence this complex loop that just jams a zero for the scale
255 value if it sees a segment out of order, and it assures that
256 we start at 0 and end at 1.0
257 */
258 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000259 int startIndex = dummyFirst ? 0 : 1;
260 int count = colorCount + dummyLast;
261 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000262 // force the last value to be 1.0
263 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000264 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000266 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000267 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 }
reed@android.com41bccf52009-04-03 13:33:51 +0000269 // pin curr withing range
270 if (curr < 0) {
271 curr = 0;
272 } else if (curr > SK_Fixed1) {
273 curr = SK_Fixed1;
274 }
275 recs->fPos = curr;
276 if (curr > prev) {
277 recs->fScale = (1 << 24) / (curr - prev);
278 } else {
279 recs->fScale = 0; // ignore this segment
280 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000281 // get ready for the next value
282 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000283 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000284 }
reed@android.com41bccf52009-04-03 13:33:51 +0000285 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 SkFixed dp = SK_Fixed1 / (colorCount - 1);
287 SkFixed p = dp;
288 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000289 for (int i = 1; i < colorCount; i++) {
290 recs->fPos = p;
291 recs->fScale = scale;
292 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000293 p += dp;
294 }
295 }
296 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000297 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000298}
299
300Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000301 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302 fCacheAlpha = 256;
303
304 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
305
306 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000307 fCache32 = NULL;
308 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000309
reed@android.com41bccf52009-04-03 13:33:51 +0000310 int colorCount = fColorCount = buffer.readU32();
311 if (colorCount > kColorStorageCount) {
312 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
313 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
314 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000316 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318
319 fTileMode = (TileMode)buffer.readU8();
320 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000321 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 if (colorCount > 2) {
323 Rec* recs = fRecs;
324 recs[0].fPos = 0;
325 for (int i = 1; i < colorCount; i++) {
326 recs[i].fPos = buffer.readS32();
327 recs[i].fScale = buffer.readU32();
328 }
329 }
330 buffer.read(&fPtsToUnit, sizeof(SkMatrix));
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000331 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000332}
333
reed@android.com41bccf52009-04-03 13:33:51 +0000334Gradient_Shader::~Gradient_Shader() {
335 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000337 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000338 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000339 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000341 }
reed@google.com82065d62011-02-07 15:30:46 +0000342 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343}
344
reed@android.com41bccf52009-04-03 13:33:51 +0000345void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346 this->INHERITED::flatten(buffer);
347 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000348 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
350 buffer.write8(fTileMode);
351 if (fColorCount > 2) {
352 Rec* recs = fRecs;
353 for (int i = 1; i < fColorCount; i++) {
354 buffer.write32(recs[i].fPos);
355 buffer.write32(recs[i].fScale);
356 }
357 }
358 buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
359}
360
361bool Gradient_Shader::setContext(const SkBitmap& device,
362 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000363 const SkMatrix& matrix) {
364 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000366 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367
368 const SkMatrix& inverse = this->getTotalInverse();
369
370 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
371 return false;
372 }
373
374 fDstToIndexProc = fDstToIndex.getMapXYProc();
375 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
376
377 // now convert our colors in to PMColors
378 unsigned paintAlpha = this->getPaintAlpha();
379 unsigned colorAlpha = 0xFF;
380
reed@android.com3d06a8c2009-07-07 18:19:59 +0000381 // FIXME: record colorAlpha in constructor, since this is not affected
382 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000383 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000384 SkColor src = fOrigColors[i];
385 unsigned sa = SkColorGetA(src);
386 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000387 }
388
389 fFlags = this->INHERITED::getFlags();
390 if ((colorAlpha & paintAlpha) == 0xFF) {
391 fFlags |= kOpaqueAlpha_Flag;
392 }
393 // we can do span16 as long as our individual colors are opaque,
394 // regardless of the paint's alpha
395 if (0xFF == colorAlpha) {
396 fFlags |= kHasSpan16_Flag;
397 }
398
399 // if the new alpha differs from the previous time we were called, inval our cache
400 // this will trigger the cache to be rebuilt.
401 // we don't care about the first time, since the cache ptrs will already be NULL
402 if (fCacheAlpha != paintAlpha) {
403 fCache16 = NULL; // inval the cache
404 fCache32 = NULL; // inval the cache
405 fCacheAlpha = paintAlpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000406 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000407 if (fCache32PixelRef) {
408 fCache32PixelRef->notifyPixelsChanged();
409 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000410 }
411 return true;
412}
413
reed@android.com41bccf52009-04-03 13:33:51 +0000414static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000415 SkASSERT(a == SkToU8(a));
416 SkASSERT(b == SkToU8(b));
417 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000418 return a + ((b - a) * scale >> 8);
419}
420
reed@android.com41bccf52009-04-03 13:33:51 +0000421static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
422 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000423#if 0
424 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
425 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
426 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
427 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
428
429 return SkPackARGB32(a, r, g, b);
430#else
431 int otherBlend = 256 - blend;
432
433#if 0
434 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
435 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
436 SkASSERT((t0 & t1) == 0);
437 return t0 | t1;
438#else
439 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
440 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
441#endif
442
443#endif
444}
445
446#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
447
reed@android.com41bccf52009-04-03 13:33:51 +0000448/** We take the original colors, not our premultiplied PMColors, since we can
449 build a 16bit table as long as the original colors are opaque, even if the
450 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451*/
reed@android.com512a8762009-12-14 15:25:36 +0000452void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
453 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000454 SkASSERT(count > 1);
455 SkASSERT(SkColorGetA(c0) == 0xFF);
456 SkASSERT(SkColorGetA(c1) == 0xFF);
457
458 SkFixed r = SkColorGetR(c0);
459 SkFixed g = SkColorGetG(c0);
460 SkFixed b = SkColorGetB(c0);
461
462 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
463 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
464 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
465
466 r = SkIntToFixed(r) + 0x8000;
467 g = SkIntToFixed(g) + 0x8000;
468 b = SkIntToFixed(b) + 0x8000;
469
470 do {
471 unsigned rr = r >> 16;
472 unsigned gg = g >> 16;
473 unsigned bb = b >> 16;
474 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000475 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000476 cache += 1;
477 r += dr;
478 g += dg;
479 b += db;
480 } while (--count != 0);
481}
482
reed@google.com55b8e8c2011-01-13 16:22:35 +0000483/*
484 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
485 * semantics of how we 2x2 dither 32->16
486 */
487static inline U8CPU dither_fixed_to_8(SkFixed n) {
488 n >>= 8;
489 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
490}
491
492/*
493 * For dithering with premultiply, we want to ceiling the alpha component,
494 * to ensure that it is always >= any color component.
495 */
496static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
497 n >>= 8;
498 return ((n << 1) - (n | (n >> 8))) >> 8;
499}
500
501void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
502 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000503 SkASSERT(count > 1);
504
reed@android.com1c12abe2009-07-02 15:01:02 +0000505 // need to apply paintAlpha to our two endpoints
506 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
507 SkFixed da;
508 {
509 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
510 da = SkIntToFixed(tmp - a) / (count - 1);
511 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000512
reed@android.com1c12abe2009-07-02 15:01:02 +0000513 SkFixed r = SkColorGetR(c0);
514 SkFixed g = SkColorGetG(c0);
515 SkFixed b = SkColorGetB(c0);
516 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
517 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
518 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000519
520 a = SkIntToFixed(a) + 0x8000;
521 r = SkIntToFixed(r) + 0x8000;
522 g = SkIntToFixed(g) + 0x8000;
523 b = SkIntToFixed(b) + 0x8000;
524
525 do {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000526 cache[0] = SkPreMultiplyARGB(a >> 16, r >> 16, g >> 16, b >> 16);
527 cache[kCache32Count] = SkPreMultiplyARGB(dither_ceil_fixed_to_8(a),
528 dither_fixed_to_8(r),
529 dither_fixed_to_8(g),
530 dither_fixed_to_8(b));
531 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000532 a += da;
533 r += dr;
534 g += dg;
535 b += db;
536 } while (--count != 0);
537}
538
reed@android.com41bccf52009-04-03 13:33:51 +0000539static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000540 SkASSERT((unsigned)x <= SK_Fixed1);
541 return x - (x >> 16);
542}
543
reed@android.com200645d2009-12-14 16:41:57 +0000544static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000545 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000546 if (6 == bits) {
547 return (x << 10) | (x << 4) | (x >> 2);
548 }
549 if (8 == bits) {
550 return (x << 8) | x;
551 }
552 sk_throw();
553 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000554}
555
reed@google.com7c2f27d2011-03-07 19:29:00 +0000556const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000557 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000558 // double the count for dither entries
559 const int entryCount = kCache16Count * 2;
560 const size_t allocSize = sizeof(uint16_t) * entryCount;
561
reed@android.com3c9b2a42009-08-27 19:28:37 +0000562 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000563 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000564 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000565 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000566 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000567 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000568 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000569 Rec* rec = fRecs;
570 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000571 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000572 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573 SkASSERT(nextIndex < kCache16Count);
574
575 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000576 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577 prevIndex = nextIndex;
578 }
579 SkASSERT(prevIndex == kCache16Count - 1);
580 }
581
reed@android.com41bccf52009-04-03 13:33:51 +0000582 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000583 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584 uint16_t* linear = fCache16; // just computed linear data
585 uint16_t* mapped = fCache16Storage; // storage for mapped data
586 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000587 for (int i = 0; i < kCache16Count; i++) {
588 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000590 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000591 }
592 sk_free(fCache16);
593 fCache16 = fCache16Storage;
594 }
595 }
596 return fCache16;
597}
598
reed@google.com7c2f27d2011-03-07 19:29:00 +0000599const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000600 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000601 // double the count for dither entries
602 const int entryCount = kCache32Count * 2;
603 const size_t allocSize = sizeof(SkPMColor) * entryCount;
604
reed@google.comdc731fd2010-12-23 15:19:47 +0000605 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000606 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
607 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000608 }
609 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000610 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000611 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
612 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000613 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000614 Rec* rec = fRecs;
615 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000616 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
618 SkASSERT(nextIndex < kCache32Count);
619
620 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000621 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
622 fOrigColors[i],
623 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624 prevIndex = nextIndex;
625 }
626 SkASSERT(prevIndex == kCache32Count - 1);
627 }
628
reed@android.com41bccf52009-04-03 13:33:51 +0000629 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000630 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000631 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000632 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000633 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000635 for (int i = 0; i < kCache32Count; i++) {
636 int index = map->mapUnit16((i << 8) | i) >> 8;
637 mapped[i] = linear[index];
638 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000639 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000640 fCache32PixelRef->unref();
641 fCache32PixelRef = newPR;
642 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643 }
644 }
645 return fCache32;
646}
647
reed@google.comdc731fd2010-12-23 15:19:47 +0000648/*
649 * Because our caller might rebuild the same (logically the same) gradient
650 * over and over, we'd like to return exactly the same "bitmap" if possible,
651 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
652 * To do that, we maintain a private cache of built-bitmaps, based on our
653 * colors and positions. Note: we don't try to flatten the fMapper, so if one
654 * is present, we skip the cache for now.
655 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000656void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.comdc731fd2010-12-23 15:19:47 +0000657 // don't have a way to put the mapper into our cache-key yet
658 if (fMapper) {
659 // force our cahce32pixelref to be built
660 (void)this->getCache32();
661 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
662 bitmap->setPixelRef(fCache32PixelRef);
663 return;
664 }
665
666 // build our key: [numColors + colors[] + {positions[]} ]
667 int count = 1 + fColorCount;
668 if (fColorCount > 2) {
669 count += fColorCount - 1; // fRecs[].fPos
670 }
671
672 SkAutoSTMalloc<16, int32_t> storage(count);
673 int32_t* buffer = storage.get();
674
675 *buffer++ = fColorCount;
676 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
677 buffer += fColorCount;
678 if (fColorCount > 2) {
679 for (int i = 1; i < fColorCount; i++) {
680 *buffer++ = fRecs[i].fPos;
681 }
682 }
683 SkASSERT(buffer - storage.get() == count);
684
685 ///////////////////////////////////
686
687 static SkMutex gMutex;
688 static SkBitmapCache* gCache;
689 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
690 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
691 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000692
reed@google.comdc731fd2010-12-23 15:19:47 +0000693 if (NULL == gCache) {
694 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
695 }
696 size_t size = count * sizeof(int32_t);
697
698 if (!gCache->find(storage.get(), size, bitmap)) {
699 // force our cahce32pixelref to be built
700 (void)this->getCache32();
701 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
702 bitmap->setPixelRef(fCache32PixelRef);
703
704 gCache->add(storage.get(), size, *bitmap);
705 }
706}
707
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000708void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
709 if (info) {
710 if (info->fColorCount >= fColorCount) {
711 if (info->fColors) {
712 memcpy(info->fColors, fOrigColors,
713 fColorCount * sizeof(SkColor));
714 }
715 if (info->fColorOffsets) {
716 if (fColorCount == 2) {
717 info->fColorOffsets[0] = 0;
718 info->fColorOffsets[1] = SK_Scalar1;
719 } else if (fColorCount > 2) {
720 for (int i = 0; i < fColorCount; i++)
721 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
722 }
723 }
724 }
725 info->fColorCount = fColorCount;
726 info->fTileMode = fTileMode;
727 }
728}
729
reed@google.com61eb0402011-04-15 12:11:12 +0000730///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731
reed@android.com41bccf52009-04-03 13:33:51 +0000732static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733 SkVector vec = pts[1] - pts[0];
734 SkScalar mag = vec.length();
735 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
736
737 vec.scale(inv);
738 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
739 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
740 matrix->postScale(inv, inv);
741}
742
743///////////////////////////////////////////////////////////////////////////////
744
745class Linear_Gradient : public Gradient_Shader {
746public:
747 Linear_Gradient(const SkPoint pts[2],
748 const SkColor colors[], const SkScalar pos[], int colorCount,
749 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000750 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
751 fStart(pts[0]),
752 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753 {
754 pts_to_unit_matrix(pts, &fPtsToUnit);
755 }
reed@android.com9b46e772009-06-05 12:24:41 +0000756
reed@android.com5119bdb2009-06-12 21:27:03 +0000757 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000758 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
759 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000760 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*,
reed@google.com7c2f27d2011-03-07 19:29:00 +0000761 TileMode*, SkScalar* twoPointRadialParams) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000762 virtual GradientType asAGradient(GradientInfo* info) const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763
reed@google.com55b8e8c2011-01-13 16:22:35 +0000764 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765 return SkNEW_ARGS(Linear_Gradient, (buffer));
766 }
767
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000768 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
769 this->INHERITED::flatten(buffer);
770 buffer.writeScalar(fStart.fX);
771 buffer.writeScalar(fStart.fY);
772 buffer.writeScalar(fEnd.fX);
773 buffer.writeScalar(fEnd.fY);
774 }
775
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000777 Linear_Gradient(SkFlattenableReadBuffer& buffer)
778 : Gradient_Shader(buffer),
779 fStart(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
780 fEnd(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
781 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782 virtual Factory getFactory() { return CreateProc; }
783
784private:
785 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000786 const SkPoint fStart;
787 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788};
789
reed@android.com5119bdb2009-06-12 21:27:03 +0000790bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
791 const SkMatrix& matrix) {
792 if (!this->INHERITED::setContext(device, paint, matrix)) {
793 return false;
794 }
795
796 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
797 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000798 fFlags |= SkShader::kConstInY32_Flag;
799 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
800 // only claim this if we do have a 16bit mode (i.e. none of our
801 // colors have alpha), and if we are not dithering (which obviously
802 // is not const in Y).
803 fFlags |= SkShader::kConstInY16_Flag;
804 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000805 }
806 return true;
807}
808
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@google.com61eb0402011-04-15 12:11:12 +0000810static inline bool no_need_for_clamp(int fx, int dx, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811 SkASSERT(count > 0);
812 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
813}
814
reed@google.com5eb158d2011-04-15 15:50:34 +0000815#include "SkClampRange.h"
816
817#define NO_CHECK_ITER \
818 fi = fx >> 8; \
819 SkASSERT(fi <= 0xFF); \
820 fx += dx; \
821 *dstC++ = cache[toggle + fi]; \
822 toggle ^= TOGGLE_MASK
823
824
reed@google.com61eb0402011-04-15 12:11:12 +0000825void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826 SkASSERT(count > 0);
827
828 SkPoint srcPt;
829 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
830 TileProc proc = fTileProc;
831 const SkPMColor* cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000832#ifdef USE_DITHER_32BIT_GRADIENT
833 int toggle = ((x ^ y) & 1) << kCache32Bits;
834 const int TOGGLE_MASK = (1 << kCache32Bits);
835#else
836 int toggle = 0;
837 const int TOGGLE_MASK = 0;
838#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839
reed@android.comc552a432009-06-12 20:02:50 +0000840 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000841 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
842 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
844
reed@android.comc552a432009-06-12 20:02:50 +0000845 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846 SkFixed dxStorage[1];
847 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
848 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000849 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
851 dx = SkScalarToFixed(fDstToIndex.getScaleX());
852 }
853
reed@android.comc552a432009-06-12 20:02:50 +0000854 if (SkFixedNearlyZero(dx)) {
855 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 unsigned fi = proc(fx);
857 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000858 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000860 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000861#if 0
reed@google.com55b8e8c2011-01-13 16:22:35 +0000862 do {
863 unsigned fi = SkClampMax(fx >> 8, 0xFF);
864 SkASSERT(fi <= 0xFF);
865 fx += dx;
866 *dstC++ = cache[toggle + fi];
867 toggle ^= TOGGLE_MASK;
868 } while (--count != 0);
reed@google.com5eb158d2011-04-15 15:50:34 +0000869#else
870 SkClampRange range;
871 range.init(fx, dx, count, 0, 0xFF);
872
873 if ((count = range.fCount0) > 0) {
874 sk_memset32_dither(dstC,
875 cache[toggle + range.fV0],
876 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
877 count);
878 dstC += count;
879 }
880 if ((count = range.fCount1) > 0) {
881 unsigned fi;
882 int i, unroll = count >> 3;
883 for (i = 0; i < unroll; i++) {
884 NO_CHECK_ITER; NO_CHECK_ITER;
885 NO_CHECK_ITER; NO_CHECK_ITER;
886 NO_CHECK_ITER; NO_CHECK_ITER;
887 NO_CHECK_ITER; NO_CHECK_ITER;
888 }
889 if ((count &= 7) > 0) {
890 do {
891 NO_CHECK_ITER;
892 } while (--count != 0);
893 }
894 }
895 if ((count = range.fCount2) > 0) {
896 sk_memset32_dither(dstC,
897 cache[toggle + range.fV1],
898 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
899 count);
900 }
901#endif
reed@android.comc552a432009-06-12 20:02:50 +0000902 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000903 do {
904 unsigned fi = mirror_8bits(fx >> 8);
905 SkASSERT(fi <= 0xFF);
906 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000907 *dstC++ = cache[toggle + fi];
908 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000909 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000910 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000911 SkASSERT(proc == repeat_tileproc);
912 do {
913 unsigned fi = repeat_8bits(fx >> 8);
914 SkASSERT(fi <= 0xFF);
915 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000916 *dstC++ = cache[toggle + fi];
917 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000918 } while (--count != 0);
919 }
reed@android.comc552a432009-06-12 20:02:50 +0000920 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000921 SkScalar dstX = SkIntToScalar(x);
922 SkScalar dstY = SkIntToScalar(y);
923 do {
924 dstProc(fDstToIndex, dstX, dstY, &srcPt);
925 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
926 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000927 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
928 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000929 dstX += SK_Scalar1;
930 } while (--count != 0);
931 }
932}
933
reed@google.com55b8e8c2011-01-13 16:22:35 +0000934SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000935 SkMatrix* matrix,
936 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000937 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000939 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000940 }
941 if (matrix) {
942 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
943 matrix->preConcat(fPtsToUnit);
944 }
945 if (xy) {
946 xy[0] = fTileMode;
947 xy[1] = kClamp_TileMode;
948 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000949 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000950}
951
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000952SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
953 if (info) {
954 commonAsAGradient(info);
955 info->fPoint[0] = fStart;
956 info->fPoint[1] = fEnd;
957 }
958 return kLinear_GradientType;
959}
960
reed@android.com3c9b2a42009-08-27 19:28:37 +0000961static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
962 int count) {
963 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964 *dst++ = value;
965 count -= 1;
966 SkTSwap(value, other);
967 }
968
969 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000970
reed@android.com3c9b2a42009-08-27 19:28:37 +0000971 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000973 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000974}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975
reed@google.com5eb158d2011-04-15 15:50:34 +0000976#define NO_CHECK_ITER_16 \
977 fi = fx >> kCache16Shift; \
978 SkASSERT(fi <= kCache16Mask); \
979 fx += dx; \
980 *dstC++ = cache[toggle + fi]; \
981 toggle ^= TOGGLE_MASK
982
983
reed@google.com61eb0402011-04-15 12:11:12 +0000984void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000985 SkASSERT(count > 0);
986
987 SkPoint srcPt;
988 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
989 TileProc proc = fTileProc;
990 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@google.com5eb158d2011-04-15 15:50:34 +0000992 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000994 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000995 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
996 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
998
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000999 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001000 SkFixed dxStorage[1];
1001 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1002 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001003 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1005 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1006 }
1007
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001008 if (SkFixedNearlyZero(dx)) {
1009 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +00001010 unsigned fi = proc(fx) >> kCache16Shift;
1011 SkASSERT(fi <= kCache16Mask);
reed@google.com5eb158d2011-04-15 15:50:34 +00001012 dither_memset16(dstC, cache[toggle + fi],
1013 cache[(toggle ^ TOGGLE_MASK) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001014 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001015#if 0
reed@android.com8a1c16f2008-12-17 15:59:43 +00001016 do {
reed@android.com512a8762009-12-14 15:25:36 +00001017 unsigned fi = SkClampMax(fx >> kCache16Shift, kCache16Mask);
1018 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001020 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001021 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022 } while (--count != 0);
reed@google.com5eb158d2011-04-15 15:50:34 +00001023#else
1024 SkClampRange range;
1025 range.init(fx, dx, count, 0, kCache16Mask);
1026
1027 if ((count = range.fCount0) > 0) {
1028 dither_memset16(dstC,
1029 cache[toggle + range.fV0],
1030 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
1031 count);
1032 dstC += count;
1033 }
1034 if ((count = range.fCount1) > 0) {
1035 unsigned fi;
1036 int i, unroll = count >> 3;
1037 for (i = 0; i < unroll; i++) {
1038 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1039 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1040 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1041 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1042 }
1043 if ((count &= 7) > 0) {
1044 do {
1045 NO_CHECK_ITER_16;
1046 } while (--count != 0);
1047 }
1048 }
1049 if ((count = range.fCount2) > 0) {
1050 dither_memset16(dstC,
1051 cache[toggle + range.fV1],
1052 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
1053 count);
1054 }
1055#endif
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001056 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001057 do {
reed@android.com200645d2009-12-14 16:41:57 +00001058 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001059 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001062 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001064 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 SkASSERT(proc == repeat_tileproc);
1066 do {
reed@android.com200645d2009-12-14 16:41:57 +00001067 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001068 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001071 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072 } while (--count != 0);
1073 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001074 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 SkScalar dstX = SkIntToScalar(x);
1076 SkScalar dstY = SkIntToScalar(y);
1077 do {
1078 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1079 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1080 SkASSERT(fi <= 0xFFFF);
1081
reed@android.com512a8762009-12-14 15:25:36 +00001082 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001083 *dstC++ = cache[toggle + index];
reed@google.com5eb158d2011-04-15 15:50:34 +00001084 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001085
1086 dstX += SK_Scalar1;
1087 } while (--count != 0);
1088 }
1089}
1090
1091///////////////////////////////////////////////////////////////////////////////
1092
1093#define kSQRT_TABLE_BITS 11
1094#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1095
1096#include "SkRadialGradient_Table.h"
1097
1098#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1099
1100#include <stdio.h>
1101
reed@google.com61eb0402011-04-15 12:11:12 +00001102void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001103 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1104
1105 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1106 SkASSERT(file);
1107 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1108
reed@google.com61eb0402011-04-15 12:11:12 +00001109 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1110 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001112 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113
1114 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1115
1116 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001117 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001119 }
1120 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001121 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001122 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123 }
1124 ::fprintf(file, "};\n");
1125 ::fclose(file);
1126}
1127
1128#endif
1129
1130
reed@google.com61eb0402011-04-15 12:11:12 +00001131static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1132 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001133 SkScalar inv = SkScalarInvert(radius);
1134
1135 matrix->setTranslate(-center.fX, -center.fY);
1136 matrix->postScale(inv, inv);
1137}
1138
1139class Radial_Gradient : public Gradient_Shader {
1140public:
1141 Radial_Gradient(const SkPoint& center, SkScalar radius,
1142 const SkColor colors[], const SkScalar pos[], int colorCount,
1143 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001144 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1145 fCenter(center),
1146 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001147 {
1148 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1149 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1150
1151 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1152 }
reed@google.com61eb0402011-04-15 12:11:12 +00001153
1154 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155 SkASSERT(count > 0);
1156
1157 SkPoint srcPt;
1158 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1159 TileProc proc = fTileProc;
1160 const SkPMColor* cache = this->getCache32();
1161
reed@google.com61eb0402011-04-15 12:11:12 +00001162 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001163 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1164 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001165 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1166 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1167
reed@google.com61eb0402011-04-15 12:11:12 +00001168 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001169 SkFixed storage[2];
1170 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1171 dx = storage[0];
1172 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00001173 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001174 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1175 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1176 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1177 }
1178
reed@google.com61eb0402011-04-15 12:11:12 +00001179 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001180 const uint8_t* sqrt_table = gSqrt8Table;
1181 fx >>= 1;
1182 dx >>= 1;
1183 fy >>= 1;
1184 dy >>= 1;
1185 do {
1186 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1187 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1188 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1189 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1190 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1191 fx += dx;
1192 fy += dy;
1193 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001194 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001195 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001196 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1197 if (magnitudeSquared < 0) // Overflow.
1198 magnitudeSquared = SK_FixedMax;
1199 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001200 unsigned fi = mirror_tileproc(dist);
1201 SkASSERT(fi <= 0xFFFF);
1202 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1203 fx += dx;
1204 fy += dy;
1205 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001206 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001207 SkASSERT(proc == repeat_tileproc);
1208 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001209 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1210 if (magnitudeSquared < 0) // Overflow.
1211 magnitudeSquared = SK_FixedMax;
1212 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 unsigned fi = repeat_tileproc(dist);
1214 SkASSERT(fi <= 0xFFFF);
1215 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1216 fx += dx;
1217 fy += dy;
1218 } while (--count != 0);
1219 }
reed@google.com61eb0402011-04-15 12:11:12 +00001220 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221 SkScalar dstX = SkIntToScalar(x);
1222 SkScalar dstY = SkIntToScalar(y);
1223 do {
1224 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1225 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1226 SkASSERT(fi <= 0xFFFF);
1227 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1228 dstX += SK_Scalar1;
1229 } while (--count != 0);
1230 }
1231 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001232
1233 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001234 SkASSERT(count > 0);
1235
1236 SkPoint srcPt;
1237 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1238 TileProc proc = fTileProc;
1239 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241
reed@android.com3c9b2a42009-08-27 19:28:37 +00001242 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001243 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1244 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1246 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1247
reed@android.com3c9b2a42009-08-27 19:28:37 +00001248 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkFixed storage[2];
1250 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1251 dx = storage[0];
1252 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001253 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001254 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1255 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1256 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1257 }
1258
reed@android.com3c9b2a42009-08-27 19:28:37 +00001259 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260 const uint8_t* sqrt_table = gSqrt8Table;
1261
1262 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1263 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1264 precision, but that appears to be visually OK. If we decide this is OK for
1265 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1266 to avoid having to do these extra shifts each time.
1267 */
1268 fx >>= 1;
1269 dx >>= 1;
1270 fy >>= 1;
1271 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001272 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 +00001273 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1274 fy *= fy;
1275 do {
1276 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1277 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1278 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1279 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001280 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1281 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001283 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 do {
1285 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1286 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1287 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1288 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1289 fx += dx;
1290 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1292 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293 } while (--count != 0);
1294 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001295 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 do {
1297 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1298 unsigned fi = mirror_tileproc(dist);
1299 SkASSERT(fi <= 0xFFFF);
1300 fx += dx;
1301 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001302 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1303 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001305 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 SkASSERT(proc == repeat_tileproc);
1307 do {
1308 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1309 unsigned fi = repeat_tileproc(dist);
1310 SkASSERT(fi <= 0xFFFF);
1311 fx += dx;
1312 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001313 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1314 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001315 } while (--count != 0);
1316 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001317 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318 SkScalar dstX = SkIntToScalar(x);
1319 SkScalar dstY = SkIntToScalar(y);
1320 do {
1321 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1322 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1323 SkASSERT(fi <= 0xFFFF);
1324
1325 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001326 *dstC++ = cache[toggle + index];
1327 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328
1329 dstX += SK_Scalar1;
1330 } while (--count != 0);
1331 }
1332 }
1333
reed@google.com55b8e8c2011-01-13 16:22:35 +00001334 virtual BitmapType asABitmap(SkBitmap* bitmap,
1335 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001336 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001337 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001338 if (bitmap) {
1339 this->commonAsABitmap(bitmap);
1340 }
1341 if (matrix) {
1342 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1343 matrix->preConcat(fPtsToUnit);
1344 }
1345 if (xy) {
1346 xy[0] = fTileMode;
1347 xy[1] = kClamp_TileMode;
1348 }
1349 return kRadial_BitmapType;
1350 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001351 virtual GradientType asAGradient(GradientInfo* info) const {
1352 if (info) {
1353 commonAsAGradient(info);
1354 info->fPoint[0] = fCenter;
1355 info->fRadius[0] = fRadius;
1356 }
1357 return kRadial_GradientType;
1358 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001359
1360 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001361 return SkNEW_ARGS(Radial_Gradient, (buffer));
1362 }
1363
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001364 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1365 this->INHERITED::flatten(buffer);
1366 buffer.writeScalar(fCenter.fX);
1367 buffer.writeScalar(fCenter.fY);
1368 buffer.writeScalar(fRadius);
1369 }
1370
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001372 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1373 : Gradient_Shader(buffer),
1374 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1375 fRadius(buffer.readScalar()) {
1376 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001377 virtual Factory getFactory() { return CreateProc; }
1378
1379private:
1380 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001381 const SkPoint fCenter;
1382 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001383};
1384
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001385/* Two-point radial gradients are specified by two circles, each with a center
1386 point and radius. The gradient can be considered to be a series of
1387 concentric circles, with the color interpolated from the start circle
1388 (at t=0) to the end circle (at t=1).
1389
1390 For each point (x, y) in the span, we want to find the
1391 interpolated circle that intersects that point. The center
1392 of the desired circle (Cx, Cy) falls at some distance t
1393 along the line segment between the start point (Sx, Sy) and
1394 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001395
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001396 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1397 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001398
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001399 The radius of the desired circle (r) is also a linear interpolation t
1400 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001401
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001402 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001403
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001404 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001405
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001406 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001407
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001408 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001409
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001410 (x - ((1 - t) * Sx + t * Ex))^2
1411 + (y - ((1 - t) * Sy + t * Ey))^2
1412 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001413
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001414 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001415
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001416 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1417 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1418 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001419
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001420 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1421
1422 [Dx^2 + Dy^2 - Dr^2)] * t^2
1423 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1424 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001425
1426 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001427 possible circles on which the point may fall. Solving for t yields
1428 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001429
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001430 If a<0, the start circle is entirely contained in the
1431 end circle, and one of the roots will be <0 or >1 (off the line
1432 segment). If a>0, the start circle falls at least partially
1433 outside the end circle (or vice versa), and the gradient
1434 defines a "tube" where a point may be on one circle (on the
1435 inside of the tube) or the other (outside of the tube). We choose
1436 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001437
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001438 In order to keep the math to within the limits of fixed point,
1439 we divide the entire quadratic by Dr^2, and replace
1440 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001441
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001442 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1443 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1444 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001445
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001446 (x' and y' are computed by appending the subtract and scale to the
1447 fDstToIndex matrix in the constructor).
1448
1449 Since the 'A' component of the quadratic is independent of x' and y', it
1450 is precomputed in the constructor. Since the 'B' component is linear in
1451 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001452 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001453 a perspective projection), it must be computed in the loop.
1454
1455*/
1456
reed@google.com84e9c082011-04-13 17:44:24 +00001457static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1458 SkScalar sr2d2, SkScalar foura,
1459 SkScalar oneOverTwoA, bool posRoot) {
1460 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
1461 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001462 if (discrim < 0) {
1463 discrim = -discrim;
1464 }
reed@google.com84e9c082011-04-13 17:44:24 +00001465 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1466 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001467 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001468 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001469 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001470 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001471 }
reed@google.com84e9c082011-04-13 17:44:24 +00001472 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001473}
1474
1475class Two_Point_Radial_Gradient : public Gradient_Shader {
1476public:
1477 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1478 const SkPoint& end, SkScalar endRadius,
1479 const SkColor colors[], const SkScalar pos[],
1480 int colorCount, SkShader::TileMode mode,
1481 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001482 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1483 fCenter1(start),
1484 fCenter2(end),
1485 fRadius1(startRadius),
1486 fRadius2(endRadius) {
1487 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001488 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001489
1490 virtual BitmapType asABitmap(SkBitmap* bitmap,
1491 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001492 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001493 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001494 if (bitmap) {
1495 this->commonAsABitmap(bitmap);
1496 }
1497 SkScalar diffL = 0; // just to avoid gcc warning
1498 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001499 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001500 SkScalarSquare(fDiff.fY));
1501 }
1502 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001503 if (diffL) {
1504 SkScalar invDiffL = SkScalarInvert(diffL);
1505 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1506 SkScalarMul(invDiffL, fDiff.fX));
1507 } else {
1508 matrix->reset();
1509 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001510 matrix->preConcat(fPtsToUnit);
1511 }
1512 if (xy) {
1513 xy[0] = fTileMode;
1514 xy[1] = kClamp_TileMode;
1515 }
1516 if (NULL != twoPointRadialParams) {
1517 twoPointRadialParams[0] = diffL;
1518 twoPointRadialParams[1] = fStartRadius;
1519 twoPointRadialParams[2] = fDiffRadius;
1520 }
1521 return kTwoPointRadial_BitmapType;
1522 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001523
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001524 virtual GradientType asAGradient(GradientInfo* info) const {
1525 if (info) {
1526 commonAsAGradient(info);
1527 info->fPoint[0] = fCenter1;
1528 info->fPoint[1] = fCenter2;
1529 info->fRadius[0] = fRadius1;
1530 info->fRadius[1] = fRadius2;
1531 }
1532 return kRadial2_GradientType;
1533 }
1534
reed@google.com61eb0402011-04-15 12:11:12 +00001535 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001536 SkASSERT(count > 0);
1537
1538 // Zero difference between radii: fill with transparent black.
1539 if (fDiffRadius == 0) {
1540 sk_bzero(dstC, count * sizeof(*dstC));
1541 return;
1542 }
1543 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1544 TileProc proc = fTileProc;
1545 const SkPMColor* cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001546
1547 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001548 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001549 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001550 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001551 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1552 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001553 SkScalar dx, fx = srcPt.fX;
1554 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001555
reed@google.com61eb0402011-04-15 12:11:12 +00001556 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001557 SkFixed fixedX, fixedY;
1558 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1559 dx = SkFixedToScalar(fixedX);
1560 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001561 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001562 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001563 dx = fDstToIndex.getScaleX();
1564 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001565 }
reed@google.com84e9c082011-04-13 17:44:24 +00001566 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1567 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1568 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1569 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001570 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001571 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001572 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001573 SkFixed index = SkClampMax(t, 0xFFFF);
1574 SkASSERT(index <= 0xFFFF);
1575 *dstC++ = cache[index >> (16 - kCache32Bits)];
1576 fx += dx;
1577 fy += dy;
1578 b += db;
1579 }
reed@google.com61eb0402011-04-15 12:11:12 +00001580 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001581 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001582 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001583 SkFixed index = mirror_tileproc(t);
1584 SkASSERT(index <= 0xFFFF);
1585 *dstC++ = cache[index >> (16 - kCache32Bits)];
1586 fx += dx;
1587 fy += dy;
1588 b += db;
1589 }
reed@google.com61eb0402011-04-15 12:11:12 +00001590 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001591 SkASSERT(proc == repeat_tileproc);
1592 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001593 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001594 SkFixed index = repeat_tileproc(t);
1595 SkASSERT(index <= 0xFFFF);
1596 *dstC++ = cache[index >> (16 - kCache32Bits)];
1597 fx += dx;
1598 fy += dy;
1599 b += db;
1600 }
1601 }
reed@google.com61eb0402011-04-15 12:11:12 +00001602 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001603 SkScalar dstX = SkIntToScalar(x);
1604 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001605 for (; count > 0; --count) {
1606 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001607 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001608 SkScalar fx = srcPt.fX;
1609 SkScalar fy = srcPt.fY;
1610 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1611 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1612 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001613 SkFixed index = proc(t);
1614 SkASSERT(index <= 0xFFFF);
1615 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001616 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001617 }
1618 }
1619 }
1620
reed@android.com6c59a172009-09-22 20:24:05 +00001621 virtual bool setContext(const SkBitmap& device,
1622 const SkPaint& paint,
1623 const SkMatrix& matrix) {
1624 if (!this->INHERITED::setContext(device, paint, matrix)) {
1625 return false;
1626 }
1627
1628 // we don't have a span16 proc
1629 fFlags &= ~kHasSpan16_Flag;
1630 return true;
1631 }
1632
reed@google.com55b8e8c2011-01-13 16:22:35 +00001633 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001634 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1635 }
1636
reed@android.combcfc7332009-11-10 16:19:39 +00001637 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1638 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001639 buffer.writeScalar(fCenter1.fX);
1640 buffer.writeScalar(fCenter1.fY);
1641 buffer.writeScalar(fCenter2.fX);
1642 buffer.writeScalar(fCenter2.fY);
1643 buffer.writeScalar(fRadius1);
1644 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001645 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001646
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001647protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001648 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001649 : Gradient_Shader(buffer),
1650 fCenter1(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1651 fCenter2(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1652 fRadius1(buffer.readScalar()),
1653 fRadius2(buffer.readScalar()) {
1654 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001655 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001656 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001657
1658private:
1659 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001660 const SkPoint fCenter1;
1661 const SkPoint fCenter2;
1662 const SkScalar fRadius1;
1663 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001664 SkPoint fDiff;
1665 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001666
1667 void init() {
1668 fDiff = fCenter1 - fCenter2;
1669 fDiffRadius = fRadius2 - fRadius1;
1670 SkScalar inv = SkScalarInvert(fDiffRadius);
1671 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1672 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1673 fStartRadius = SkScalarMul(fRadius1, inv);
1674 fSr2D2 = SkScalarSquare(fStartRadius);
1675 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1676 fOneOverTwoA = SkScalarInvert(fA * 2);
1677
1678 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1679 fPtsToUnit.postScale(inv, inv);
1680 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001681};
1682
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683///////////////////////////////////////////////////////////////////////////////
1684
1685class Sweep_Gradient : public Gradient_Shader {
1686public:
1687 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1688 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001689 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1690 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 {
1692 fPtsToUnit.setTranslate(-cx, -cy);
1693 }
1694 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1695 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001696
1697 virtual BitmapType asABitmap(SkBitmap* bitmap,
1698 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001699 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001700 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001701 if (bitmap) {
1702 this->commonAsABitmap(bitmap);
1703 }
1704 if (matrix) {
1705 *matrix = fPtsToUnit;
1706 }
1707 if (xy) {
1708 xy[0] = fTileMode;
1709 xy[1] = kClamp_TileMode;
1710 }
1711 return kSweep_BitmapType;
1712 }
1713
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001714 virtual GradientType asAGradient(GradientInfo* info) const {
1715 if (info) {
1716 commonAsAGradient(info);
1717 info->fPoint[0] = fCenter;
1718 }
1719 return kSweep_GradientType;
1720 }
1721
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1723 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1724 }
1725
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001726 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1727 this->INHERITED::flatten(buffer);
1728 buffer.writeScalar(fCenter.fX);
1729 buffer.writeScalar(fCenter.fY);
1730 }
1731
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001733 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1734 : Gradient_Shader(buffer),
1735 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
1736 }
1737
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738 virtual Factory getFactory() { return CreateProc; }
1739
1740private:
1741 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001742 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743};
1744
1745#ifdef COMPUTE_SWEEP_TABLE
1746#define PI 3.14159265
1747static bool gSweepTableReady;
1748static uint8_t gSweepTable[65];
1749
1750/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1751 We scale the results to [0..32]
1752*/
reed@google.com61eb0402011-04-15 12:11:12 +00001753static const uint8_t* build_sweep_table() {
1754 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 const int N = 65;
1756 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001757
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 for (int i = 0; i < N; i++)
1759 {
1760 double arg = i / DENOM;
1761 double v = atan(arg);
1762 int iv = (int)round(v * DENOM * 2 / PI);
1763// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1764 printf("%d, ", iv);
1765 gSweepTable[i] = iv;
1766 }
1767 gSweepTableReady = true;
1768 }
1769 return gSweepTable;
1770}
1771#else
1772static const uint8_t gSweepTable[] = {
1773 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1774 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1775 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1776 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1777 32
1778};
1779static const uint8_t* build_sweep_table() { return gSweepTable; }
1780#endif
1781
1782// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1783// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1784// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1785
1786//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001787static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001788 SkASSERT(numer <= denom);
1789 SkASSERT(numer > 0);
1790 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001791
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 int nbits = SkCLZ(numer);
1793 int dbits = SkCLZ(denom);
1794 int bits = 6 - nbits + dbits;
1795 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001796
reed@google.com61eb0402011-04-15 12:11:12 +00001797 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001799 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800
1801 denom <<= dbits - 1;
1802 numer <<= nbits - 1;
1803
1804 unsigned result = 0;
1805
1806 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001807 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001809 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001811 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001812
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001814 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 // make room for the rest of the answer bits
1816 result <<= bits;
1817 switch (bits) {
1818 case 6:
1819 if ((numer = (numer << 1) - denom) >= 0)
1820 result |= 32;
1821 else
1822 numer += denom;
1823 case 5:
1824 if ((numer = (numer << 1) - denom) >= 0)
1825 result |= 16;
1826 else
1827 numer += denom;
1828 case 4:
1829 if ((numer = (numer << 1) - denom) >= 0)
1830 result |= 8;
1831 else
1832 numer += denom;
1833 case 3:
1834 if ((numer = (numer << 1) - denom) >= 0)
1835 result |= 4;
1836 else
1837 numer += denom;
1838 case 2:
1839 if ((numer = (numer << 1) - denom) >= 0)
1840 result |= 2;
1841 else
1842 numer += denom;
1843 case 1:
1844 default: // not strictly need, but makes GCC make better ARM code
1845 if ((numer = (numer << 1) - denom) >= 0)
1846 result |= 1;
1847 else
1848 numer += denom;
1849 }
1850 }
1851 return result;
1852}
1853
1854// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001855static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856#ifdef SK_DEBUG
1857 {
1858 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001859 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001860 gOnce = true;
1861 SkASSERT(div_64(55, 55) == 64);
1862 SkASSERT(div_64(128, 256) == 32);
1863 SkASSERT(div_64(2326528, 4685824) == 31);
1864 SkASSERT(div_64(753664, 5210112) == 9);
1865 SkASSERT(div_64(229376, 4882432) == 3);
1866 SkASSERT(div_64(2, 64) == 2);
1867 SkASSERT(div_64(1, 64) == 1);
1868 // test that we handle underflow correctly
1869 SkASSERT(div_64(12345, 0x54321234) == 0);
1870 }
1871 }
1872#endif
1873
1874 SkASSERT(y > 0 && x > 0);
1875 const uint8_t* table = build_sweep_table();
1876
1877 unsigned result;
1878 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00001879 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001880 // first part of the atan(v) = PI/2 - atan(1/v) identity
1881 // since our div_64 and table want v <= 1, where v = y/x
1882 SkTSwap<SkFixed>(x, y);
1883 }
1884
1885 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001886
reed@android.com8a1c16f2008-12-17 15:59:43 +00001887#ifdef SK_DEBUG
1888 {
1889 unsigned result2 = SkDivBits(y, x, 6);
1890 SkASSERT(result2 == result ||
1891 (result == 1 && result2 == 0));
1892 }
1893#endif
1894
1895 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1896 result = table[result];
1897
reed@google.com61eb0402011-04-15 12:11:12 +00001898 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899 // complete the atan(v) = PI/2 - atan(1/v) identity
1900 result = 64 - result;
1901 // pin to 63
1902 result -= result >> 6;
1903 }
1904
1905 SkASSERT(result <= 63);
1906 return result;
1907}
1908
1909// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com61eb0402011-04-15 12:11:12 +00001910static unsigned SkATan2_255(SkFixed y, SkFixed x) {
1911 if (x == 0) {
1912 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001914 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 return y < 0 ? 192 : 64;
1916 }
reed@google.com61eb0402011-04-15 12:11:12 +00001917 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001918 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001919 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001920
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921 /* Find the right quadrant for x,y
1922 Since atan_0_90 only handles the first quadrant, we rotate x,y
1923 appropriately before calling it, and then add the right amount
1924 to account for the real quadrant.
1925 quadrant 0 : add 0 | x > 0 && y > 0
1926 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1927 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1928 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001929
reed@android.com8a1c16f2008-12-17 15:59:43 +00001930 map x<0 to (1 << 6)
1931 map y<0 to (3 << 6)
1932 add = map_x ^ map_y
1933 */
1934 int xsign = x >> 31;
1935 int ysign = y >> 31;
1936 int add = ((-xsign) ^ (ysign & 3)) << 6;
1937
1938#ifdef SK_DEBUG
1939 if (0 == add)
1940 SkASSERT(x > 0 && y > 0);
1941 else if (64 == add)
1942 SkASSERT(x < 0 && y > 0);
1943 else if (128 == add)
1944 SkASSERT(x < 0 && y < 0);
1945 else if (192 == add)
1946 SkASSERT(x > 0 && y < 0);
1947 else
1948 SkASSERT(!"bad value for add");
1949#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00001950
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1952 where we need to rotate x,y by 90 or -90
1953 */
1954 x = (x ^ xsign) - xsign;
1955 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00001956 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00001958 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959
1960 unsigned result = add + atan_0_90(y, x);
1961 SkASSERT(result < 256);
1962 return result;
1963}
1964
reed@google.com61eb0402011-04-15 12:11:12 +00001965void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 SkMatrix::MapXYProc proc = fDstToIndexProc;
1967 const SkMatrix& matrix = fDstToIndex;
1968 const SkPMColor* cache = this->getCache32();
1969 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001970
reed@google.com61eb0402011-04-15 12:11:12 +00001971 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001972 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1973 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1974 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1975 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001976
reed@google.com61eb0402011-04-15 12:11:12 +00001977 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001978 SkFixed storage[2];
1979 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1980 &storage[0], &storage[1]);
1981 dx = storage[0];
1982 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00001983 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1985 dx = SkScalarToFixed(matrix.getScaleX());
1986 dy = SkScalarToFixed(matrix.getSkewY());
1987 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001988
reed@google.com61eb0402011-04-15 12:11:12 +00001989 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001990 *dstC++ = cache[SkATan2_255(fy, fx)];
1991 fx += dx;
1992 fy += dy;
1993 }
reed@google.com61eb0402011-04-15 12:11:12 +00001994 } else { // perspective case
1995 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1997 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001998
reed@android.com8a1c16f2008-12-17 15:59:43 +00001999 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2000 SkScalarToFixed(srcPt.fX));
2001 *dstC++ = cache[index];
2002 }
2003 }
2004}
2005
reed@google.com61eb0402011-04-15 12:11:12 +00002006void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007 SkMatrix::MapXYProc proc = fDstToIndexProc;
2008 const SkMatrix& matrix = fDstToIndex;
2009 const uint16_t* cache = this->getCache16();
2010 int toggle = ((x ^ y) & 1) << kCache16Bits;
2011 SkPoint srcPt;
2012
reed@google.com61eb0402011-04-15 12:11:12 +00002013 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2015 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2016 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2017 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002018
reed@google.com61eb0402011-04-15 12:11:12 +00002019 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020 SkFixed storage[2];
2021 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2022 &storage[0], &storage[1]);
2023 dx = storage[0];
2024 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002025 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2027 dx = SkScalarToFixed(matrix.getScaleX());
2028 dy = SkScalarToFixed(matrix.getSkewY());
2029 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002030
reed@google.com61eb0402011-04-15 12:11:12 +00002031 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2033 *dstC++ = cache[toggle + index];
2034 toggle ^= (1 << kCache16Bits);
2035 fx += dx;
2036 fy += dy;
2037 }
reed@google.com61eb0402011-04-15 12:11:12 +00002038 } else { // perspective case
2039 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002040 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2041 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002042
reed@android.com8a1c16f2008-12-17 15:59:43 +00002043 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2044 SkScalarToFixed(srcPt.fX));
2045 index >>= (8 - kCache16Bits);
2046 *dstC++ = cache[toggle + index];
2047 toggle ^= (1 << kCache16Bits);
2048 }
2049 }
2050}
2051
reed@google.com61eb0402011-04-15 12:11:12 +00002052///////////////////////////////////////////////////////////////////////////////
2053///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054
2055// assumes colors is SkColor* and pos is SkScalar*
2056#define EXPAND_1_COLOR(count) \
2057 SkColor tmp[2]; \
2058 do { \
2059 if (1 == count) { \
2060 tmp[0] = tmp[1] = colors[0]; \
2061 colors = tmp; \
2062 pos = NULL; \
2063 count = 2; \
2064 } \
2065 } while (0)
2066
reed@google.com61eb0402011-04-15 12:11:12 +00002067SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2068 const SkColor colors[],
2069 const SkScalar pos[], int colorCount,
2070 SkShader::TileMode mode,
2071 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072 if (NULL == pts || NULL == colors || colorCount < 1) {
2073 return NULL;
2074 }
2075 EXPAND_1_COLOR(colorCount);
2076
reed@android.comab840b82009-07-01 17:00:03 +00002077 return SkNEW_ARGS(Linear_Gradient,
2078 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002079}
2080
reed@google.com61eb0402011-04-15 12:11:12 +00002081SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2082 const SkColor colors[],
2083 const SkScalar pos[], int colorCount,
2084 SkShader::TileMode mode,
2085 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002086 if (radius <= 0 || NULL == colors || colorCount < 1) {
2087 return NULL;
2088 }
2089 EXPAND_1_COLOR(colorCount);
2090
reed@android.comab840b82009-07-01 17:00:03 +00002091 return SkNEW_ARGS(Radial_Gradient,
2092 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093}
2094
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002095SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2096 SkScalar startRadius,
2097 const SkPoint& end,
2098 SkScalar endRadius,
2099 const SkColor colors[],
2100 const SkScalar pos[],
2101 int colorCount,
2102 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002103 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002104 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2105 return NULL;
2106 }
2107 EXPAND_1_COLOR(colorCount);
2108
2109 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002110 (start, startRadius, end, endRadius, colors, pos,
2111 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002112}
2113
reed@android.com8a1c16f2008-12-17 15:59:43 +00002114SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2115 const SkColor colors[],
2116 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002117 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 if (NULL == colors || count < 1) {
2119 return NULL;
2120 }
2121 EXPAND_1_COLOR(count);
2122
2123 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2124}
2125
2126static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2127 Linear_Gradient::CreateProc);
2128
2129static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2130 Radial_Gradient::CreateProc);
2131
2132static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2133 Sweep_Gradient::CreateProc);
2134