blob: 5a3a80ab16a1b418d1b628b5199cca15cdb9fb8f [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**
5** 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
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** 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
15** limitations under the License.
16*/
17
18#include "SkGradientShader.h"
19#include "SkColorPriv.h"
20#include "SkUnitMapper.h"
21#include "SkUtils.h"
22
reed@android.com8a1c16f2008-12-17 15:59:43 +000023///////////////////////////////////////////////////////////////////////////
24
25typedef SkFixed (*TileProc)(SkFixed);
26
reed@android.com41bccf52009-04-03 13:33:51 +000027static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000028 return SkClampMax(x, 0xFFFF);
29}
30
reed@android.com41bccf52009-04-03 13:33:51 +000031static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000032 return x & 0xFFFF;
33}
34
reed@android.com41bccf52009-04-03 13:33:51 +000035static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000036 int s = x << 15 >> 31;
37 return (x ^ s) & 0xFFFF;
38}
39
40static const TileProc gTileProcs[] = {
41 clamp_tileproc,
42 repeat_tileproc,
43 mirror_tileproc
44};
45
46//////////////////////////////////////////////////////////////////////////////
47
reed@android.com200645d2009-12-14 16:41:57 +000048static inline int repeat_bits(int x, const int bits) {
49 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000050}
51
reed@android.com200645d2009-12-14 16:41:57 +000052static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000053#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000054 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000055 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000056 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000057#else
reed@android.com200645d2009-12-14 16:41:57 +000058 int s = x << (31 - bits) >> 31;
59 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000060#endif
61}
62
reed@android.com41bccf52009-04-03 13:33:51 +000063static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 return x & 0xFF;
65}
66
reed@android.com41bccf52009-04-03 13:33:51 +000067static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000068#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000069 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000070 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +000071 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000072 return x & 255;
73#else
74 int s = x << 23 >> 31;
75 return (x ^ s) & 0xFF;
76#endif
77}
78
79//////////////////////////////////////////////////////////////////////////////
80
81class Gradient_Shader : public SkShader {
82public:
83 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +000084 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +000085 virtual ~Gradient_Shader();
86
87 // overrides
88 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
89 virtual uint32_t getFlags() { return fFlags; }
90
91protected:
92 Gradient_Shader(SkFlattenableReadBuffer& );
93 SkUnitMapper* fMapper;
94 SkMatrix fPtsToUnit; // set by subclass
95 SkMatrix fDstToIndex;
96 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +000097 TileMode fTileMode;
98 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +000099 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 uint8_t fDstToIndexClass;
101 uint8_t fFlags;
102
103 struct Rec {
104 SkFixed fPos; // 0...1
105 uint32_t fScale; // (1 << 24) / range
106 };
107 Rec* fRecs;
108
109 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000110 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000112 kCache16Mask = kCache16Count - 1,
113 kCache16Shift = 16 - kCache16Bits,
114
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115 kCache32Bits = 8, // pretty much should always be 8
116 kCache32Count = 1 << kCache32Bits
117 };
118 virtual void flatten(SkFlattenableWriteBuffer& );
119 const uint16_t* getCache16();
120 const SkPMColor* getCache32();
121
reed@android.com9b46e772009-06-05 12:24:41 +0000122 // called when we kill our cached colors (to be rebuilt later on demand)
reed@android.comec3d6e52009-06-05 14:43:55 +0000123 virtual void onCacheReset() = 0;
reed@android.com9b46e772009-06-05 12:24:41 +0000124
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125private:
126 enum {
127 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
128
reed@android.com1c12abe2009-07-02 15:01:02 +0000129 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130 };
131 SkColor fStorage[(kStorageSize + 3) >> 2];
132 SkColor* fOrigColors;
133
134 uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
135 SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
136
137 uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
138 SkPMColor* fCache32Storage; // storage for fCache32, allocated on demand
139 unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
140
reed@android.com512a8762009-12-14 15:25:36 +0000141 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
142
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143 typedef SkShader INHERITED;
144};
145
reed@android.com41bccf52009-04-03 13:33:51 +0000146static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 SkASSERT(x >= 0 && x <= SK_Scalar1);
148
149#ifdef SK_SCALAR_IS_FLOAT
150 return (unsigned)(x * 0xFFFF);
151#else
152 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
153#endif
154}
155
reed@android.com41bccf52009-04-03 13:33:51 +0000156Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
157 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158 SkASSERT(colorCount > 1);
159
160 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
161
162 fMapper = mapper;
163 mapper->safeRef();
164
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
166 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
167 fTileMode = mode;
168 fTileProc = gTileProcs[mode];
reed@android.com41bccf52009-04-03 13:33:51 +0000169
170 fCache16 = fCache16Storage = NULL;
171 fCache32 = fCache32Storage = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172
reed@android.com41bccf52009-04-03 13:33:51 +0000173 /* Note: we let the caller skip the first and/or last position.
174 i.e. pos[0] = 0.3, pos[1] = 0.7
175 In these cases, we insert dummy entries to ensure that the final data
176 will be bracketed by [0, 1].
177 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
178
179 Thus colorCount (the caller's value, and fColorCount (our value) may
180 differ by up to 2. In the above example:
181 colorCount = 2
182 fColorCount = 4
183 */
184 fColorCount = colorCount;
185 // check if we need to add in dummy start and/or end position/colors
186 bool dummyFirst = false;
187 bool dummyLast = false;
188 if (pos) {
189 dummyFirst = pos[0] != 0;
190 dummyLast = pos[colorCount - 1] != SK_Scalar1;
191 fColorCount += dummyFirst + dummyLast;
192 }
193
194 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000195 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000196 fOrigColors = reinterpret_cast<SkColor*>(
197 sk_malloc_throw(size * fColorCount));
198 }
199 else {
200 fOrigColors = fStorage;
201 }
202
203 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 {
reed@android.com41bccf52009-04-03 13:33:51 +0000205 SkColor* origColors = fOrigColors;
206 if (dummyFirst) {
207 *origColors++ = colors[0];
208 }
209 memcpy(origColors, colors, colorCount * sizeof(SkColor));
210 if (dummyLast) {
211 origColors += colorCount;
212 *origColors = colors[colorCount - 1];
213 }
214 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215
reed@android.com1c12abe2009-07-02 15:01:02 +0000216 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000217 if (fColorCount > 2) {
218 Rec* recs = fRecs;
219 recs->fPos = 0;
220 // recs->fScale = 0; // unused;
221 recs += 1;
222 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223 /* We need to convert the user's array of relative positions into
224 fixed-point positions and scale factors. We need these results
225 to be strictly monotonic (no two values equal or out of order).
226 Hence this complex loop that just jams a zero for the scale
227 value if it sees a segment out of order, and it assures that
228 we start at 0 and end at 1.0
229 */
230 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000231 int startIndex = dummyFirst ? 0 : 1;
232 int count = colorCount + dummyLast;
233 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 // force the last value to be 1.0
235 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000236 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000237 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000238 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000239 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 }
reed@android.com41bccf52009-04-03 13:33:51 +0000241 // pin curr withing range
242 if (curr < 0) {
243 curr = 0;
244 } else if (curr > SK_Fixed1) {
245 curr = SK_Fixed1;
246 }
247 recs->fPos = curr;
248 if (curr > prev) {
249 recs->fScale = (1 << 24) / (curr - prev);
250 } else {
251 recs->fScale = 0; // ignore this segment
252 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000253 // get ready for the next value
254 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000255 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256 }
reed@android.com41bccf52009-04-03 13:33:51 +0000257 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 SkFixed dp = SK_Fixed1 / (colorCount - 1);
259 SkFixed p = dp;
260 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000261 for (int i = 1; i < colorCount; i++) {
262 recs->fPos = p;
263 recs->fScale = scale;
264 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265 p += dp;
266 }
267 }
268 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000269 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000270}
271
272Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000273 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000274 fCacheAlpha = 256;
275
276 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
277
278 fCache16 = fCache16Storage = NULL;
279 fCache32 = fCache32Storage = NULL;
280
reed@android.com41bccf52009-04-03 13:33:51 +0000281 int colorCount = fColorCount = buffer.readU32();
282 if (colorCount > kColorStorageCount) {
283 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
284 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
285 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000287 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000288 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289
290 fTileMode = (TileMode)buffer.readU8();
291 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000292 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000293 if (colorCount > 2) {
294 Rec* recs = fRecs;
295 recs[0].fPos = 0;
296 for (int i = 1; i < colorCount; i++) {
297 recs[i].fPos = buffer.readS32();
298 recs[i].fScale = buffer.readU32();
299 }
300 }
301 buffer.read(&fPtsToUnit, sizeof(SkMatrix));
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000302 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303}
304
reed@android.com41bccf52009-04-03 13:33:51 +0000305Gradient_Shader::~Gradient_Shader() {
306 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000308 }
309 if (fCache32Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 sk_free(fCache32Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000311 }
312 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000314 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315 fMapper->safeUnref();
316}
317
reed@android.com41bccf52009-04-03 13:33:51 +0000318void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319 this->INHERITED::flatten(buffer);
320 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000321 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
323 buffer.write8(fTileMode);
324 if (fColorCount > 2) {
325 Rec* recs = fRecs;
326 for (int i = 1; i < fColorCount; i++) {
327 buffer.write32(recs[i].fPos);
328 buffer.write32(recs[i].fScale);
329 }
330 }
331 buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
332}
333
334bool Gradient_Shader::setContext(const SkBitmap& device,
335 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000336 const SkMatrix& matrix) {
337 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000339 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340
341 const SkMatrix& inverse = this->getTotalInverse();
342
343 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
344 return false;
345 }
346
347 fDstToIndexProc = fDstToIndex.getMapXYProc();
348 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
349
350 // now convert our colors in to PMColors
351 unsigned paintAlpha = this->getPaintAlpha();
352 unsigned colorAlpha = 0xFF;
353
reed@android.com3d06a8c2009-07-07 18:19:59 +0000354 // FIXME: record colorAlpha in constructor, since this is not affected
355 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000356 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000357 SkColor src = fOrigColors[i];
358 unsigned sa = SkColorGetA(src);
359 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 }
361
362 fFlags = this->INHERITED::getFlags();
363 if ((colorAlpha & paintAlpha) == 0xFF) {
364 fFlags |= kOpaqueAlpha_Flag;
365 }
366 // we can do span16 as long as our individual colors are opaque,
367 // regardless of the paint's alpha
368 if (0xFF == colorAlpha) {
369 fFlags |= kHasSpan16_Flag;
370 }
371
372 // if the new alpha differs from the previous time we were called, inval our cache
373 // this will trigger the cache to be rebuilt.
374 // we don't care about the first time, since the cache ptrs will already be NULL
375 if (fCacheAlpha != paintAlpha) {
376 fCache16 = NULL; // inval the cache
377 fCache32 = NULL; // inval the cache
378 fCacheAlpha = paintAlpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000379 // inform our subclasses
380 this->onCacheReset();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000381 }
382 return true;
383}
384
reed@android.com41bccf52009-04-03 13:33:51 +0000385static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000386 SkASSERT(a == SkToU8(a));
387 SkASSERT(b == SkToU8(b));
388 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389 return a + ((b - a) * scale >> 8);
390}
391
reed@android.com41bccf52009-04-03 13:33:51 +0000392static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
393 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000394#if 0
395 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
396 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
397 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
398 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
399
400 return SkPackARGB32(a, r, g, b);
401#else
402 int otherBlend = 256 - blend;
403
404#if 0
405 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
406 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
407 SkASSERT((t0 & t1) == 0);
408 return t0 | t1;
409#else
410 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
411 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
412#endif
413
414#endif
415}
416
417#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
418
reed@android.com41bccf52009-04-03 13:33:51 +0000419/** We take the original colors, not our premultiplied PMColors, since we can
420 build a 16bit table as long as the original colors are opaque, even if the
421 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000422*/
reed@android.com512a8762009-12-14 15:25:36 +0000423void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
424 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425 SkASSERT(count > 1);
426 SkASSERT(SkColorGetA(c0) == 0xFF);
427 SkASSERT(SkColorGetA(c1) == 0xFF);
428
429 SkFixed r = SkColorGetR(c0);
430 SkFixed g = SkColorGetG(c0);
431 SkFixed b = SkColorGetB(c0);
432
433 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
434 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
435 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
436
437 r = SkIntToFixed(r) + 0x8000;
438 g = SkIntToFixed(g) + 0x8000;
439 b = SkIntToFixed(b) + 0x8000;
440
441 do {
442 unsigned rr = r >> 16;
443 unsigned gg = g >> 16;
444 unsigned bb = b >> 16;
445 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000446 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000447 cache += 1;
448 r += dr;
449 g += dg;
450 b += db;
451 } while (--count != 0);
452}
453
reed@android.com1c12abe2009-07-02 15:01:02 +0000454static void build_32bit_cache(SkPMColor cache[], SkColor c0, SkColor c1,
455 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000456 SkASSERT(count > 1);
457
reed@android.com1c12abe2009-07-02 15:01:02 +0000458 // need to apply paintAlpha to our two endpoints
459 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
460 SkFixed da;
461 {
462 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
463 da = SkIntToFixed(tmp - a) / (count - 1);
464 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000465
reed@android.com1c12abe2009-07-02 15:01:02 +0000466 SkFixed r = SkColorGetR(c0);
467 SkFixed g = SkColorGetG(c0);
468 SkFixed b = SkColorGetB(c0);
469 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
470 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
471 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000472
473 a = SkIntToFixed(a) + 0x8000;
474 r = SkIntToFixed(r) + 0x8000;
475 g = SkIntToFixed(g) + 0x8000;
476 b = SkIntToFixed(b) + 0x8000;
477
478 do {
reed@android.com1c12abe2009-07-02 15:01:02 +0000479 *cache++ = SkPreMultiplyARGB(a >> 16, r >> 16, g >> 16, b >> 16);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000480 a += da;
481 r += dr;
482 g += dg;
483 b += db;
484 } while (--count != 0);
485}
486
reed@android.com41bccf52009-04-03 13:33:51 +0000487static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000488 SkASSERT((unsigned)x <= SK_Fixed1);
489 return x - (x >> 16);
490}
491
reed@android.com200645d2009-12-14 16:41:57 +0000492static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000493 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000494 if (6 == bits) {
495 return (x << 10) | (x << 4) | (x >> 2);
496 }
497 if (8 == bits) {
498 return (x << 8) | x;
499 }
500 sk_throw();
501 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000502}
503
reed@android.com41bccf52009-04-03 13:33:51 +0000504const uint16_t* Gradient_Shader::getCache16() {
505 if (fCache16 == NULL) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000506 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@android.com8a1c16f2008-12-17 15:59:43 +0000507 fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count * 2);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000508 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000509 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000510 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000511 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000512 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000513 Rec* rec = fRecs;
514 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000515 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000516 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000517 SkASSERT(nextIndex < kCache16Count);
518
519 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000520 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000521 prevIndex = nextIndex;
522 }
523 SkASSERT(prevIndex == kCache16Count - 1);
524 }
525
reed@android.com41bccf52009-04-03 13:33:51 +0000526 if (fMapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000527 fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count * 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000528 uint16_t* linear = fCache16; // just computed linear data
529 uint16_t* mapped = fCache16Storage; // storage for mapped data
530 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000531 for (int i = 0; i < kCache16Count; i++) {
532 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000533 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000534 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000535 }
536 sk_free(fCache16);
537 fCache16 = fCache16Storage;
538 }
539 }
540 return fCache16;
541}
542
reed@android.com41bccf52009-04-03 13:33:51 +0000543const SkPMColor* Gradient_Shader::getCache32() {
544 if (fCache32 == NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000545 if (fCache32Storage == NULL) // set the storage and our working ptr
546 fCache32Storage = (SkPMColor*)sk_malloc_throw(sizeof(SkPMColor) * kCache32Count);
547
548 fCache32 = fCache32Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000549 if (fColorCount == 2) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000550 build_32bit_cache(fCache32, fOrigColors[0], fOrigColors[1],
551 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000552 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000553 Rec* rec = fRecs;
554 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000555 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000556 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
557 SkASSERT(nextIndex < kCache32Count);
558
559 if (nextIndex > prevIndex)
reed@android.com1c12abe2009-07-02 15:01:02 +0000560 build_32bit_cache(fCache32 + prevIndex, fOrigColors[i-1],
561 fOrigColors[i],
562 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000563 prevIndex = nextIndex;
564 }
565 SkASSERT(prevIndex == kCache32Count - 1);
566 }
567
reed@android.com41bccf52009-04-03 13:33:51 +0000568 if (fMapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000569 fCache32Storage = (SkPMColor*)sk_malloc_throw(sizeof(SkPMColor) * kCache32Count);
570 SkPMColor* linear = fCache32; // just computed linear data
571 SkPMColor* mapped = fCache32Storage; // storage for mapped data
572 SkUnitMapper* map = fMapper;
reed@android.com41bccf52009-04-03 13:33:51 +0000573 for (int i = 0; i < 256; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000574 mapped[i] = linear[map->mapUnit16((i << 8) | i) >> 8];
reed@android.com41bccf52009-04-03 13:33:51 +0000575 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000576 sk_free(fCache32);
577 fCache32 = fCache32Storage;
578 }
579 }
580 return fCache32;
581}
582
583///////////////////////////////////////////////////////////////////////////
584
reed@android.com41bccf52009-04-03 13:33:51 +0000585static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000586 SkVector vec = pts[1] - pts[0];
587 SkScalar mag = vec.length();
588 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
589
590 vec.scale(inv);
591 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
592 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
593 matrix->postScale(inv, inv);
594}
595
596///////////////////////////////////////////////////////////////////////////////
597
598class Linear_Gradient : public Gradient_Shader {
599public:
600 Linear_Gradient(const SkPoint pts[2],
601 const SkColor colors[], const SkScalar pos[], int colorCount,
602 SkShader::TileMode mode, SkUnitMapper* mapper)
603 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
604 {
reed@android.com9b46e772009-06-05 12:24:41 +0000605 fCachedBitmap = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606 pts_to_unit_matrix(pts, &fPtsToUnit);
607 }
reed@android.com9b46e772009-06-05 12:24:41 +0000608 virtual ~Linear_Gradient() {
609 if (fCachedBitmap) {
610 SkDELETE(fCachedBitmap);
611 }
612 }
613
reed@android.com5119bdb2009-06-12 21:27:03 +0000614 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
616 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
617 virtual bool asABitmap(SkBitmap*, SkMatrix*, TileMode*);
reed@android.com9b46e772009-06-05 12:24:41 +0000618 virtual void onCacheReset() {
619 if (fCachedBitmap) {
620 SkDELETE(fCachedBitmap);
621 fCachedBitmap = NULL;
622 }
623 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624
625 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
626 return SkNEW_ARGS(Linear_Gradient, (buffer));
627 }
628
629protected:
reed@android.comec3d6e52009-06-05 14:43:55 +0000630 Linear_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {
631 fCachedBitmap = NULL;
632 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 virtual Factory getFactory() { return CreateProc; }
634
635private:
reed@android.com9b46e772009-06-05 12:24:41 +0000636 SkBitmap* fCachedBitmap; // allocated on demand
637
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 typedef Gradient_Shader INHERITED;
639};
640
reed@android.com5119bdb2009-06-12 21:27:03 +0000641bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
642 const SkMatrix& matrix) {
643 if (!this->INHERITED::setContext(device, paint, matrix)) {
644 return false;
645 }
646
647 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
648 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000649 fFlags |= SkShader::kConstInY32_Flag;
650 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
651 // only claim this if we do have a 16bit mode (i.e. none of our
652 // colors have alpha), and if we are not dithering (which obviously
653 // is not const in Y).
654 fFlags |= SkShader::kConstInY16_Flag;
655 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000656 }
657 return true;
658}
659
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@android.comfc25abd2009-01-15 14:38:33 +0000661static inline bool no_need_for_clamp(int fx, int dx, int count)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662{
663 SkASSERT(count > 0);
664 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
665}
666
667void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
668{
669 SkASSERT(count > 0);
670
671 SkPoint srcPt;
672 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
673 TileProc proc = fTileProc;
674 const SkPMColor* cache = this->getCache32();
675
reed@android.comc552a432009-06-12 20:02:50 +0000676 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000677 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
678 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
reed@android.comc552a432009-06-12 20:02:50 +0000679 // preround fx by half the amount we throw away
680 fx += 1 << 7;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681
reed@android.comc552a432009-06-12 20:02:50 +0000682 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683 SkFixed dxStorage[1];
684 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
685 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000686 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
688 dx = SkScalarToFixed(fDstToIndex.getScaleX());
689 }
690
reed@android.comc552a432009-06-12 20:02:50 +0000691 if (SkFixedNearlyZero(dx)) {
692 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693 unsigned fi = proc(fx);
694 SkASSERT(fi <= 0xFFFF);
695 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000696 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697#if 0
698 if (no_need_for_clamp(fx, dx, count))
699 {
700 unsigned fi;
701 while ((count -= 4) >= 0)
702 {
703 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
704 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
705 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
706 fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
707 }
708 SkASSERT(count <= -1 && count >= -4);
709 count += 4;
710 while (--count >= 0)
711 {
712 fi = fx >> 8;
713 SkASSERT(fi <= 0xFF);
714 fx += dx;
715 *dstC++ = cache[fi];
716 }
717 }
718 else
719#endif
720 do {
721 unsigned fi = SkClampMax(fx >> 8, 0xFF);
722 SkASSERT(fi <= 0xFF);
723 fx += dx;
724 *dstC++ = cache[fi];
725 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000726 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727 do {
728 unsigned fi = mirror_8bits(fx >> 8);
729 SkASSERT(fi <= 0xFF);
730 fx += dx;
731 *dstC++ = cache[fi];
732 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000733 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734 SkASSERT(proc == repeat_tileproc);
735 do {
736 unsigned fi = repeat_8bits(fx >> 8);
737 SkASSERT(fi <= 0xFF);
738 fx += dx;
739 *dstC++ = cache[fi];
740 } while (--count != 0);
741 }
reed@android.comc552a432009-06-12 20:02:50 +0000742 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743 SkScalar dstX = SkIntToScalar(x);
744 SkScalar dstY = SkIntToScalar(y);
745 do {
746 dstProc(fDstToIndex, dstX, dstY, &srcPt);
747 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
748 SkASSERT(fi <= 0xFFFF);
749 *dstC++ = cache[fi >> (16 - kCache32Bits)];
750 dstX += SK_Scalar1;
751 } while (--count != 0);
752 }
753}
754
755bool Linear_Gradient::asABitmap(SkBitmap* bitmap, SkMatrix* matrix,
756 TileMode xy[]) {
reed@android.com9b46e772009-06-05 12:24:41 +0000757 // we cache our "bitmap", so it's generationID will be const on subsequent
758 // calls to asABitmap
759 if (NULL == fCachedBitmap) {
760 fCachedBitmap = SkNEW(SkBitmap);
761 fCachedBitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
762 fCachedBitmap->setPixels((void*)this->getCache32(), NULL);
763 }
764
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765 if (bitmap) {
reed@android.com9b46e772009-06-05 12:24:41 +0000766 *bitmap = *fCachedBitmap;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767 }
768 if (matrix) {
769 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
770 matrix->preConcat(fPtsToUnit);
771 }
772 if (xy) {
773 xy[0] = fTileMode;
774 xy[1] = kClamp_TileMode;
775 }
776 return true;
777}
778
reed@android.com3c9b2a42009-08-27 19:28:37 +0000779static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
780 int count) {
781 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782 *dst++ = value;
783 count -= 1;
784 SkTSwap(value, other);
785 }
786
787 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
788
reed@android.com3c9b2a42009-08-27 19:28:37 +0000789 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000791 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793
794void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
795{
796 SkASSERT(count > 0);
797
798 SkPoint srcPt;
799 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
800 TileProc proc = fTileProc;
801 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000804 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
806 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000807 // preround fx by half the amount we throw away
808 fx += 1 << 7;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000810 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811 SkFixed dxStorage[1];
812 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
813 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000814 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
816 dx = SkScalarToFixed(fDstToIndex.getScaleX());
817 }
818
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000819 if (SkFixedNearlyZero(dx)) {
820 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +0000821 unsigned fi = proc(fx) >> kCache16Shift;
822 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000823 dither_memset16(dstC, cache[toggle + fi], cache[(toggle ^ (1 << kCache16Bits)) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000824 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 do {
reed@android.com512a8762009-12-14 15:25:36 +0000826 unsigned fi = SkClampMax(fx >> kCache16Shift, kCache16Mask);
827 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 *dstC++ = cache[toggle + fi];
830 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000832 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000833 do {
reed@android.com200645d2009-12-14 16:41:57 +0000834 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +0000835 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 *dstC++ = cache[toggle + fi];
838 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000840 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841 SkASSERT(proc == repeat_tileproc);
842 do {
reed@android.com200645d2009-12-14 16:41:57 +0000843 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +0000844 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846 *dstC++ = cache[toggle + fi];
847 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848 } while (--count != 0);
849 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000850 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851 SkScalar dstX = SkIntToScalar(x);
852 SkScalar dstY = SkIntToScalar(y);
853 do {
854 dstProc(fDstToIndex, dstX, dstY, &srcPt);
855 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
856 SkASSERT(fi <= 0xFFFF);
857
reed@android.com512a8762009-12-14 15:25:36 +0000858 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 *dstC++ = cache[toggle + index];
860 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861
862 dstX += SK_Scalar1;
863 } while (--count != 0);
864 }
865}
866
867///////////////////////////////////////////////////////////////////////////////
868
869#define kSQRT_TABLE_BITS 11
870#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
871
872#include "SkRadialGradient_Table.h"
873
874#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
875
876#include <stdio.h>
877
878void SkRadialGradient_BuildTable()
879{
880 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
881
882 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
883 SkASSERT(file);
884 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
885
886 for (int i = 0; i < kSQRT_TABLE_SIZE; i++)
887 {
888 if ((i & 15) == 0)
889 ::fprintf(file, "\t");
890
891 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
892
893 ::fprintf(file, "0x%02X", value);
894 if (i < kSQRT_TABLE_SIZE-1)
895 ::fprintf(file, ", ");
896 if ((i & 15) == 15)
897 ::fprintf(file, "\n");
898 }
899 ::fprintf(file, "};\n");
900 ::fclose(file);
901}
902
903#endif
904
905
906static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius, SkMatrix* matrix)
907{
908 SkScalar inv = SkScalarInvert(radius);
909
910 matrix->setTranslate(-center.fX, -center.fY);
911 matrix->postScale(inv, inv);
912}
913
914class Radial_Gradient : public Gradient_Shader {
915public:
916 Radial_Gradient(const SkPoint& center, SkScalar radius,
917 const SkColor colors[], const SkScalar pos[], int colorCount,
918 SkShader::TileMode mode, SkUnitMapper* mapper)
919 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
920 {
921 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
922 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
923
924 rad_to_unit_matrix(center, radius, &fPtsToUnit);
925 }
926 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
927 {
928 SkASSERT(count > 0);
929
930 SkPoint srcPt;
931 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
932 TileProc proc = fTileProc;
933 const SkPMColor* cache = this->getCache32();
934
935 if (fDstToIndexClass != kPerspective_MatrixClass)
936 {
937 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
938 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
939 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
940
941 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
942 {
943 SkFixed storage[2];
944 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
945 dx = storage[0];
946 dy = storage[1];
947 }
948 else
949 {
950 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
951 dx = SkScalarToFixed(fDstToIndex.getScaleX());
952 dy = SkScalarToFixed(fDstToIndex.getSkewY());
953 }
954
955 if (proc == clamp_tileproc)
956 {
957 const uint8_t* sqrt_table = gSqrt8Table;
958 fx >>= 1;
959 dx >>= 1;
960 fy >>= 1;
961 dy >>= 1;
962 do {
963 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
964 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
965 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
966 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
967 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
968 fx += dx;
969 fy += dy;
970 } while (--count != 0);
971 }
972 else if (proc == mirror_tileproc)
973 {
974 do {
975 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
976 unsigned fi = mirror_tileproc(dist);
977 SkASSERT(fi <= 0xFFFF);
978 *dstC++ = cache[fi >> (16 - kCache32Bits)];
979 fx += dx;
980 fy += dy;
981 } while (--count != 0);
982 }
983 else
984 {
985 SkASSERT(proc == repeat_tileproc);
986 do {
987 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
988 unsigned fi = repeat_tileproc(dist);
989 SkASSERT(fi <= 0xFFFF);
990 *dstC++ = cache[fi >> (16 - kCache32Bits)];
991 fx += dx;
992 fy += dy;
993 } while (--count != 0);
994 }
995 }
996 else // perspective case
997 {
998 SkScalar dstX = SkIntToScalar(x);
999 SkScalar dstY = SkIntToScalar(y);
1000 do {
1001 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1002 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1003 SkASSERT(fi <= 0xFFFF);
1004 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1005 dstX += SK_Scalar1;
1006 } while (--count != 0);
1007 }
1008 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001009
1010 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011 SkASSERT(count > 0);
1012
1013 SkPoint srcPt;
1014 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1015 TileProc proc = fTileProc;
1016 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001018
reed@android.com3c9b2a42009-08-27 19:28:37 +00001019 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001020 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
1021 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1022 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1023
reed@android.com3c9b2a42009-08-27 19:28:37 +00001024 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001025 SkFixed storage[2];
1026 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1027 dx = storage[0];
1028 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001029 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001030 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1031 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1032 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1033 }
1034
reed@android.com3c9b2a42009-08-27 19:28:37 +00001035 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001036 const uint8_t* sqrt_table = gSqrt8Table;
1037
1038 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1039 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1040 precision, but that appears to be visually OK. If we decide this is OK for
1041 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1042 to avoid having to do these extra shifts each time.
1043 */
1044 fx >>= 1;
1045 dx >>= 1;
1046 fy >>= 1;
1047 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001048 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 +00001049 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1050 fy *= fy;
1051 do {
1052 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1053 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1054 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1055 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001056 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1057 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001058 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001059 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 do {
1061 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1062 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1063 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1064 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1065 fx += dx;
1066 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001067 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1068 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 } while (--count != 0);
1070 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001071 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072 do {
1073 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1074 unsigned fi = mirror_tileproc(dist);
1075 SkASSERT(fi <= 0xFFFF);
1076 fx += dx;
1077 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001078 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1079 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001080 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001081 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 SkASSERT(proc == repeat_tileproc);
1083 do {
1084 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1085 unsigned fi = repeat_tileproc(dist);
1086 SkASSERT(fi <= 0xFFFF);
1087 fx += dx;
1088 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001089 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1090 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001091 } while (--count != 0);
1092 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001093 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001094 SkScalar dstX = SkIntToScalar(x);
1095 SkScalar dstY = SkIntToScalar(y);
1096 do {
1097 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1098 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1099 SkASSERT(fi <= 0xFFFF);
1100
1101 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001102 *dstC++ = cache[toggle + index];
1103 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001104
1105 dstX += SK_Scalar1;
1106 } while (--count != 0);
1107 }
1108 }
1109
1110 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1111 return SkNEW_ARGS(Radial_Gradient, (buffer));
1112 }
1113
1114protected:
1115 Radial_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {};
1116 virtual Factory getFactory() { return CreateProc; }
reed@android.comec3d6e52009-06-05 14:43:55 +00001117 virtual void onCacheReset() {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118
1119private:
1120 typedef Gradient_Shader INHERITED;
1121};
1122
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001123/* Two-point radial gradients are specified by two circles, each with a center
1124 point and radius. The gradient can be considered to be a series of
1125 concentric circles, with the color interpolated from the start circle
1126 (at t=0) to the end circle (at t=1).
1127
1128 For each point (x, y) in the span, we want to find the
1129 interpolated circle that intersects that point. The center
1130 of the desired circle (Cx, Cy) falls at some distance t
1131 along the line segment between the start point (Sx, Sy) and
1132 end point (Ex, Ey):
1133
1134 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1135 Cy = (1 - t) * Sy + t * Ey
1136
1137 The radius of the desired circle (r) is also a linear interpolation t
1138 between the start and end radii (Sr and Er):
1139
1140 r = (1 - t) * Sr + t * Er
1141
1142 But
1143
1144 (x - Cx)^2 + (y - Cy)^2 = r^2
1145
1146 so
1147
1148 (x - ((1 - t) * Sx + t * Ex))^2
1149 + (y - ((1 - t) * Sy + t * Ey))^2
1150 = ((1 - t) * Sr + t * Er)^2
1151
1152 Solving for t yields
1153
1154 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1155 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1156 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
1157
1158 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1159
1160 [Dx^2 + Dy^2 - Dr^2)] * t^2
1161 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1162 + [dx^2 + dy^2 - Sr^2] = 0
1163
1164 A quadratic in t. The two roots of the quadratic reflect the two
1165 possible circles on which the point may fall. Solving for t yields
1166 the gradient value to use.
1167
1168 If a<0, the start circle is entirely contained in the
1169 end circle, and one of the roots will be <0 or >1 (off the line
1170 segment). If a>0, the start circle falls at least partially
1171 outside the end circle (or vice versa), and the gradient
1172 defines a "tube" where a point may be on one circle (on the
1173 inside of the tube) or the other (outside of the tube). We choose
1174 one arbitrarily.
1175
1176 In order to keep the math to within the limits of fixed point,
1177 we divide the entire quadratic by Dr^2, and replace
1178 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
1179
1180 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1181 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1182 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
1183
1184 (x' and y' are computed by appending the subtract and scale to the
1185 fDstToIndex matrix in the constructor).
1186
1187 Since the 'A' component of the quadratic is independent of x' and y', it
1188 is precomputed in the constructor. Since the 'B' component is linear in
1189 x' and y', if x and y are linear in the span, 'B' can be computed
1190 incrementally with a simple delta (db below). If it is not (e.g.,
1191 a perspective projection), it must be computed in the loop.
1192
1193*/
1194
1195static inline SkFixed two_point_radial(SkFixed b, SkFixed fx, SkFixed fy, SkFixed sr2d2, SkFixed foura, SkFixed oneOverTwoA, bool posRoot) {
1196 SkFixed c = SkFixedSquare(fx) + SkFixedSquare(fy) - sr2d2;
1197 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1198 if (discrim < 0) {
1199 discrim = -discrim;
1200 }
1201 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1202 if (posRoot) {
1203 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1204 } else {
1205 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1206 }
1207}
1208
1209class Two_Point_Radial_Gradient : public Gradient_Shader {
1210public:
1211 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1212 const SkPoint& end, SkScalar endRadius,
1213 const SkColor colors[], const SkScalar pos[],
1214 int colorCount, SkShader::TileMode mode,
1215 SkUnitMapper* mapper)
1216 : Gradient_Shader(colors, pos, colorCount, mode, mapper)
1217 {
1218 fDiff = start - end;
1219 fDiffRadius = endRadius - startRadius;
1220 SkScalar inv = SkScalarInvert(fDiffRadius);
1221 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1222 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1223 fStartRadius = SkScalarMul(startRadius, inv);
1224 fSr2D2 = SkScalarSquare(fStartRadius);
1225 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1226 fOneOverTwoA = SkScalarInvert(fA * 2);
1227
1228 fPtsToUnit.setTranslate(-start.fX, -start.fY);
1229 fPtsToUnit.postScale(inv, inv);
1230 }
1231 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1232 {
1233 SkASSERT(count > 0);
1234
1235 // Zero difference between radii: fill with transparent black.
1236 if (fDiffRadius == 0) {
1237 sk_bzero(dstC, count * sizeof(*dstC));
1238 return;
1239 }
1240 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1241 TileProc proc = fTileProc;
1242 const SkPMColor* cache = this->getCache32();
1243 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1244 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1245 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1246 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1247 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1248 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1249 bool posRoot = fDiffRadius < 0;
1250 if (fDstToIndexClass != kPerspective_MatrixClass)
1251 {
1252 SkPoint srcPt;
1253 dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
1254 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1255 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1256
1257 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1258 {
1259 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1260 }
1261 else
1262 {
1263 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1264 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1265 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1266 }
1267 SkFixed b = (SkFixedMul(diffx, fx) +
1268 SkFixedMul(diffy, fy) - startRadius) << 1;
1269 SkFixed db = (SkFixedMul(diffx, dx) +
1270 SkFixedMul(diffy, dy)) << 1;
1271 if (proc == clamp_tileproc)
1272 {
1273 for (; count > 0; --count) {
1274 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1275 SkFixed index = SkClampMax(t, 0xFFFF);
1276 SkASSERT(index <= 0xFFFF);
1277 *dstC++ = cache[index >> (16 - kCache32Bits)];
1278 fx += dx;
1279 fy += dy;
1280 b += db;
1281 }
1282 }
1283 else if (proc == mirror_tileproc)
1284 {
1285 for (; count > 0; --count) {
1286 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1287 SkFixed index = mirror_tileproc(t);
1288 SkASSERT(index <= 0xFFFF);
1289 *dstC++ = cache[index >> (16 - kCache32Bits)];
1290 fx += dx;
1291 fy += dy;
1292 b += db;
1293 }
1294 }
1295 else
1296 {
1297 SkASSERT(proc == repeat_tileproc);
1298 for (; count > 0; --count) {
1299 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1300 SkFixed index = repeat_tileproc(t);
1301 SkASSERT(index <= 0xFFFF);
1302 *dstC++ = cache[index >> (16 - kCache32Bits)];
1303 fx += dx;
1304 fy += dy;
1305 b += db;
1306 }
1307 }
1308 }
1309 else // perspective case
1310 {
reed@android.com6c59a172009-09-22 20:24:05 +00001311 SkScalar dstX = SkIntToScalar(x);
1312 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001313 for (; count > 0; --count) {
1314 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001315 dstProc(fDstToIndex, dstX, dstY, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001316 SkFixed fx = SkScalarToFixed(srcPt.fX);
1317 SkFixed fy = SkScalarToFixed(srcPt.fY);
1318 SkFixed b = (SkFixedMul(diffx, fx) +
1319 SkFixedMul(diffy, fy) - startRadius) << 1;
1320 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1321 SkFixed index = proc(t);
1322 SkASSERT(index <= 0xFFFF);
1323 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001324 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001325 }
1326 }
1327 }
1328
reed@android.com6c59a172009-09-22 20:24:05 +00001329 virtual bool setContext(const SkBitmap& device,
1330 const SkPaint& paint,
1331 const SkMatrix& matrix) {
1332 if (!this->INHERITED::setContext(device, paint, matrix)) {
1333 return false;
1334 }
1335
1336 // we don't have a span16 proc
1337 fFlags &= ~kHasSpan16_Flag;
1338 return true;
1339 }
1340
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001341 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1342 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1343 }
1344
reed@android.combcfc7332009-11-10 16:19:39 +00001345 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1346 this->INHERITED::flatten(buffer);
1347 buffer.writeScalar(fDiff.fX);
1348 buffer.writeScalar(fDiff.fY);
1349 buffer.writeScalar(fStartRadius);
1350 buffer.writeScalar(fDiffRadius);
1351 buffer.writeScalar(fSr2D2);
1352 buffer.writeScalar(fA);
1353 buffer.writeScalar(fOneOverTwoA);
1354 }
1355
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001356protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001357 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
1358 : Gradient_Shader(buffer) {
1359 fDiff.fX = buffer.readScalar();
1360 fDiff.fY = buffer.readScalar();
1361 fStartRadius = buffer.readScalar();
1362 fDiffRadius = buffer.readScalar();
1363 fSr2D2 = buffer.readScalar();
1364 fA = buffer.readScalar();
1365 fOneOverTwoA = buffer.readScalar();
1366 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001367 virtual Factory getFactory() { return CreateProc; }
1368 virtual void onCacheReset() {}
1369
1370private:
1371 typedef Gradient_Shader INHERITED;
1372 SkPoint fDiff;
1373 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
1374};
1375
reed@android.com8a1c16f2008-12-17 15:59:43 +00001376///////////////////////////////////////////////////////////////////////////////
1377
1378class Sweep_Gradient : public Gradient_Shader {
1379public:
1380 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1381 const SkScalar pos[], int count, SkUnitMapper* mapper)
1382 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper)
1383 {
1384 fPtsToUnit.setTranslate(-cx, -cy);
1385 }
1386 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1387 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
1388
1389 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1390 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1391 }
1392
1393protected:
1394 Sweep_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395 virtual Factory getFactory() { return CreateProc; }
reed@android.comec3d6e52009-06-05 14:43:55 +00001396 virtual void onCacheReset() {}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001397
1398private:
1399 typedef Gradient_Shader INHERITED;
1400};
1401
1402#ifdef COMPUTE_SWEEP_TABLE
1403#define PI 3.14159265
1404static bool gSweepTableReady;
1405static uint8_t gSweepTable[65];
1406
1407/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1408 We scale the results to [0..32]
1409*/
1410static const uint8_t* build_sweep_table()
1411{
1412 if (!gSweepTableReady)
1413 {
1414 const int N = 65;
1415 const double DENOM = N - 1;
1416
1417 for (int i = 0; i < N; i++)
1418 {
1419 double arg = i / DENOM;
1420 double v = atan(arg);
1421 int iv = (int)round(v * DENOM * 2 / PI);
1422// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1423 printf("%d, ", iv);
1424 gSweepTable[i] = iv;
1425 }
1426 gSweepTableReady = true;
1427 }
1428 return gSweepTable;
1429}
1430#else
1431static const uint8_t gSweepTable[] = {
1432 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1433 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1434 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1435 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1436 32
1437};
1438static const uint8_t* build_sweep_table() { return gSweepTable; }
1439#endif
1440
1441// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1442// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1443// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1444
1445//unsigned div_64(int numer, int denom);
1446static unsigned div_64(int numer, int denom)
1447{
1448 SkASSERT(numer <= denom);
1449 SkASSERT(numer > 0);
1450 SkASSERT(denom > 0);
1451
1452 int nbits = SkCLZ(numer);
1453 int dbits = SkCLZ(denom);
1454 int bits = 6 - nbits + dbits;
1455 SkASSERT(bits <= 6);
1456
1457 if (bits < 0) // detect underflow
1458 return 0;
1459
1460 denom <<= dbits - 1;
1461 numer <<= nbits - 1;
1462
1463 unsigned result = 0;
1464
1465 // do the first one
1466 if ((numer -= denom) >= 0)
1467 result = 1;
1468 else
1469 numer += denom;
1470
1471 // Now fall into our switch statement if there are more bits to compute
1472 if (bits > 0)
1473 {
1474 // make room for the rest of the answer bits
1475 result <<= bits;
1476 switch (bits) {
1477 case 6:
1478 if ((numer = (numer << 1) - denom) >= 0)
1479 result |= 32;
1480 else
1481 numer += denom;
1482 case 5:
1483 if ((numer = (numer << 1) - denom) >= 0)
1484 result |= 16;
1485 else
1486 numer += denom;
1487 case 4:
1488 if ((numer = (numer << 1) - denom) >= 0)
1489 result |= 8;
1490 else
1491 numer += denom;
1492 case 3:
1493 if ((numer = (numer << 1) - denom) >= 0)
1494 result |= 4;
1495 else
1496 numer += denom;
1497 case 2:
1498 if ((numer = (numer << 1) - denom) >= 0)
1499 result |= 2;
1500 else
1501 numer += denom;
1502 case 1:
1503 default: // not strictly need, but makes GCC make better ARM code
1504 if ((numer = (numer << 1) - denom) >= 0)
1505 result |= 1;
1506 else
1507 numer += denom;
1508 }
1509 }
1510 return result;
1511}
1512
1513// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1514static unsigned atan_0_90(SkFixed y, SkFixed x)
1515{
1516#ifdef SK_DEBUG
1517 {
1518 static bool gOnce;
1519 if (!gOnce)
1520 {
1521 gOnce = true;
1522 SkASSERT(div_64(55, 55) == 64);
1523 SkASSERT(div_64(128, 256) == 32);
1524 SkASSERT(div_64(2326528, 4685824) == 31);
1525 SkASSERT(div_64(753664, 5210112) == 9);
1526 SkASSERT(div_64(229376, 4882432) == 3);
1527 SkASSERT(div_64(2, 64) == 2);
1528 SkASSERT(div_64(1, 64) == 1);
1529 // test that we handle underflow correctly
1530 SkASSERT(div_64(12345, 0x54321234) == 0);
1531 }
1532 }
1533#endif
1534
1535 SkASSERT(y > 0 && x > 0);
1536 const uint8_t* table = build_sweep_table();
1537
1538 unsigned result;
1539 bool swap = (x < y);
1540 if (swap)
1541 {
1542 // first part of the atan(v) = PI/2 - atan(1/v) identity
1543 // since our div_64 and table want v <= 1, where v = y/x
1544 SkTSwap<SkFixed>(x, y);
1545 }
1546
1547 result = div_64(y, x);
1548
1549#ifdef SK_DEBUG
1550 {
1551 unsigned result2 = SkDivBits(y, x, 6);
1552 SkASSERT(result2 == result ||
1553 (result == 1 && result2 == 0));
1554 }
1555#endif
1556
1557 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1558 result = table[result];
1559
1560 if (swap)
1561 {
1562 // complete the atan(v) = PI/2 - atan(1/v) identity
1563 result = 64 - result;
1564 // pin to 63
1565 result -= result >> 6;
1566 }
1567
1568 SkASSERT(result <= 63);
1569 return result;
1570}
1571
1572// returns angle in a circle [0..2PI) -> [0..255]
1573static unsigned SkATan2_255(SkFixed y, SkFixed x)
1574{
1575 if (x == 0)
1576 {
1577 if (y == 0)
1578 return 0;
1579 return y < 0 ? 192 : 64;
1580 }
1581 if (y == 0)
1582 return x < 0 ? 128 : 0;
1583
1584 /* Find the right quadrant for x,y
1585 Since atan_0_90 only handles the first quadrant, we rotate x,y
1586 appropriately before calling it, and then add the right amount
1587 to account for the real quadrant.
1588 quadrant 0 : add 0 | x > 0 && y > 0
1589 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1590 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1591 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
1592
1593 map x<0 to (1 << 6)
1594 map y<0 to (3 << 6)
1595 add = map_x ^ map_y
1596 */
1597 int xsign = x >> 31;
1598 int ysign = y >> 31;
1599 int add = ((-xsign) ^ (ysign & 3)) << 6;
1600
1601#ifdef SK_DEBUG
1602 if (0 == add)
1603 SkASSERT(x > 0 && y > 0);
1604 else if (64 == add)
1605 SkASSERT(x < 0 && y > 0);
1606 else if (128 == add)
1607 SkASSERT(x < 0 && y < 0);
1608 else if (192 == add)
1609 SkASSERT(x > 0 && y < 0);
1610 else
1611 SkASSERT(!"bad value for add");
1612#endif
1613
1614 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1615 where we need to rotate x,y by 90 or -90
1616 */
1617 x = (x ^ xsign) - xsign;
1618 y = (y ^ ysign) - ysign;
1619 if (add & 64) // quads 1 or 3 need to swap x,y
1620 SkTSwap<SkFixed>(x, y);
1621
1622 unsigned result = add + atan_0_90(y, x);
1623 SkASSERT(result < 256);
1624 return result;
1625}
1626
1627void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1628{
1629 SkMatrix::MapXYProc proc = fDstToIndexProc;
1630 const SkMatrix& matrix = fDstToIndex;
1631 const SkPMColor* cache = this->getCache32();
1632 SkPoint srcPt;
1633
1634 if (fDstToIndexClass != kPerspective_MatrixClass)
1635 {
1636 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1637 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1638 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1639 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1640
1641 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1642 {
1643 SkFixed storage[2];
1644 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1645 &storage[0], &storage[1]);
1646 dx = storage[0];
1647 dy = storage[1];
1648 }
1649 else
1650 {
1651 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1652 dx = SkScalarToFixed(matrix.getScaleX());
1653 dy = SkScalarToFixed(matrix.getSkewY());
1654 }
1655
1656 for (; count > 0; --count)
1657 {
1658 *dstC++ = cache[SkATan2_255(fy, fx)];
1659 fx += dx;
1660 fy += dy;
1661 }
1662 }
1663 else // perspective case
1664 {
1665 for (int stop = x + count; x < stop; x++)
1666 {
1667 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1668 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1669
1670 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1671 SkScalarToFixed(srcPt.fX));
1672 *dstC++ = cache[index];
1673 }
1674 }
1675}
1676
1677void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1678{
1679 SkMatrix::MapXYProc proc = fDstToIndexProc;
1680 const SkMatrix& matrix = fDstToIndex;
1681 const uint16_t* cache = this->getCache16();
1682 int toggle = ((x ^ y) & 1) << kCache16Bits;
1683 SkPoint srcPt;
1684
1685 if (fDstToIndexClass != kPerspective_MatrixClass)
1686 {
1687 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1688 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1689 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1690 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1691
1692 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1693 {
1694 SkFixed storage[2];
1695 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1696 &storage[0], &storage[1]);
1697 dx = storage[0];
1698 dy = storage[1];
1699 }
1700 else
1701 {
1702 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1703 dx = SkScalarToFixed(matrix.getScaleX());
1704 dy = SkScalarToFixed(matrix.getSkewY());
1705 }
1706
1707 for (; count > 0; --count)
1708 {
1709 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1710 *dstC++ = cache[toggle + index];
1711 toggle ^= (1 << kCache16Bits);
1712 fx += dx;
1713 fy += dy;
1714 }
1715 }
1716 else // perspective case
1717 {
1718 for (int stop = x + count; x < stop; x++)
1719 {
1720 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1721 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1722
1723 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1724 SkScalarToFixed(srcPt.fX));
1725 index >>= (8 - kCache16Bits);
1726 *dstC++ = cache[toggle + index];
1727 toggle ^= (1 << kCache16Bits);
1728 }
1729 }
1730}
1731
1732///////////////////////////////////////////////////////////////////////////
1733///////////////////////////////////////////////////////////////////////////
1734
1735// assumes colors is SkColor* and pos is SkScalar*
1736#define EXPAND_1_COLOR(count) \
1737 SkColor tmp[2]; \
1738 do { \
1739 if (1 == count) { \
1740 tmp[0] = tmp[1] = colors[0]; \
1741 colors = tmp; \
1742 pos = NULL; \
1743 count = 2; \
1744 } \
1745 } while (0)
1746
1747SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
1748 const SkColor colors[], const SkScalar pos[], int colorCount,
1749 SkShader::TileMode mode, SkUnitMapper* mapper)
1750{
1751 if (NULL == pts || NULL == colors || colorCount < 1) {
1752 return NULL;
1753 }
1754 EXPAND_1_COLOR(colorCount);
1755
reed@android.comab840b82009-07-01 17:00:03 +00001756 return SkNEW_ARGS(Linear_Gradient,
1757 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758}
1759
1760SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
1761 const SkColor colors[], const SkScalar pos[], int colorCount,
1762 SkShader::TileMode mode, SkUnitMapper* mapper)
1763{
1764 if (radius <= 0 || NULL == colors || colorCount < 1) {
1765 return NULL;
1766 }
1767 EXPAND_1_COLOR(colorCount);
1768
reed@android.comab840b82009-07-01 17:00:03 +00001769 return SkNEW_ARGS(Radial_Gradient,
1770 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771}
1772
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001773SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
1774 SkScalar startRadius,
1775 const SkPoint& end,
1776 SkScalar endRadius,
1777 const SkColor colors[],
1778 const SkScalar pos[],
1779 int colorCount,
1780 SkShader::TileMode mode,
1781 SkUnitMapper* mapper)
1782{
1783 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
1784 return NULL;
1785 }
1786 EXPAND_1_COLOR(colorCount);
1787
1788 return SkNEW_ARGS(Two_Point_Radial_Gradient,
1789 (start, startRadius, end, endRadius, colors, pos, colorCount, mode, mapper));
1790}
1791
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
1793 const SkColor colors[],
1794 const SkScalar pos[],
1795 int count, SkUnitMapper* mapper)
1796{
1797 if (NULL == colors || count < 1) {
1798 return NULL;
1799 }
1800 EXPAND_1_COLOR(count);
1801
1802 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
1803}
1804
1805static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
1806 Linear_Gradient::CreateProc);
1807
1808static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
1809 Radial_Gradient::CreateProc);
1810
1811static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
1812 Sweep_Gradient::CreateProc);
1813