blob: 714181cbd68ba6b1f528073ecf02c40f0fa24965 [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///////////////////////////////////////////////////////////////////////////////
tomhudson@google.com9ce767c2011-04-25 20:49:39 +000049// Can't use a two-argument function with side effects like this in a
50// constructor's initializer's argument list because the order of
51// evaluations in that context is undefined (and backwards on linux/gcc).
52static SkPoint unflatten_point(SkReader32& buffer) {
53 SkPoint retval;
54 retval.fX = buffer.readScalar();
55 retval.fY = buffer.readScalar();
56 return retval;
57}
58
59///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000060
61typedef SkFixed (*TileProc)(SkFixed);
62
reed@android.com41bccf52009-04-03 13:33:51 +000063static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 return SkClampMax(x, 0xFFFF);
65}
66
reed@android.com41bccf52009-04-03 13:33:51 +000067static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 return x & 0xFFFF;
69}
70
reed@android.com41bccf52009-04-03 13:33:51 +000071static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000072 int s = x << 15 >> 31;
73 return (x ^ s) & 0xFFFF;
74}
75
76static const TileProc gTileProcs[] = {
77 clamp_tileproc,
78 repeat_tileproc,
79 mirror_tileproc
80};
81
reed@google.com61eb0402011-04-15 12:11:12 +000082///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +000083
reed@android.com200645d2009-12-14 16:41:57 +000084static inline int repeat_bits(int x, const int bits) {
85 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000086}
87
reed@android.com200645d2009-12-14 16:41:57 +000088static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000089#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000090 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000091 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000092 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000093#else
reed@android.com200645d2009-12-14 16:41:57 +000094 int s = x << (31 - bits) >> 31;
95 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000096#endif
97}
98
reed@android.com41bccf52009-04-03 13:33:51 +000099static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 return x & 0xFF;
101}
102
reed@android.com41bccf52009-04-03 13:33:51 +0000103static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +0000105 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +0000107 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108 return x & 255;
109#else
110 int s = x << 23 >> 31;
111 return (x ^ s) & 0xFF;
112#endif
113}
114
reed@google.com61eb0402011-04-15 12:11:12 +0000115///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116
117class Gradient_Shader : public SkShader {
118public:
119 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +0000120 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 virtual ~Gradient_Shader();
122
123 // overrides
124 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
125 virtual uint32_t getFlags() { return fFlags; }
126
127protected:
128 Gradient_Shader(SkFlattenableReadBuffer& );
129 SkUnitMapper* fMapper;
130 SkMatrix fPtsToUnit; // set by subclass
131 SkMatrix fDstToIndex;
132 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133 TileMode fTileMode;
134 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000135 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000136 uint8_t fDstToIndexClass;
137 uint8_t fFlags;
138
139 struct Rec {
140 SkFixed fPos; // 0...1
141 uint32_t fScale; // (1 << 24) / range
142 };
143 Rec* fRecs;
144
145 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000146 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000148 kCache16Mask = kCache16Count - 1,
149 kCache16Shift = 16 - kCache16Bits,
150
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151 kCache32Bits = 8, // pretty much should always be 8
152 kCache32Count = 1 << kCache32Bits
153 };
154 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000155 const uint16_t* getCache16() const;
156 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157
reed@google.com7c2f27d2011-03-07 19:29:00 +0000158 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000159 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000160
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161private:
162 enum {
163 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
164
reed@android.com1c12abe2009-07-02 15:01:02 +0000165 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166 };
167 SkColor fStorage[(kStorageSize + 3) >> 2];
168 SkColor* fOrigColors;
169
reed@google.com7c2f27d2011-03-07 19:29:00 +0000170 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
171 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172
reed@google.com7c2f27d2011-03-07 19:29:00 +0000173 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
174 mutable SkMallocPixelRef* fCache32PixelRef;
reed@google.com95eed982011-07-05 17:01:56 +0000175 mutable unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176
reed@android.com512a8762009-12-14 15:25:36 +0000177 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000178 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
179 U8CPU alpha);
reed@google.com95eed982011-07-05 17:01:56 +0000180 void setCacheAlpha(U8CPU alpha) const;
reed@android.com512a8762009-12-14 15:25:36 +0000181
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182 typedef SkShader INHERITED;
183};
184
reed@android.com41bccf52009-04-03 13:33:51 +0000185static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186 SkASSERT(x >= 0 && x <= SK_Scalar1);
187
188#ifdef SK_SCALAR_IS_FLOAT
189 return (unsigned)(x * 0xFFFF);
190#else
191 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
192#endif
193}
194
reed@android.com41bccf52009-04-03 13:33:51 +0000195Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
196 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000197 SkASSERT(colorCount > 1);
198
199 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
200
201 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000202 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
205 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
206 fTileMode = mode;
207 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000208
reed@android.com41bccf52009-04-03 13:33:51 +0000209 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000210 fCache32 = NULL;
211 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212
reed@android.com41bccf52009-04-03 13:33:51 +0000213 /* Note: we let the caller skip the first and/or last position.
214 i.e. pos[0] = 0.3, pos[1] = 0.7
215 In these cases, we insert dummy entries to ensure that the final data
216 will be bracketed by [0, 1].
217 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
218
219 Thus colorCount (the caller's value, and fColorCount (our value) may
220 differ by up to 2. In the above example:
221 colorCount = 2
222 fColorCount = 4
223 */
224 fColorCount = colorCount;
225 // check if we need to add in dummy start and/or end position/colors
226 bool dummyFirst = false;
227 bool dummyLast = false;
228 if (pos) {
229 dummyFirst = pos[0] != 0;
230 dummyLast = pos[colorCount - 1] != SK_Scalar1;
231 fColorCount += dummyFirst + dummyLast;
232 }
233
234 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000235 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000236 fOrigColors = reinterpret_cast<SkColor*>(
237 sk_malloc_throw(size * fColorCount));
238 }
239 else {
240 fOrigColors = fStorage;
241 }
242
243 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244 {
reed@android.com41bccf52009-04-03 13:33:51 +0000245 SkColor* origColors = fOrigColors;
246 if (dummyFirst) {
247 *origColors++ = colors[0];
248 }
249 memcpy(origColors, colors, colorCount * sizeof(SkColor));
250 if (dummyLast) {
251 origColors += colorCount;
252 *origColors = colors[colorCount - 1];
253 }
254 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255
reed@android.com1c12abe2009-07-02 15:01:02 +0000256 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000257 if (fColorCount > 2) {
258 Rec* recs = fRecs;
259 recs->fPos = 0;
260 // recs->fScale = 0; // unused;
261 recs += 1;
262 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 /* We need to convert the user's array of relative positions into
264 fixed-point positions and scale factors. We need these results
265 to be strictly monotonic (no two values equal or out of order).
266 Hence this complex loop that just jams a zero for the scale
267 value if it sees a segment out of order, and it assures that
268 we start at 0 and end at 1.0
269 */
270 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000271 int startIndex = dummyFirst ? 0 : 1;
272 int count = colorCount + dummyLast;
273 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000274 // force the last value to be 1.0
275 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000276 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000277 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000278 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000279 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000280 }
reed@android.com41bccf52009-04-03 13:33:51 +0000281 // pin curr withing range
282 if (curr < 0) {
283 curr = 0;
284 } else if (curr > SK_Fixed1) {
285 curr = SK_Fixed1;
286 }
287 recs->fPos = curr;
288 if (curr > prev) {
289 recs->fScale = (1 << 24) / (curr - prev);
290 } else {
291 recs->fScale = 0; // ignore this segment
292 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000293 // get ready for the next value
294 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000295 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296 }
reed@android.com41bccf52009-04-03 13:33:51 +0000297 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000298 SkFixed dp = SK_Fixed1 / (colorCount - 1);
299 SkFixed p = dp;
300 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000301 for (int i = 1; i < colorCount; i++) {
302 recs->fPos = p;
303 recs->fScale = scale;
304 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305 p += dp;
306 }
307 }
308 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000309 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310}
311
312Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000313 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000314 fCacheAlpha = 256;
315
316 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
317
318 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000319 fCache32 = NULL;
320 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321
reed@android.com41bccf52009-04-03 13:33:51 +0000322 int colorCount = fColorCount = buffer.readU32();
323 if (colorCount > kColorStorageCount) {
324 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
325 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
326 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000328 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330
331 fTileMode = (TileMode)buffer.readU8();
332 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000333 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334 if (colorCount > 2) {
335 Rec* recs = fRecs;
336 recs[0].fPos = 0;
337 for (int i = 1; i < colorCount; i++) {
338 recs[i].fPos = buffer.readS32();
339 recs[i].fScale = buffer.readU32();
340 }
341 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000342 SkReadMatrix(&buffer, &fPtsToUnit);
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000343 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344}
345
reed@android.com41bccf52009-04-03 13:33:51 +0000346Gradient_Shader::~Gradient_Shader() {
347 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000348 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000349 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000350 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000351 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000353 }
reed@google.com82065d62011-02-07 15:30:46 +0000354 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355}
356
reed@android.com41bccf52009-04-03 13:33:51 +0000357void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358 this->INHERITED::flatten(buffer);
359 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000360 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
362 buffer.write8(fTileMode);
363 if (fColorCount > 2) {
364 Rec* recs = fRecs;
365 for (int i = 1; i < fColorCount; i++) {
366 buffer.write32(recs[i].fPos);
367 buffer.write32(recs[i].fScale);
368 }
369 }
reed@google.comf2eb5ab2011-05-10 22:56:42 +0000370 SkWriteMatrix(&buffer, fPtsToUnit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371}
372
373bool Gradient_Shader::setContext(const SkBitmap& device,
374 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000375 const SkMatrix& matrix) {
376 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000377 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000378 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379
380 const SkMatrix& inverse = this->getTotalInverse();
381
382 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
383 return false;
384 }
385
386 fDstToIndexProc = fDstToIndex.getMapXYProc();
387 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
388
389 // now convert our colors in to PMColors
390 unsigned paintAlpha = this->getPaintAlpha();
391 unsigned colorAlpha = 0xFF;
392
reed@android.com3d06a8c2009-07-07 18:19:59 +0000393 // FIXME: record colorAlpha in constructor, since this is not affected
394 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000395 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000396 SkColor src = fOrigColors[i];
397 unsigned sa = SkColorGetA(src);
398 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000399 }
400
401 fFlags = this->INHERITED::getFlags();
402 if ((colorAlpha & paintAlpha) == 0xFF) {
403 fFlags |= kOpaqueAlpha_Flag;
404 }
405 // we can do span16 as long as our individual colors are opaque,
406 // regardless of the paint's alpha
407 if (0xFF == colorAlpha) {
408 fFlags |= kHasSpan16_Flag;
409 }
410
reed@google.com95eed982011-07-05 17:01:56 +0000411 this->setCacheAlpha(paintAlpha);
412 return true;
413}
414
415void Gradient_Shader::setCacheAlpha(U8CPU alpha) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000416 // if the new alpha differs from the previous time we were called, inval our cache
417 // this will trigger the cache to be rebuilt.
418 // we don't care about the first time, since the cache ptrs will already be NULL
reed@google.com95eed982011-07-05 17:01:56 +0000419 if (fCacheAlpha != alpha) {
420 fCache16 = NULL; // inval the cache
421 fCache32 = NULL; // inval the cache
422 fCacheAlpha = alpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000423 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000424 if (fCache32PixelRef) {
425 fCache32PixelRef->notifyPixelsChanged();
426 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000427 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428}
429
reed@android.com41bccf52009-04-03 13:33:51 +0000430static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431 SkASSERT(a == SkToU8(a));
432 SkASSERT(b == SkToU8(b));
433 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434 return a + ((b - a) * scale >> 8);
435}
436
reed@android.com41bccf52009-04-03 13:33:51 +0000437static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
438 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439#if 0
440 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
441 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
442 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
443 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
444
445 return SkPackARGB32(a, r, g, b);
446#else
447 int otherBlend = 256 - blend;
448
449#if 0
450 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
451 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
452 SkASSERT((t0 & t1) == 0);
453 return t0 | t1;
454#else
455 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
456 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
457#endif
458
459#endif
460}
461
462#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
463
reed@android.com41bccf52009-04-03 13:33:51 +0000464/** We take the original colors, not our premultiplied PMColors, since we can
465 build a 16bit table as long as the original colors are opaque, even if the
466 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000467*/
reed@android.com512a8762009-12-14 15:25:36 +0000468void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
469 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470 SkASSERT(count > 1);
471 SkASSERT(SkColorGetA(c0) == 0xFF);
472 SkASSERT(SkColorGetA(c1) == 0xFF);
473
474 SkFixed r = SkColorGetR(c0);
475 SkFixed g = SkColorGetG(c0);
476 SkFixed b = SkColorGetB(c0);
477
478 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
479 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
480 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
481
482 r = SkIntToFixed(r) + 0x8000;
483 g = SkIntToFixed(g) + 0x8000;
484 b = SkIntToFixed(b) + 0x8000;
485
486 do {
487 unsigned rr = r >> 16;
488 unsigned gg = g >> 16;
489 unsigned bb = b >> 16;
490 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000491 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000492 cache += 1;
493 r += dr;
494 g += dg;
495 b += db;
496 } while (--count != 0);
497}
498
reed@google.com55b8e8c2011-01-13 16:22:35 +0000499/*
500 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
501 * semantics of how we 2x2 dither 32->16
502 */
503static inline U8CPU dither_fixed_to_8(SkFixed n) {
504 n >>= 8;
505 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
506}
507
508/*
509 * For dithering with premultiply, we want to ceiling the alpha component,
510 * to ensure that it is always >= any color component.
511 */
512static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
513 n >>= 8;
514 return ((n << 1) - (n | (n >> 8))) >> 8;
515}
516
517void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
518 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000519 SkASSERT(count > 1);
520
reed@android.com1c12abe2009-07-02 15:01:02 +0000521 // need to apply paintAlpha to our two endpoints
522 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
523 SkFixed da;
524 {
525 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
526 da = SkIntToFixed(tmp - a) / (count - 1);
527 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000528
reed@android.com1c12abe2009-07-02 15:01:02 +0000529 SkFixed r = SkColorGetR(c0);
530 SkFixed g = SkColorGetG(c0);
531 SkFixed b = SkColorGetB(c0);
532 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
533 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
534 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000535
536 a = SkIntToFixed(a) + 0x8000;
537 r = SkIntToFixed(r) + 0x8000;
538 g = SkIntToFixed(g) + 0x8000;
539 b = SkIntToFixed(b) + 0x8000;
540
541 do {
reed@google.com0b8b3bb2011-06-30 21:06:22 +0000542 cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
543 cache[kCache32Count] = SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
544 dither_fixed_to_8(r),
545 dither_fixed_to_8(g),
546 dither_fixed_to_8(b));
reed@google.com55b8e8c2011-01-13 16:22:35 +0000547 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000548 a += da;
549 r += dr;
550 g += dg;
551 b += db;
552 } while (--count != 0);
553}
554
reed@android.com41bccf52009-04-03 13:33:51 +0000555static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000556 SkASSERT((unsigned)x <= SK_Fixed1);
557 return x - (x >> 16);
558}
559
reed@android.com200645d2009-12-14 16:41:57 +0000560static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000561 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000562 if (6 == bits) {
563 return (x << 10) | (x << 4) | (x >> 2);
564 }
565 if (8 == bits) {
566 return (x << 8) | x;
567 }
568 sk_throw();
569 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570}
571
reed@google.com7c2f27d2011-03-07 19:29:00 +0000572const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000573 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000574 // double the count for dither entries
575 const int entryCount = kCache16Count * 2;
576 const size_t allocSize = sizeof(uint16_t) * entryCount;
577
reed@android.com3c9b2a42009-08-27 19:28:37 +0000578 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000579 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000580 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000582 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000583 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000584 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000585 Rec* rec = fRecs;
586 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000587 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000588 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000589 SkASSERT(nextIndex < kCache16Count);
590
591 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000592 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000593 prevIndex = nextIndex;
594 }
595 SkASSERT(prevIndex == kCache16Count - 1);
596 }
597
reed@android.com41bccf52009-04-03 13:33:51 +0000598 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000599 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000600 uint16_t* linear = fCache16; // just computed linear data
601 uint16_t* mapped = fCache16Storage; // storage for mapped data
602 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000603 for (int i = 0; i < kCache16Count; i++) {
604 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000605 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000606 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000607 }
608 sk_free(fCache16);
609 fCache16 = fCache16Storage;
610 }
611 }
612 return fCache16;
613}
614
reed@google.com7c2f27d2011-03-07 19:29:00 +0000615const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000616 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000617 // double the count for dither entries
618 const int entryCount = kCache32Count * 2;
619 const size_t allocSize = sizeof(SkPMColor) * entryCount;
620
reed@google.comdc731fd2010-12-23 15:19:47 +0000621 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000622 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
623 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000624 }
625 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000626 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000627 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
628 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000629 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630 Rec* rec = fRecs;
631 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000632 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
634 SkASSERT(nextIndex < kCache32Count);
635
636 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000637 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
638 fOrigColors[i],
639 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000640 prevIndex = nextIndex;
641 }
642 SkASSERT(prevIndex == kCache32Count - 1);
643 }
644
reed@android.com41bccf52009-04-03 13:33:51 +0000645 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000646 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000647 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000649 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000651 for (int i = 0; i < kCache32Count; i++) {
652 int index = map->mapUnit16((i << 8) | i) >> 8;
653 mapped[i] = linear[index];
654 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000655 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000656 fCache32PixelRef->unref();
657 fCache32PixelRef = newPR;
658 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659 }
660 }
661 return fCache32;
662}
663
reed@google.comdc731fd2010-12-23 15:19:47 +0000664/*
665 * Because our caller might rebuild the same (logically the same) gradient
666 * over and over, we'd like to return exactly the same "bitmap" if possible,
667 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
668 * To do that, we maintain a private cache of built-bitmaps, based on our
669 * colors and positions. Note: we don't try to flatten the fMapper, so if one
670 * is present, we skip the cache for now.
671 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000672void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.com95eed982011-07-05 17:01:56 +0000673 // our caller assumes no external alpha, so we ensure that our cache is
674 // built with 0xFF
675 this->setCacheAlpha(0xFF);
676
reed@google.comdc731fd2010-12-23 15:19:47 +0000677 // don't have a way to put the mapper into our cache-key yet
678 if (fMapper) {
679 // force our cahce32pixelref to be built
680 (void)this->getCache32();
681 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
682 bitmap->setPixelRef(fCache32PixelRef);
683 return;
684 }
685
686 // build our key: [numColors + colors[] + {positions[]} ]
687 int count = 1 + fColorCount;
688 if (fColorCount > 2) {
689 count += fColorCount - 1; // fRecs[].fPos
690 }
691
692 SkAutoSTMalloc<16, int32_t> storage(count);
693 int32_t* buffer = storage.get();
694
695 *buffer++ = fColorCount;
696 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
697 buffer += fColorCount;
698 if (fColorCount > 2) {
699 for (int i = 1; i < fColorCount; i++) {
700 *buffer++ = fRecs[i].fPos;
701 }
702 }
703 SkASSERT(buffer - storage.get() == count);
704
705 ///////////////////////////////////
706
707 static SkMutex gMutex;
708 static SkBitmapCache* gCache;
709 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
710 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
711 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000712
reed@google.comdc731fd2010-12-23 15:19:47 +0000713 if (NULL == gCache) {
714 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
715 }
716 size_t size = count * sizeof(int32_t);
717
718 if (!gCache->find(storage.get(), size, bitmap)) {
719 // force our cahce32pixelref to be built
720 (void)this->getCache32();
721 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
722 bitmap->setPixelRef(fCache32PixelRef);
723
724 gCache->add(storage.get(), size, *bitmap);
725 }
726}
727
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000728void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
729 if (info) {
730 if (info->fColorCount >= fColorCount) {
731 if (info->fColors) {
732 memcpy(info->fColors, fOrigColors,
733 fColorCount * sizeof(SkColor));
734 }
735 if (info->fColorOffsets) {
736 if (fColorCount == 2) {
737 info->fColorOffsets[0] = 0;
738 info->fColorOffsets[1] = SK_Scalar1;
739 } else if (fColorCount > 2) {
740 for (int i = 0; i < fColorCount; i++)
741 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
742 }
743 }
744 }
745 info->fColorCount = fColorCount;
746 info->fTileMode = fTileMode;
747 }
748}
749
reed@google.com61eb0402011-04-15 12:11:12 +0000750///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751
reed@android.com41bccf52009-04-03 13:33:51 +0000752static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753 SkVector vec = pts[1] - pts[0];
754 SkScalar mag = vec.length();
755 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
756
757 vec.scale(inv);
758 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
759 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
760 matrix->postScale(inv, inv);
761}
762
763///////////////////////////////////////////////////////////////////////////////
764
765class Linear_Gradient : public Gradient_Shader {
766public:
767 Linear_Gradient(const SkPoint pts[2],
768 const SkColor colors[], const SkScalar pos[], int colorCount,
769 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000770 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
771 fStart(pts[0]),
772 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000773 {
774 pts_to_unit_matrix(pts, &fPtsToUnit);
775 }
reed@android.com9b46e772009-06-05 12:24:41 +0000776
reed@android.com5119bdb2009-06-12 21:27:03 +0000777 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
779 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000780 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*,
reed@google.com7c2f27d2011-03-07 19:29:00 +0000781 TileMode*, SkScalar* twoPointRadialParams) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000782 virtual GradientType asAGradient(GradientInfo* info) const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783
reed@google.com55b8e8c2011-01-13 16:22:35 +0000784 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785 return SkNEW_ARGS(Linear_Gradient, (buffer));
786 }
787
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000788 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
789 this->INHERITED::flatten(buffer);
790 buffer.writeScalar(fStart.fX);
791 buffer.writeScalar(fStart.fY);
792 buffer.writeScalar(fEnd.fX);
793 buffer.writeScalar(fEnd.fY);
794 }
795
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000797 Linear_Gradient(SkFlattenableReadBuffer& buffer)
798 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +0000799 fStart(unflatten_point(buffer)),
800 fEnd(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000801 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802 virtual Factory getFactory() { return CreateProc; }
803
804private:
805 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000806 const SkPoint fStart;
807 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808};
809
reed@android.com5119bdb2009-06-12 21:27:03 +0000810bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
811 const SkMatrix& matrix) {
812 if (!this->INHERITED::setContext(device, paint, matrix)) {
813 return false;
814 }
815
816 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
817 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000818 fFlags |= SkShader::kConstInY32_Flag;
819 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
820 // only claim this if we do have a 16bit mode (i.e. none of our
821 // colors have alpha), and if we are not dithering (which obviously
822 // is not const in Y).
823 fFlags |= SkShader::kConstInY16_Flag;
824 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000825 }
826 return true;
827}
828
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@google.com61eb0402011-04-15 12:11:12 +0000830static inline bool no_need_for_clamp(int fx, int dx, int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831 SkASSERT(count > 0);
832 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
833}
834
reed@google.com5eb158d2011-04-15 15:50:34 +0000835#include "SkClampRange.h"
836
837#define NO_CHECK_ITER \
reed@google.com13659f12011-04-18 19:59:38 +0000838 do { \
839 unsigned fi = fx >> 8; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000840 SkASSERT(fi <= 0xFF); \
841 fx += dx; \
842 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000843 toggle ^= TOGGLE_MASK; \
844 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000845
846
reed@google.com61eb0402011-04-15 12:11:12 +0000847void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848 SkASSERT(count > 0);
849
850 SkPoint srcPt;
851 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
852 TileProc proc = fTileProc;
853 const SkPMColor* cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000854#ifdef USE_DITHER_32BIT_GRADIENT
855 int toggle = ((x ^ y) & 1) << kCache32Bits;
856 const int TOGGLE_MASK = (1 << kCache32Bits);
857#else
858 int toggle = 0;
859 const int TOGGLE_MASK = 0;
860#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861
reed@android.comc552a432009-06-12 20:02:50 +0000862 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000863 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
864 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
866
reed@android.comc552a432009-06-12 20:02:50 +0000867 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 SkFixed dxStorage[1];
869 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
870 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000871 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
873 dx = SkScalarToFixed(fDstToIndex.getScaleX());
874 }
875
reed@android.comc552a432009-06-12 20:02:50 +0000876 if (SkFixedNearlyZero(dx)) {
877 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 unsigned fi = proc(fx);
879 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000880 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000882 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000883 SkClampRange range;
884 range.init(fx, dx, count, 0, 0xFF);
885
886 if ((count = range.fCount0) > 0) {
887 sk_memset32_dither(dstC,
888 cache[toggle + range.fV0],
889 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
890 count);
891 dstC += count;
892 }
893 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +0000894 int unroll = count >> 3;
895 fx = range.fFx1;
896 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +0000897 NO_CHECK_ITER; NO_CHECK_ITER;
898 NO_CHECK_ITER; NO_CHECK_ITER;
899 NO_CHECK_ITER; NO_CHECK_ITER;
900 NO_CHECK_ITER; NO_CHECK_ITER;
901 }
902 if ((count &= 7) > 0) {
903 do {
904 NO_CHECK_ITER;
905 } while (--count != 0);
906 }
907 }
908 if ((count = range.fCount2) > 0) {
909 sk_memset32_dither(dstC,
910 cache[toggle + range.fV1],
911 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
912 count);
913 }
reed@android.comc552a432009-06-12 20:02:50 +0000914 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000915 do {
916 unsigned fi = mirror_8bits(fx >> 8);
917 SkASSERT(fi <= 0xFF);
918 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000919 *dstC++ = cache[toggle + fi];
920 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000921 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000922 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000923 SkASSERT(proc == repeat_tileproc);
924 do {
925 unsigned fi = repeat_8bits(fx >> 8);
926 SkASSERT(fi <= 0xFF);
927 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000928 *dstC++ = cache[toggle + fi];
929 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000930 } while (--count != 0);
931 }
reed@android.comc552a432009-06-12 20:02:50 +0000932 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000933 SkScalar dstX = SkIntToScalar(x);
934 SkScalar dstY = SkIntToScalar(y);
935 do {
936 dstProc(fDstToIndex, dstX, dstY, &srcPt);
937 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
938 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000939 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
940 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000941 dstX += SK_Scalar1;
942 } while (--count != 0);
943 }
944}
945
reed@google.com55b8e8c2011-01-13 16:22:35 +0000946SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000947 SkMatrix* matrix,
948 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000949 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000950 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000951 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000952 }
953 if (matrix) {
954 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
955 matrix->preConcat(fPtsToUnit);
956 }
957 if (xy) {
958 xy[0] = fTileMode;
959 xy[1] = kClamp_TileMode;
960 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000961 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962}
963
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000964SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
965 if (info) {
966 commonAsAGradient(info);
967 info->fPoint[0] = fStart;
968 info->fPoint[1] = fEnd;
969 }
970 return kLinear_GradientType;
971}
972
reed@android.com3c9b2a42009-08-27 19:28:37 +0000973static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
974 int count) {
975 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 *dst++ = value;
977 count -= 1;
978 SkTSwap(value, other);
979 }
980
981 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000982
reed@android.com3c9b2a42009-08-27 19:28:37 +0000983 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000985 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000987
reed@google.com5eb158d2011-04-15 15:50:34 +0000988#define NO_CHECK_ITER_16 \
reed@google.com13659f12011-04-18 19:59:38 +0000989 do { \
990 unsigned fi = fx >> kCache16Shift; \
reed@google.com5eb158d2011-04-15 15:50:34 +0000991 SkASSERT(fi <= kCache16Mask); \
992 fx += dx; \
993 *dstC++ = cache[toggle + fi]; \
reed@google.com13659f12011-04-18 19:59:38 +0000994 toggle ^= TOGGLE_MASK; \
995 } while (0)
reed@google.com5eb158d2011-04-15 15:50:34 +0000996
997
reed@google.com61eb0402011-04-15 12:11:12 +0000998void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000999 SkASSERT(count > 0);
1000
1001 SkPoint srcPt;
1002 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1003 TileProc proc = fTileProc;
1004 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@google.com5eb158d2011-04-15 15:50:34 +00001006 const int TOGGLE_MASK = (1 << kCache32Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001008 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001009 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1010 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1012
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001013 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 SkFixed dxStorage[1];
1015 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
1016 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001017 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001018 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1019 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1020 }
1021
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001022 if (SkFixedNearlyZero(dx)) {
1023 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +00001024 unsigned fi = proc(fx) >> kCache16Shift;
1025 SkASSERT(fi <= kCache16Mask);
reed@google.com5eb158d2011-04-15 15:50:34 +00001026 dither_memset16(dstC, cache[toggle + fi],
1027 cache[(toggle ^ TOGGLE_MASK) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001028 } else if (proc == clamp_tileproc) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001029 SkClampRange range;
1030 range.init(fx, dx, count, 0, kCache16Mask);
1031
1032 if ((count = range.fCount0) > 0) {
1033 dither_memset16(dstC,
1034 cache[toggle + range.fV0],
1035 cache[(toggle ^ TOGGLE_MASK) + range.fV0],
1036 count);
1037 dstC += count;
1038 }
1039 if ((count = range.fCount1) > 0) {
reed@google.com13659f12011-04-18 19:59:38 +00001040 int unroll = count >> 3;
1041 fx = range.fFx1;
1042 for (int i = 0; i < unroll; i++) {
reed@google.com5eb158d2011-04-15 15:50:34 +00001043 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1044 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1045 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1046 NO_CHECK_ITER_16; NO_CHECK_ITER_16;
1047 }
1048 if ((count &= 7) > 0) {
1049 do {
1050 NO_CHECK_ITER_16;
1051 } while (--count != 0);
1052 }
1053 }
1054 if ((count = range.fCount2) > 0) {
1055 dither_memset16(dstC,
1056 cache[toggle + range.fV1],
1057 cache[(toggle ^ TOGGLE_MASK) + range.fV1],
1058 count);
1059 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001060 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061 do {
reed@android.com200645d2009-12-14 16:41:57 +00001062 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001063 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001064 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001066 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001067 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001068 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 SkASSERT(proc == repeat_tileproc);
1070 do {
reed@android.com200645d2009-12-14 16:41:57 +00001071 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +00001072 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001073 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074 *dstC++ = cache[toggle + fi];
reed@google.com5eb158d2011-04-15 15:50:34 +00001075 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001076 } while (--count != 0);
1077 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +00001078 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001079 SkScalar dstX = SkIntToScalar(x);
1080 SkScalar dstY = SkIntToScalar(y);
1081 do {
1082 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1083 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
1084 SkASSERT(fi <= 0xFFFF);
1085
reed@android.com512a8762009-12-14 15:25:36 +00001086 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001087 *dstC++ = cache[toggle + index];
reed@google.com5eb158d2011-04-15 15:50:34 +00001088 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001089
1090 dstX += SK_Scalar1;
1091 } while (--count != 0);
1092 }
1093}
1094
1095///////////////////////////////////////////////////////////////////////////////
1096
1097#define kSQRT_TABLE_BITS 11
1098#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
1099
1100#include "SkRadialGradient_Table.h"
1101
1102#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
1103
1104#include <stdio.h>
1105
reed@google.com61eb0402011-04-15 12:11:12 +00001106void SkRadialGradient_BuildTable() {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001107 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1108
1109 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1110 SkASSERT(file);
1111 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1112
reed@google.com61eb0402011-04-15 12:11:12 +00001113 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
1114 if ((i & 15) == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001115 ::fprintf(file, "\t");
reed@google.com61eb0402011-04-15 12:11:12 +00001116 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001117
1118 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1119
1120 ::fprintf(file, "0x%02X", value);
reed@google.com61eb0402011-04-15 12:11:12 +00001121 if (i < kSQRT_TABLE_SIZE-1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122 ::fprintf(file, ", ");
reed@google.com61eb0402011-04-15 12:11:12 +00001123 }
1124 if ((i & 15) == 15) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001125 ::fprintf(file, "\n");
reed@google.com61eb0402011-04-15 12:11:12 +00001126 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127 }
1128 ::fprintf(file, "};\n");
1129 ::fclose(file);
1130}
1131
1132#endif
1133
1134
reed@google.com61eb0402011-04-15 12:11:12 +00001135static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
1136 SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001137 SkScalar inv = SkScalarInvert(radius);
1138
1139 matrix->setTranslate(-center.fX, -center.fY);
1140 matrix->postScale(inv, inv);
1141}
1142
1143class Radial_Gradient : public Gradient_Shader {
1144public:
1145 Radial_Gradient(const SkPoint& center, SkScalar radius,
1146 const SkColor colors[], const SkScalar pos[], int colorCount,
1147 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001148 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1149 fCenter(center),
1150 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001151 {
1152 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1153 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1154
1155 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1156 }
reed@google.com61eb0402011-04-15 12:11:12 +00001157
1158 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001159 SkASSERT(count > 0);
1160
1161 SkPoint srcPt;
1162 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1163 TileProc proc = fTileProc;
1164 const SkPMColor* cache = this->getCache32();
1165
reed@google.com61eb0402011-04-15 12:11:12 +00001166 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001167 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1168 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001169 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1170 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1171
reed@google.com61eb0402011-04-15 12:11:12 +00001172 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173 SkFixed storage[2];
1174 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1175 dx = storage[0];
1176 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00001177 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001178 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1179 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1180 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1181 }
1182
reed@google.com61eb0402011-04-15 12:11:12 +00001183 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001184 const uint8_t* sqrt_table = gSqrt8Table;
1185 fx >>= 1;
1186 dx >>= 1;
1187 fy >>= 1;
1188 dy >>= 1;
1189 do {
1190 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1191 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1192 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1193 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1194 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1195 fx += dx;
1196 fy += dy;
1197 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001198 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001200 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1201 if (magnitudeSquared < 0) // Overflow.
1202 magnitudeSquared = SK_FixedMax;
1203 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 unsigned fi = mirror_tileproc(dist);
1205 SkASSERT(fi <= 0xFFFF);
1206 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1207 fx += dx;
1208 fy += dy;
1209 } while (--count != 0);
reed@google.com61eb0402011-04-15 12:11:12 +00001210 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211 SkASSERT(proc == repeat_tileproc);
1212 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001213 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1214 if (magnitudeSquared < 0) // Overflow.
1215 magnitudeSquared = SK_FixedMax;
1216 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217 unsigned fi = repeat_tileproc(dist);
1218 SkASSERT(fi <= 0xFFFF);
1219 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1220 fx += dx;
1221 fy += dy;
1222 } while (--count != 0);
1223 }
reed@google.com61eb0402011-04-15 12:11:12 +00001224 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225 SkScalar dstX = SkIntToScalar(x);
1226 SkScalar dstY = SkIntToScalar(y);
1227 do {
1228 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1229 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1230 SkASSERT(fi <= 0xFFFF);
1231 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1232 dstX += SK_Scalar1;
1233 } while (--count != 0);
1234 }
1235 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001236
1237 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238 SkASSERT(count > 0);
1239
1240 SkPoint srcPt;
1241 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1242 TileProc proc = fTileProc;
1243 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245
reed@android.com3c9b2a42009-08-27 19:28:37 +00001246 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001247 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1248 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1250 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1251
reed@android.com3c9b2a42009-08-27 19:28:37 +00001252 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 SkFixed storage[2];
1254 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1255 dx = storage[0];
1256 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001257 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1259 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1260 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1261 }
1262
reed@android.com3c9b2a42009-08-27 19:28:37 +00001263 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 const uint8_t* sqrt_table = gSqrt8Table;
1265
1266 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1267 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1268 precision, but that appears to be visually OK. If we decide this is OK for
1269 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1270 to avoid having to do these extra shifts each time.
1271 */
1272 fx >>= 1;
1273 dx >>= 1;
1274 fy >>= 1;
1275 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001276 if (dy == 0) { // might perform this check for the other modes, but the win will be a smaller % of the total
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1278 fy *= fy;
1279 do {
1280 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1281 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1282 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1283 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1285 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001287 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 do {
1289 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1290 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1291 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1292 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1293 fx += dx;
1294 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001295 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1296 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297 } while (--count != 0);
1298 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001299 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300 do {
1301 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1302 unsigned fi = mirror_tileproc(dist);
1303 SkASSERT(fi <= 0xFFFF);
1304 fx += dx;
1305 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1307 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001309 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310 SkASSERT(proc == repeat_tileproc);
1311 do {
1312 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1313 unsigned fi = repeat_tileproc(dist);
1314 SkASSERT(fi <= 0xFFFF);
1315 fx += dx;
1316 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001317 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1318 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 } while (--count != 0);
1320 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001321 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322 SkScalar dstX = SkIntToScalar(x);
1323 SkScalar dstY = SkIntToScalar(y);
1324 do {
1325 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1326 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1327 SkASSERT(fi <= 0xFFFF);
1328
1329 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 *dstC++ = cache[toggle + index];
1331 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001332
1333 dstX += SK_Scalar1;
1334 } while (--count != 0);
1335 }
1336 }
1337
reed@google.com55b8e8c2011-01-13 16:22:35 +00001338 virtual BitmapType asABitmap(SkBitmap* bitmap,
1339 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001340 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001341 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001342 if (bitmap) {
1343 this->commonAsABitmap(bitmap);
1344 }
1345 if (matrix) {
1346 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1347 matrix->preConcat(fPtsToUnit);
1348 }
1349 if (xy) {
1350 xy[0] = fTileMode;
1351 xy[1] = kClamp_TileMode;
1352 }
1353 return kRadial_BitmapType;
1354 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001355 virtual GradientType asAGradient(GradientInfo* info) const {
1356 if (info) {
1357 commonAsAGradient(info);
1358 info->fPoint[0] = fCenter;
1359 info->fRadius[0] = fRadius;
1360 }
1361 return kRadial_GradientType;
1362 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001363
1364 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365 return SkNEW_ARGS(Radial_Gradient, (buffer));
1366 }
1367
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001368 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1369 this->INHERITED::flatten(buffer);
1370 buffer.writeScalar(fCenter.fX);
1371 buffer.writeScalar(fCenter.fY);
1372 buffer.writeScalar(fRadius);
1373 }
1374
reed@android.com8a1c16f2008-12-17 15:59:43 +00001375protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001376 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1377 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001378 fCenter(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001379 fRadius(buffer.readScalar()) {
1380 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001381 virtual Factory getFactory() { return CreateProc; }
1382
1383private:
1384 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001385 const SkPoint fCenter;
1386 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001387};
1388
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001389/* Two-point radial gradients are specified by two circles, each with a center
1390 point and radius. The gradient can be considered to be a series of
1391 concentric circles, with the color interpolated from the start circle
1392 (at t=0) to the end circle (at t=1).
1393
1394 For each point (x, y) in the span, we want to find the
1395 interpolated circle that intersects that point. The center
1396 of the desired circle (Cx, Cy) falls at some distance t
1397 along the line segment between the start point (Sx, Sy) and
1398 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001399
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001400 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1401 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001402
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001403 The radius of the desired circle (r) is also a linear interpolation t
1404 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001405
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001406 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001407
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001408 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001409
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001410 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001411
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001412 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001413
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001414 (x - ((1 - t) * Sx + t * Ex))^2
1415 + (y - ((1 - t) * Sy + t * Ey))^2
1416 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001417
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001418 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001419
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001420 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1421 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1422 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001423
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001424 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1425
1426 [Dx^2 + Dy^2 - Dr^2)] * t^2
1427 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1428 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001429
1430 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001431 possible circles on which the point may fall. Solving for t yields
1432 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001433
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001434 If a<0, the start circle is entirely contained in the
1435 end circle, and one of the roots will be <0 or >1 (off the line
1436 segment). If a>0, the start circle falls at least partially
1437 outside the end circle (or vice versa), and the gradient
1438 defines a "tube" where a point may be on one circle (on the
1439 inside of the tube) or the other (outside of the tube). We choose
1440 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001441
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001442 In order to keep the math to within the limits of fixed point,
1443 we divide the entire quadratic by Dr^2, and replace
1444 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001445
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001446 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1447 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1448 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001449
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001450 (x' and y' are computed by appending the subtract and scale to the
1451 fDstToIndex matrix in the constructor).
1452
1453 Since the 'A' component of the quadratic is independent of x' and y', it
1454 is precomputed in the constructor. Since the 'B' component is linear in
1455 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001456 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001457 a perspective projection), it must be computed in the loop.
1458
1459*/
1460
reed@google.com84e9c082011-04-13 17:44:24 +00001461static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1462 SkScalar sr2d2, SkScalar foura,
1463 SkScalar oneOverTwoA, bool posRoot) {
1464 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
reed@google.com8871a842011-06-23 15:07:04 +00001465 if (0 == foura) {
1466 return SkScalarToFixed(SkScalarDiv(-c, b));
1467 }
1468
reed@google.com84e9c082011-04-13 17:44:24 +00001469 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001470 if (discrim < 0) {
1471 discrim = -discrim;
1472 }
reed@google.com84e9c082011-04-13 17:44:24 +00001473 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1474 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001475 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001476 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001477 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001478 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001479 }
reed@google.com84e9c082011-04-13 17:44:24 +00001480 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001481}
1482
1483class Two_Point_Radial_Gradient : public Gradient_Shader {
1484public:
1485 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1486 const SkPoint& end, SkScalar endRadius,
1487 const SkColor colors[], const SkScalar pos[],
1488 int colorCount, SkShader::TileMode mode,
1489 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001490 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1491 fCenter1(start),
1492 fCenter2(end),
1493 fRadius1(startRadius),
1494 fRadius2(endRadius) {
1495 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001496 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001497
1498 virtual BitmapType asABitmap(SkBitmap* bitmap,
1499 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001500 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001501 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001502 if (bitmap) {
1503 this->commonAsABitmap(bitmap);
1504 }
1505 SkScalar diffL = 0; // just to avoid gcc warning
1506 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001507 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001508 SkScalarSquare(fDiff.fY));
1509 }
1510 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001511 if (diffL) {
1512 SkScalar invDiffL = SkScalarInvert(diffL);
1513 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1514 SkScalarMul(invDiffL, fDiff.fX));
1515 } else {
1516 matrix->reset();
1517 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001518 matrix->preConcat(fPtsToUnit);
1519 }
1520 if (xy) {
1521 xy[0] = fTileMode;
1522 xy[1] = kClamp_TileMode;
1523 }
1524 if (NULL != twoPointRadialParams) {
1525 twoPointRadialParams[0] = diffL;
1526 twoPointRadialParams[1] = fStartRadius;
1527 twoPointRadialParams[2] = fDiffRadius;
1528 }
1529 return kTwoPointRadial_BitmapType;
1530 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001531
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001532 virtual GradientType asAGradient(GradientInfo* info) const {
1533 if (info) {
1534 commonAsAGradient(info);
1535 info->fPoint[0] = fCenter1;
1536 info->fPoint[1] = fCenter2;
1537 info->fRadius[0] = fRadius1;
1538 info->fRadius[1] = fRadius2;
1539 }
1540 return kRadial2_GradientType;
1541 }
1542
reed@google.com61eb0402011-04-15 12:11:12 +00001543 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001544 SkASSERT(count > 0);
1545
1546 // Zero difference between radii: fill with transparent black.
1547 if (fDiffRadius == 0) {
1548 sk_bzero(dstC, count * sizeof(*dstC));
1549 return;
1550 }
1551 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1552 TileProc proc = fTileProc;
1553 const SkPMColor* cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001554
1555 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001556 bool posRoot = fDiffRadius < 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001557 if (fDstToIndexClass != kPerspective_MatrixClass) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001558 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001559 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1560 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001561 SkScalar dx, fx = srcPt.fX;
1562 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001563
reed@google.com61eb0402011-04-15 12:11:12 +00001564 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@google.com84e9c082011-04-13 17:44:24 +00001565 SkFixed fixedX, fixedY;
1566 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1567 dx = SkFixedToScalar(fixedX);
1568 dy = SkFixedToScalar(fixedY);
reed@google.com61eb0402011-04-15 12:11:12 +00001569 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001570 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001571 dx = fDstToIndex.getScaleX();
1572 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001573 }
reed@google.com84e9c082011-04-13 17:44:24 +00001574 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1575 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1576 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1577 SkScalarMul(fDiff.fY, dy)) * 2;
reed@google.com61eb0402011-04-15 12:11:12 +00001578 if (proc == clamp_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001579 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001580 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001581 SkFixed index = SkClampMax(t, 0xFFFF);
1582 SkASSERT(index <= 0xFFFF);
1583 *dstC++ = cache[index >> (16 - kCache32Bits)];
1584 fx += dx;
1585 fy += dy;
1586 b += db;
1587 }
reed@google.com61eb0402011-04-15 12:11:12 +00001588 } else if (proc == mirror_tileproc) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001589 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001590 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001591 SkFixed index = mirror_tileproc(t);
1592 SkASSERT(index <= 0xFFFF);
1593 *dstC++ = cache[index >> (16 - kCache32Bits)];
1594 fx += dx;
1595 fy += dy;
1596 b += db;
1597 }
reed@google.com61eb0402011-04-15 12:11:12 +00001598 } else {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001599 SkASSERT(proc == repeat_tileproc);
1600 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001601 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001602 SkFixed index = repeat_tileproc(t);
1603 SkASSERT(index <= 0xFFFF);
1604 *dstC++ = cache[index >> (16 - kCache32Bits)];
1605 fx += dx;
1606 fy += dy;
1607 b += db;
1608 }
1609 }
reed@google.com61eb0402011-04-15 12:11:12 +00001610 } else { // perspective case
reed@android.com6c59a172009-09-22 20:24:05 +00001611 SkScalar dstX = SkIntToScalar(x);
1612 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001613 for (; count > 0; --count) {
1614 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001615 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001616 SkScalar fx = srcPt.fX;
1617 SkScalar fy = srcPt.fY;
1618 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1619 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1620 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001621 SkFixed index = proc(t);
1622 SkASSERT(index <= 0xFFFF);
1623 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001624 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001625 }
1626 }
1627 }
1628
reed@android.com6c59a172009-09-22 20:24:05 +00001629 virtual bool setContext(const SkBitmap& device,
1630 const SkPaint& paint,
1631 const SkMatrix& matrix) {
1632 if (!this->INHERITED::setContext(device, paint, matrix)) {
1633 return false;
1634 }
1635
1636 // we don't have a span16 proc
1637 fFlags &= ~kHasSpan16_Flag;
1638 return true;
1639 }
1640
reed@google.com55b8e8c2011-01-13 16:22:35 +00001641 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001642 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1643 }
1644
reed@android.combcfc7332009-11-10 16:19:39 +00001645 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1646 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001647 buffer.writeScalar(fCenter1.fX);
1648 buffer.writeScalar(fCenter1.fY);
1649 buffer.writeScalar(fCenter2.fX);
1650 buffer.writeScalar(fCenter2.fY);
1651 buffer.writeScalar(fRadius1);
1652 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001653 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001654
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001655protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001656 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001657 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001658 fCenter1(unflatten_point(buffer)),
1659 fCenter2(unflatten_point(buffer)),
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001660 fRadius1(buffer.readScalar()),
1661 fRadius2(buffer.readScalar()) {
1662 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001663 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001664 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001665
1666private:
1667 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001668 const SkPoint fCenter1;
1669 const SkPoint fCenter2;
1670 const SkScalar fRadius1;
1671 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001672 SkPoint fDiff;
1673 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001674
1675 void init() {
1676 fDiff = fCenter1 - fCenter2;
1677 fDiffRadius = fRadius2 - fRadius1;
1678 SkScalar inv = SkScalarInvert(fDiffRadius);
1679 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1680 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1681 fStartRadius = SkScalarMul(fRadius1, inv);
1682 fSr2D2 = SkScalarSquare(fStartRadius);
1683 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
reed@google.com21031e92011-06-27 18:38:27 +00001684 fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001685
1686 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1687 fPtsToUnit.postScale(inv, inv);
1688 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001689};
1690
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691///////////////////////////////////////////////////////////////////////////////
1692
1693class Sweep_Gradient : public Gradient_Shader {
1694public:
1695 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1696 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001697 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1698 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 {
1700 fPtsToUnit.setTranslate(-cx, -cy);
1701 }
1702 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1703 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001704
1705 virtual BitmapType asABitmap(SkBitmap* bitmap,
1706 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001707 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001708 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001709 if (bitmap) {
1710 this->commonAsABitmap(bitmap);
1711 }
1712 if (matrix) {
1713 *matrix = fPtsToUnit;
1714 }
1715 if (xy) {
1716 xy[0] = fTileMode;
1717 xy[1] = kClamp_TileMode;
1718 }
1719 return kSweep_BitmapType;
1720 }
1721
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001722 virtual GradientType asAGradient(GradientInfo* info) const {
1723 if (info) {
1724 commonAsAGradient(info);
1725 info->fPoint[0] = fCenter;
1726 }
1727 return kSweep_GradientType;
1728 }
1729
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1731 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1732 }
1733
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001734 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1735 this->INHERITED::flatten(buffer);
1736 buffer.writeScalar(fCenter.fX);
1737 buffer.writeScalar(fCenter.fY);
1738 }
1739
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001741 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1742 : Gradient_Shader(buffer),
tomhudson@google.com9ce767c2011-04-25 20:49:39 +00001743 fCenter(unflatten_point(buffer)) {
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001744 }
1745
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 virtual Factory getFactory() { return CreateProc; }
1747
1748private:
1749 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001750 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751};
1752
1753#ifdef COMPUTE_SWEEP_TABLE
1754#define PI 3.14159265
1755static bool gSweepTableReady;
1756static uint8_t gSweepTable[65];
1757
1758/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1759 We scale the results to [0..32]
1760*/
reed@google.com61eb0402011-04-15 12:11:12 +00001761static const uint8_t* build_sweep_table() {
1762 if (!gSweepTableReady) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 const int N = 65;
1764 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001765
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 for (int i = 0; i < N; i++)
1767 {
1768 double arg = i / DENOM;
1769 double v = atan(arg);
1770 int iv = (int)round(v * DENOM * 2 / PI);
1771// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1772 printf("%d, ", iv);
1773 gSweepTable[i] = iv;
1774 }
1775 gSweepTableReady = true;
1776 }
1777 return gSweepTable;
1778}
1779#else
1780static const uint8_t gSweepTable[] = {
1781 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1782 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1783 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1784 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1785 32
1786};
1787static const uint8_t* build_sweep_table() { return gSweepTable; }
1788#endif
1789
1790// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1791// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1792// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1793
1794//unsigned div_64(int numer, int denom);
reed@google.com61eb0402011-04-15 12:11:12 +00001795static unsigned div_64(int numer, int denom) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796 SkASSERT(numer <= denom);
1797 SkASSERT(numer > 0);
1798 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001799
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 int nbits = SkCLZ(numer);
1801 int dbits = SkCLZ(denom);
1802 int bits = 6 - nbits + dbits;
1803 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001804
reed@google.com61eb0402011-04-15 12:11:12 +00001805 if (bits < 0) { // detect underflow
reed@android.com8a1c16f2008-12-17 15:59:43 +00001806 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001807 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808
1809 denom <<= dbits - 1;
1810 numer <<= nbits - 1;
1811
1812 unsigned result = 0;
1813
1814 // do the first one
reed@google.com61eb0402011-04-15 12:11:12 +00001815 if ((numer -= denom) >= 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 result = 1;
reed@google.com61eb0402011-04-15 12:11:12 +00001817 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 numer += denom;
reed@google.com61eb0402011-04-15 12:11:12 +00001819 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001820
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821 // Now fall into our switch statement if there are more bits to compute
reed@google.com61eb0402011-04-15 12:11:12 +00001822 if (bits > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 // make room for the rest of the answer bits
1824 result <<= bits;
1825 switch (bits) {
1826 case 6:
1827 if ((numer = (numer << 1) - denom) >= 0)
1828 result |= 32;
1829 else
1830 numer += denom;
1831 case 5:
1832 if ((numer = (numer << 1) - denom) >= 0)
1833 result |= 16;
1834 else
1835 numer += denom;
1836 case 4:
1837 if ((numer = (numer << 1) - denom) >= 0)
1838 result |= 8;
1839 else
1840 numer += denom;
1841 case 3:
1842 if ((numer = (numer << 1) - denom) >= 0)
1843 result |= 4;
1844 else
1845 numer += denom;
1846 case 2:
1847 if ((numer = (numer << 1) - denom) >= 0)
1848 result |= 2;
1849 else
1850 numer += denom;
1851 case 1:
1852 default: // not strictly need, but makes GCC make better ARM code
1853 if ((numer = (numer << 1) - denom) >= 0)
1854 result |= 1;
1855 else
1856 numer += denom;
1857 }
1858 }
1859 return result;
1860}
1861
1862// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
reed@google.com61eb0402011-04-15 12:11:12 +00001863static unsigned atan_0_90(SkFixed y, SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864#ifdef SK_DEBUG
1865 {
1866 static bool gOnce;
reed@google.com61eb0402011-04-15 12:11:12 +00001867 if (!gOnce) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868 gOnce = true;
1869 SkASSERT(div_64(55, 55) == 64);
1870 SkASSERT(div_64(128, 256) == 32);
1871 SkASSERT(div_64(2326528, 4685824) == 31);
1872 SkASSERT(div_64(753664, 5210112) == 9);
1873 SkASSERT(div_64(229376, 4882432) == 3);
1874 SkASSERT(div_64(2, 64) == 2);
1875 SkASSERT(div_64(1, 64) == 1);
1876 // test that we handle underflow correctly
1877 SkASSERT(div_64(12345, 0x54321234) == 0);
1878 }
1879 }
1880#endif
1881
1882 SkASSERT(y > 0 && x > 0);
1883 const uint8_t* table = build_sweep_table();
1884
1885 unsigned result;
1886 bool swap = (x < y);
reed@google.com61eb0402011-04-15 12:11:12 +00001887 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888 // first part of the atan(v) = PI/2 - atan(1/v) identity
1889 // since our div_64 and table want v <= 1, where v = y/x
1890 SkTSwap<SkFixed>(x, y);
1891 }
1892
1893 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001894
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895#ifdef SK_DEBUG
1896 {
1897 unsigned result2 = SkDivBits(y, x, 6);
1898 SkASSERT(result2 == result ||
1899 (result == 1 && result2 == 0));
1900 }
1901#endif
1902
1903 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1904 result = table[result];
1905
reed@google.com61eb0402011-04-15 12:11:12 +00001906 if (swap) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 // complete the atan(v) = PI/2 - atan(1/v) identity
1908 result = 64 - result;
1909 // pin to 63
1910 result -= result >> 6;
1911 }
1912
1913 SkASSERT(result <= 63);
1914 return result;
1915}
1916
1917// returns angle in a circle [0..2PI) -> [0..255]
reed@google.com61eb0402011-04-15 12:11:12 +00001918static unsigned SkATan2_255(SkFixed y, SkFixed x) {
1919 if (x == 0) {
1920 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921 return 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001922 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001923 return y < 0 ? 192 : 64;
1924 }
reed@google.com61eb0402011-04-15 12:11:12 +00001925 if (y == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926 return x < 0 ? 128 : 0;
reed@google.com61eb0402011-04-15 12:11:12 +00001927 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001928
reed@android.com8a1c16f2008-12-17 15:59:43 +00001929 /* Find the right quadrant for x,y
1930 Since atan_0_90 only handles the first quadrant, we rotate x,y
1931 appropriately before calling it, and then add the right amount
1932 to account for the real quadrant.
1933 quadrant 0 : add 0 | x > 0 && y > 0
1934 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1935 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1936 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001937
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 map x<0 to (1 << 6)
1939 map y<0 to (3 << 6)
1940 add = map_x ^ map_y
1941 */
1942 int xsign = x >> 31;
1943 int ysign = y >> 31;
1944 int add = ((-xsign) ^ (ysign & 3)) << 6;
1945
1946#ifdef SK_DEBUG
1947 if (0 == add)
1948 SkASSERT(x > 0 && y > 0);
1949 else if (64 == add)
1950 SkASSERT(x < 0 && y > 0);
1951 else if (128 == add)
1952 SkASSERT(x < 0 && y < 0);
1953 else if (192 == add)
1954 SkASSERT(x > 0 && y < 0);
1955 else
1956 SkASSERT(!"bad value for add");
1957#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00001958
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1960 where we need to rotate x,y by 90 or -90
1961 */
1962 x = (x ^ xsign) - xsign;
1963 y = (y ^ ysign) - ysign;
reed@google.com61eb0402011-04-15 12:11:12 +00001964 if (add & 64) { // quads 1 or 3 need to swap x,y
reed@android.com8a1c16f2008-12-17 15:59:43 +00001965 SkTSwap<SkFixed>(x, y);
reed@google.com61eb0402011-04-15 12:11:12 +00001966 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967
1968 unsigned result = add + atan_0_90(y, x);
1969 SkASSERT(result < 256);
1970 return result;
1971}
1972
reed@google.com61eb0402011-04-15 12:11:12 +00001973void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001974 SkMatrix::MapXYProc proc = fDstToIndexProc;
1975 const SkMatrix& matrix = fDstToIndex;
1976 const SkPMColor* cache = this->getCache32();
1977 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001978
reed@google.com61eb0402011-04-15 12:11:12 +00001979 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1981 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1982 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1983 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001984
reed@google.com61eb0402011-04-15 12:11:12 +00001985 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 SkFixed storage[2];
1987 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1988 &storage[0], &storage[1]);
1989 dx = storage[0];
1990 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00001991 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1993 dx = SkScalarToFixed(matrix.getScaleX());
1994 dy = SkScalarToFixed(matrix.getSkewY());
1995 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001996
reed@google.com61eb0402011-04-15 12:11:12 +00001997 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001998 *dstC++ = cache[SkATan2_255(fy, fx)];
1999 fx += dx;
2000 fy += dy;
2001 }
reed@google.com61eb0402011-04-15 12:11:12 +00002002 } else { // perspective case
2003 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002004 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2005 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002006
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2008 SkScalarToFixed(srcPt.fX));
2009 *dstC++ = cache[index];
2010 }
2011 }
2012}
2013
reed@google.com61eb0402011-04-15 12:11:12 +00002014void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015 SkMatrix::MapXYProc proc = fDstToIndexProc;
2016 const SkMatrix& matrix = fDstToIndex;
2017 const uint16_t* cache = this->getCache16();
2018 int toggle = ((x ^ y) & 1) << kCache16Bits;
2019 SkPoint srcPt;
2020
reed@google.com61eb0402011-04-15 12:11:12 +00002021 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002022 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2023 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
2024 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
2025 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002026
reed@google.com61eb0402011-04-15 12:11:12 +00002027 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002028 SkFixed storage[2];
2029 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
2030 &storage[0], &storage[1]);
2031 dx = storage[0];
2032 dy = storage[1];
reed@google.com61eb0402011-04-15 12:11:12 +00002033 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
2035 dx = SkScalarToFixed(matrix.getScaleX());
2036 dy = SkScalarToFixed(matrix.getSkewY());
2037 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00002038
reed@google.com61eb0402011-04-15 12:11:12 +00002039 for (; count > 0; --count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002040 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
2041 *dstC++ = cache[toggle + index];
2042 toggle ^= (1 << kCache16Bits);
2043 fx += dx;
2044 fy += dy;
2045 }
reed@google.com61eb0402011-04-15 12:11:12 +00002046 } else { // perspective case
2047 for (int stop = x + count; x < stop; x++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
2049 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00002050
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
2052 SkScalarToFixed(srcPt.fX));
2053 index >>= (8 - kCache16Bits);
2054 *dstC++ = cache[toggle + index];
2055 toggle ^= (1 << kCache16Bits);
2056 }
2057 }
2058}
2059
reed@google.com61eb0402011-04-15 12:11:12 +00002060///////////////////////////////////////////////////////////////////////////////
2061///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002062
2063// assumes colors is SkColor* and pos is SkScalar*
2064#define EXPAND_1_COLOR(count) \
2065 SkColor tmp[2]; \
2066 do { \
2067 if (1 == count) { \
2068 tmp[0] = tmp[1] = colors[0]; \
2069 colors = tmp; \
2070 pos = NULL; \
2071 count = 2; \
2072 } \
2073 } while (0)
2074
reed@google.com61eb0402011-04-15 12:11:12 +00002075SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
2076 const SkColor colors[],
2077 const SkScalar pos[], int colorCount,
2078 SkShader::TileMode mode,
2079 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002080 if (NULL == pts || NULL == colors || colorCount < 1) {
2081 return NULL;
2082 }
2083 EXPAND_1_COLOR(colorCount);
2084
reed@android.comab840b82009-07-01 17:00:03 +00002085 return SkNEW_ARGS(Linear_Gradient,
2086 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087}
2088
reed@google.com61eb0402011-04-15 12:11:12 +00002089SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
2090 const SkColor colors[],
2091 const SkScalar pos[], int colorCount,
2092 SkShader::TileMode mode,
2093 SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002094 if (radius <= 0 || NULL == colors || colorCount < 1) {
2095 return NULL;
2096 }
2097 EXPAND_1_COLOR(colorCount);
2098
reed@android.comab840b82009-07-01 17:00:03 +00002099 return SkNEW_ARGS(Radial_Gradient,
2100 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002101}
2102
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002103SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2104 SkScalar startRadius,
2105 const SkPoint& end,
2106 SkScalar endRadius,
2107 const SkColor colors[],
2108 const SkScalar pos[],
2109 int colorCount,
2110 SkShader::TileMode mode,
reed@google.com61eb0402011-04-15 12:11:12 +00002111 SkUnitMapper* mapper) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002112 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2113 return NULL;
2114 }
2115 EXPAND_1_COLOR(colorCount);
2116
2117 return SkNEW_ARGS(Two_Point_Radial_Gradient,
reed@google.com61eb0402011-04-15 12:11:12 +00002118 (start, startRadius, end, endRadius, colors, pos,
2119 colorCount, mode, mapper));
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002120}
2121
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2123 const SkColor colors[],
2124 const SkScalar pos[],
reed@google.com61eb0402011-04-15 12:11:12 +00002125 int count, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 if (NULL == colors || count < 1) {
2127 return NULL;
2128 }
2129 EXPAND_1_COLOR(count);
2130
2131 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2132}
2133
2134static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2135 Linear_Gradient::CreateProc);
2136
2137static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2138 Radial_Gradient::CreateProc);
2139
2140static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2141 Sweep_Gradient::CreateProc);
tomhudson@google.com938d6042011-04-27 13:57:03 +00002142
2143static SkFlattenable::Registrar
2144 gTwoPointRadialGradientReg("Two_Point_Radial_Gradient",
2145 Two_Point_Radial_Gradient::CreateProc);