blob: 2a5008d5ae28126b3a21f25015d0754e30c6f3ba [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/* libs/graphics/effects/SkGradientShader.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
reed@google.com55b8e8c2011-01-13 16:22:35 +00005** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
reed@android.com8a1c16f2008-12-17 15:59:43 +00008**
reed@google.com55b8e8c2011-01-13 16:22:35 +00009** http://www.apache.org/licenses/LICENSE-2.0
reed@android.com8a1c16f2008-12-17 15:59:43 +000010**
reed@google.com55b8e8c2011-01-13 16:22:35 +000011** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
reed@android.com8a1c16f2008-12-17 15:59:43 +000015** limitations under the License.
16*/
17
18#include "SkGradientShader.h"
19#include "SkColorPriv.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000020#include "SkMallocPixelRef.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021#include "SkUnitMapper.h"
22#include "SkUtils.h"
reed@google.comdc731fd2010-12-23 15:19:47 +000023#include "SkTemplates.h"
24#include "SkBitmapCache.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000025
reed@google.com9c7443d2011-01-17 18:46:37 +000026#ifndef SK_DISABLE_DITHER_32BIT_GRADIENT
27 #define USE_DITHER_32BIT_GRADIENT
28#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +000029
reed@android.com8a1c16f2008-12-17 15:59:43 +000030///////////////////////////////////////////////////////////////////////////
31
32typedef SkFixed (*TileProc)(SkFixed);
33
reed@android.com41bccf52009-04-03 13:33:51 +000034static SkFixed clamp_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000035 return SkClampMax(x, 0xFFFF);
36}
37
reed@android.com41bccf52009-04-03 13:33:51 +000038static SkFixed repeat_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000039 return x & 0xFFFF;
40}
41
reed@android.com41bccf52009-04-03 13:33:51 +000042static inline SkFixed mirror_tileproc(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000043 int s = x << 15 >> 31;
44 return (x ^ s) & 0xFFFF;
45}
46
47static const TileProc gTileProcs[] = {
48 clamp_tileproc,
49 repeat_tileproc,
50 mirror_tileproc
51};
52
53//////////////////////////////////////////////////////////////////////////////
54
reed@android.com200645d2009-12-14 16:41:57 +000055static inline int repeat_bits(int x, const int bits) {
56 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000057}
58
reed@android.com200645d2009-12-14 16:41:57 +000059static inline int mirror_bits(int x, const int bits) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000060#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com200645d2009-12-14 16:41:57 +000061 if (x & (1 << bits))
reed@android.com8a1c16f2008-12-17 15:59:43 +000062 x = ~x;
reed@android.com200645d2009-12-14 16:41:57 +000063 return x & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000064#else
reed@android.com200645d2009-12-14 16:41:57 +000065 int s = x << (31 - bits) >> 31;
66 return (x ^ s) & ((1 << bits) - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000067#endif
68}
69
reed@android.com41bccf52009-04-03 13:33:51 +000070static inline int repeat_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000071 return x & 0xFF;
72}
73
reed@android.com41bccf52009-04-03 13:33:51 +000074static inline int mirror_8bits(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000075#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
reed@android.com41bccf52009-04-03 13:33:51 +000076 if (x & 256) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 x = ~x;
reed@android.com41bccf52009-04-03 13:33:51 +000078 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 return x & 255;
80#else
81 int s = x << 23 >> 31;
82 return (x ^ s) & 0xFF;
83#endif
84}
85
86//////////////////////////////////////////////////////////////////////////////
87
88class Gradient_Shader : public SkShader {
89public:
90 Gradient_Shader(const SkColor colors[], const SkScalar pos[],
reed@android.com41bccf52009-04-03 13:33:51 +000091 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +000092 virtual ~Gradient_Shader();
93
94 // overrides
95 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
96 virtual uint32_t getFlags() { return fFlags; }
97
98protected:
99 Gradient_Shader(SkFlattenableReadBuffer& );
100 SkUnitMapper* fMapper;
101 SkMatrix fPtsToUnit; // set by subclass
102 SkMatrix fDstToIndex;
103 SkMatrix::MapXYProc fDstToIndexProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 TileMode fTileMode;
105 TileProc fTileProc;
reed@android.com41bccf52009-04-03 13:33:51 +0000106 int fColorCount;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 uint8_t fDstToIndexClass;
108 uint8_t fFlags;
109
110 struct Rec {
111 SkFixed fPos; // 0...1
112 uint32_t fScale; // (1 << 24) / range
113 };
114 Rec* fRecs;
115
116 enum {
reed@android.com512a8762009-12-14 15:25:36 +0000117 kCache16Bits = 8, // seems like enough for visual accuracy
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118 kCache16Count = 1 << kCache16Bits,
reed@android.com512a8762009-12-14 15:25:36 +0000119 kCache16Mask = kCache16Count - 1,
120 kCache16Shift = 16 - kCache16Bits,
121
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 kCache32Bits = 8, // pretty much should always be 8
123 kCache32Count = 1 << kCache32Bits
124 };
125 virtual void flatten(SkFlattenableWriteBuffer& );
reed@google.com7c2f27d2011-03-07 19:29:00 +0000126 const uint16_t* getCache16() const;
127 const SkPMColor* getCache32() const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128
reed@google.com7c2f27d2011-03-07 19:29:00 +0000129 void commonAsABitmap(SkBitmap*) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000130 void commonAsAGradient(GradientInfo*) const;
reed@android.com9b46e772009-06-05 12:24:41 +0000131
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132private:
133 enum {
134 kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
135
reed@android.com1c12abe2009-07-02 15:01:02 +0000136 kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
reed@android.com8a1c16f2008-12-17 15:59:43 +0000137 };
138 SkColor fStorage[(kStorageSize + 3) >> 2];
139 SkColor* fOrigColors;
140
reed@google.com7c2f27d2011-03-07 19:29:00 +0000141 mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
142 mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
reed@google.com7c2f27d2011-03-07 19:29:00 +0000144 mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
145 mutable SkMallocPixelRef* fCache32PixelRef;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000146 unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
147
reed@android.com512a8762009-12-14 15:25:36 +0000148 static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000149 static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
150 U8CPU alpha);
reed@android.com512a8762009-12-14 15:25:36 +0000151
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152 typedef SkShader INHERITED;
153};
154
reed@android.com41bccf52009-04-03 13:33:51 +0000155static inline unsigned scalarToU16(SkScalar x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156 SkASSERT(x >= 0 && x <= SK_Scalar1);
157
158#ifdef SK_SCALAR_IS_FLOAT
159 return (unsigned)(x * 0xFFFF);
160#else
161 return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
162#endif
163}
164
reed@android.com41bccf52009-04-03 13:33:51 +0000165Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
166 int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000167 SkASSERT(colorCount > 1);
168
169 fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
170
171 fMapper = mapper;
reed@google.com82065d62011-02-07 15:30:46 +0000172 SkSafeRef(mapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 SkASSERT((unsigned)mode < SkShader::kTileModeCount);
175 SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
176 fTileMode = mode;
177 fTileProc = gTileProcs[mode];
reed@google.com55b8e8c2011-01-13 16:22:35 +0000178
reed@android.com41bccf52009-04-03 13:33:51 +0000179 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000180 fCache32 = NULL;
181 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182
reed@android.com41bccf52009-04-03 13:33:51 +0000183 /* Note: we let the caller skip the first and/or last position.
184 i.e. pos[0] = 0.3, pos[1] = 0.7
185 In these cases, we insert dummy entries to ensure that the final data
186 will be bracketed by [0, 1].
187 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
188
189 Thus colorCount (the caller's value, and fColorCount (our value) may
190 differ by up to 2. In the above example:
191 colorCount = 2
192 fColorCount = 4
193 */
194 fColorCount = colorCount;
195 // check if we need to add in dummy start and/or end position/colors
196 bool dummyFirst = false;
197 bool dummyLast = false;
198 if (pos) {
199 dummyFirst = pos[0] != 0;
200 dummyLast = pos[colorCount - 1] != SK_Scalar1;
201 fColorCount += dummyFirst + dummyLast;
202 }
203
204 if (fColorCount > kColorStorageCount) {
reed@android.com1c12abe2009-07-02 15:01:02 +0000205 size_t size = sizeof(SkColor) + sizeof(Rec);
reed@android.com41bccf52009-04-03 13:33:51 +0000206 fOrigColors = reinterpret_cast<SkColor*>(
207 sk_malloc_throw(size * fColorCount));
208 }
209 else {
210 fOrigColors = fStorage;
211 }
212
213 // Now copy over the colors, adding the dummies as needed
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 {
reed@android.com41bccf52009-04-03 13:33:51 +0000215 SkColor* origColors = fOrigColors;
216 if (dummyFirst) {
217 *origColors++ = colors[0];
218 }
219 memcpy(origColors, colors, colorCount * sizeof(SkColor));
220 if (dummyLast) {
221 origColors += colorCount;
222 *origColors = colors[colorCount - 1];
223 }
224 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225
reed@android.com1c12abe2009-07-02 15:01:02 +0000226 fRecs = (Rec*)(fOrigColors + fColorCount);
reed@android.com41bccf52009-04-03 13:33:51 +0000227 if (fColorCount > 2) {
228 Rec* recs = fRecs;
229 recs->fPos = 0;
230 // recs->fScale = 0; // unused;
231 recs += 1;
232 if (pos) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000233 /* We need to convert the user's array of relative positions into
234 fixed-point positions and scale factors. We need these results
235 to be strictly monotonic (no two values equal or out of order).
236 Hence this complex loop that just jams a zero for the scale
237 value if it sees a segment out of order, and it assures that
238 we start at 0 and end at 1.0
239 */
240 SkFixed prev = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000241 int startIndex = dummyFirst ? 0 : 1;
242 int count = colorCount + dummyLast;
243 for (int i = startIndex; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244 // force the last value to be 1.0
245 SkFixed curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000246 if (i == colorCount) { // we're really at the dummyLast
reed@android.com8a1c16f2008-12-17 15:59:43 +0000247 curr = SK_Fixed1;
reed@android.com41bccf52009-04-03 13:33:51 +0000248 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000249 curr = SkScalarToFixed(pos[i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250 }
reed@android.com41bccf52009-04-03 13:33:51 +0000251 // pin curr withing range
252 if (curr < 0) {
253 curr = 0;
254 } else if (curr > SK_Fixed1) {
255 curr = SK_Fixed1;
256 }
257 recs->fPos = curr;
258 if (curr > prev) {
259 recs->fScale = (1 << 24) / (curr - prev);
260 } else {
261 recs->fScale = 0; // ignore this segment
262 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 // get ready for the next value
264 prev = curr;
reed@android.com41bccf52009-04-03 13:33:51 +0000265 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266 }
reed@android.com41bccf52009-04-03 13:33:51 +0000267 } else { // assume even distribution
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 SkFixed dp = SK_Fixed1 / (colorCount - 1);
269 SkFixed p = dp;
270 SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
reed@android.com41bccf52009-04-03 13:33:51 +0000271 for (int i = 1; i < colorCount; i++) {
272 recs->fPos = p;
273 recs->fScale = scale;
274 recs += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000275 p += dp;
276 }
277 }
278 }
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000279 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000280}
281
282Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
reed@android.com41bccf52009-04-03 13:33:51 +0000283 INHERITED(buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000284 fCacheAlpha = 256;
285
286 fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
287
288 fCache16 = fCache16Storage = NULL;
reed@google.comdc731fd2010-12-23 15:19:47 +0000289 fCache32 = NULL;
290 fCache32PixelRef = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000291
reed@android.com41bccf52009-04-03 13:33:51 +0000292 int colorCount = fColorCount = buffer.readU32();
293 if (colorCount > kColorStorageCount) {
294 size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
295 fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
296 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 fOrigColors = fStorage;
reed@android.com41bccf52009-04-03 13:33:51 +0000298 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 buffer.read(fOrigColors, colorCount * sizeof(SkColor));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000300
301 fTileMode = (TileMode)buffer.readU8();
302 fTileProc = gTileProcs[fTileMode];
reed@android.com1c12abe2009-07-02 15:01:02 +0000303 fRecs = (Rec*)(fOrigColors + colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 if (colorCount > 2) {
305 Rec* recs = fRecs;
306 recs[0].fPos = 0;
307 for (int i = 1; i < colorCount; i++) {
308 recs[i].fPos = buffer.readS32();
309 recs[i].fScale = buffer.readU32();
310 }
311 }
312 buffer.read(&fPtsToUnit, sizeof(SkMatrix));
reed@android.com4a7fd2b2009-06-02 19:46:20 +0000313 fFlags = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000314}
315
reed@android.com41bccf52009-04-03 13:33:51 +0000316Gradient_Shader::~Gradient_Shader() {
317 if (fCache16Storage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318 sk_free(fCache16Storage);
reed@android.com41bccf52009-04-03 13:33:51 +0000319 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000320 SkSafeUnref(fCache32PixelRef);
reed@android.com41bccf52009-04-03 13:33:51 +0000321 if (fOrigColors != fStorage) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 sk_free(fOrigColors);
reed@android.com41bccf52009-04-03 13:33:51 +0000323 }
reed@google.com82065d62011-02-07 15:30:46 +0000324 SkSafeUnref(fMapper);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325}
326
reed@android.com41bccf52009-04-03 13:33:51 +0000327void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328 this->INHERITED::flatten(buffer);
329 buffer.writeFlattenable(fMapper);
reed@android.com41bccf52009-04-03 13:33:51 +0000330 buffer.write32(fColorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
332 buffer.write8(fTileMode);
333 if (fColorCount > 2) {
334 Rec* recs = fRecs;
335 for (int i = 1; i < fColorCount; i++) {
336 buffer.write32(recs[i].fPos);
337 buffer.write32(recs[i].fScale);
338 }
339 }
340 buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
341}
342
343bool Gradient_Shader::setContext(const SkBitmap& device,
344 const SkPaint& paint,
reed@android.com41bccf52009-04-03 13:33:51 +0000345 const SkMatrix& matrix) {
346 if (!this->INHERITED::setContext(device, paint, matrix)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 return false;
reed@android.com41bccf52009-04-03 13:33:51 +0000348 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349
350 const SkMatrix& inverse = this->getTotalInverse();
351
352 if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
353 return false;
354 }
355
356 fDstToIndexProc = fDstToIndex.getMapXYProc();
357 fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
358
359 // now convert our colors in to PMColors
360 unsigned paintAlpha = this->getPaintAlpha();
361 unsigned colorAlpha = 0xFF;
362
reed@android.com3d06a8c2009-07-07 18:19:59 +0000363 // FIXME: record colorAlpha in constructor, since this is not affected
364 // by setContext()
reed@android.com41bccf52009-04-03 13:33:51 +0000365 for (int i = 0; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366 SkColor src = fOrigColors[i];
367 unsigned sa = SkColorGetA(src);
368 colorAlpha &= sa;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 }
370
371 fFlags = this->INHERITED::getFlags();
372 if ((colorAlpha & paintAlpha) == 0xFF) {
373 fFlags |= kOpaqueAlpha_Flag;
374 }
375 // we can do span16 as long as our individual colors are opaque,
376 // regardless of the paint's alpha
377 if (0xFF == colorAlpha) {
378 fFlags |= kHasSpan16_Flag;
379 }
380
381 // if the new alpha differs from the previous time we were called, inval our cache
382 // this will trigger the cache to be rebuilt.
383 // we don't care about the first time, since the cache ptrs will already be NULL
384 if (fCacheAlpha != paintAlpha) {
385 fCache16 = NULL; // inval the cache
386 fCache32 = NULL; // inval the cache
387 fCacheAlpha = paintAlpha; // record the new alpha
reed@android.com9b46e772009-06-05 12:24:41 +0000388 // inform our subclasses
reed@google.comdc731fd2010-12-23 15:19:47 +0000389 if (fCache32PixelRef) {
390 fCache32PixelRef->notifyPixelsChanged();
391 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000392 }
393 return true;
394}
395
reed@android.com41bccf52009-04-03 13:33:51 +0000396static inline int blend8(int a, int b, int scale) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397 SkASSERT(a == SkToU8(a));
398 SkASSERT(b == SkToU8(b));
399 SkASSERT(scale >= 0 && scale <= 256);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000400 return a + ((b - a) * scale >> 8);
401}
402
reed@android.com41bccf52009-04-03 13:33:51 +0000403static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
404 int blend) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000405#if 0
406 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
407 int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
408 int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
409 int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
410
411 return SkPackARGB32(a, r, g, b);
412#else
413 int otherBlend = 256 - blend;
414
415#if 0
416 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
417 U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
418 SkASSERT((t0 & t1) == 0);
419 return t0 | t1;
420#else
421 return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
422 ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
423#endif
424
425#endif
426}
427
428#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
429
reed@android.com41bccf52009-04-03 13:33:51 +0000430/** We take the original colors, not our premultiplied PMColors, since we can
431 build a 16bit table as long as the original colors are opaque, even if the
432 paint specifies a non-opaque alpha.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433*/
reed@android.com512a8762009-12-14 15:25:36 +0000434void Gradient_Shader::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
435 int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436 SkASSERT(count > 1);
437 SkASSERT(SkColorGetA(c0) == 0xFF);
438 SkASSERT(SkColorGetA(c1) == 0xFF);
439
440 SkFixed r = SkColorGetR(c0);
441 SkFixed g = SkColorGetG(c0);
442 SkFixed b = SkColorGetB(c0);
443
444 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
445 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
446 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
447
448 r = SkIntToFixed(r) + 0x8000;
449 g = SkIntToFixed(g) + 0x8000;
450 b = SkIntToFixed(b) + 0x8000;
451
452 do {
453 unsigned rr = r >> 16;
454 unsigned gg = g >> 16;
455 unsigned bb = b >> 16;
456 cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
reed@android.com512a8762009-12-14 15:25:36 +0000457 cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000458 cache += 1;
459 r += dr;
460 g += dg;
461 b += db;
462 } while (--count != 0);
463}
464
reed@google.com55b8e8c2011-01-13 16:22:35 +0000465/*
466 * 2x2 dither a fixed-point color component (8.16) down to 8, matching the
467 * semantics of how we 2x2 dither 32->16
468 */
469static inline U8CPU dither_fixed_to_8(SkFixed n) {
470 n >>= 8;
471 return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8;
472}
473
474/*
475 * For dithering with premultiply, we want to ceiling the alpha component,
476 * to ensure that it is always >= any color component.
477 */
478static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) {
479 n >>= 8;
480 return ((n << 1) - (n | (n >> 8))) >> 8;
481}
482
483void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
484 int count, U8CPU paintAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000485 SkASSERT(count > 1);
486
reed@android.com1c12abe2009-07-02 15:01:02 +0000487 // need to apply paintAlpha to our two endpoints
488 SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
489 SkFixed da;
490 {
491 int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
492 da = SkIntToFixed(tmp - a) / (count - 1);
493 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000494
reed@android.com1c12abe2009-07-02 15:01:02 +0000495 SkFixed r = SkColorGetR(c0);
496 SkFixed g = SkColorGetG(c0);
497 SkFixed b = SkColorGetB(c0);
498 SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
499 SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
500 SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000501
502 a = SkIntToFixed(a) + 0x8000;
503 r = SkIntToFixed(r) + 0x8000;
504 g = SkIntToFixed(g) + 0x8000;
505 b = SkIntToFixed(b) + 0x8000;
506
507 do {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000508 cache[0] = SkPreMultiplyARGB(a >> 16, r >> 16, g >> 16, b >> 16);
509 cache[kCache32Count] = SkPreMultiplyARGB(dither_ceil_fixed_to_8(a),
510 dither_fixed_to_8(r),
511 dither_fixed_to_8(g),
512 dither_fixed_to_8(b));
513 cache += 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000514 a += da;
515 r += dr;
516 g += dg;
517 b += db;
518 } while (--count != 0);
519}
520
reed@android.com41bccf52009-04-03 13:33:51 +0000521static inline int SkFixedToFFFF(SkFixed x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 SkASSERT((unsigned)x <= SK_Fixed1);
523 return x - (x >> 16);
524}
525
reed@android.com200645d2009-12-14 16:41:57 +0000526static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
reed@android.come191b162009-12-18 21:33:39 +0000527 SkASSERT(x < (1U << bits));
reed@android.com200645d2009-12-14 16:41:57 +0000528 if (6 == bits) {
529 return (x << 10) | (x << 4) | (x >> 2);
530 }
531 if (8 == bits) {
532 return (x << 8) | x;
533 }
534 sk_throw();
535 return 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000536}
537
reed@google.com7c2f27d2011-03-07 19:29:00 +0000538const uint16_t* Gradient_Shader::getCache16() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000539 if (fCache16 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000540 // double the count for dither entries
541 const int entryCount = kCache16Count * 2;
542 const size_t allocSize = sizeof(uint16_t) * entryCount;
543
reed@android.com3c9b2a42009-08-27 19:28:37 +0000544 if (fCache16Storage == NULL) { // set the storage and our working ptr
reed@google.com55b8e8c2011-01-13 16:22:35 +0000545 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com3c9b2a42009-08-27 19:28:37 +0000546 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000547 fCache16 = fCache16Storage;
reed@android.com41bccf52009-04-03 13:33:51 +0000548 if (fColorCount == 2) {
reed@android.com512a8762009-12-14 15:25:36 +0000549 Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
reed@android.com41bccf52009-04-03 13:33:51 +0000550 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000551 Rec* rec = fRecs;
552 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000553 for (int i = 1; i < fColorCount; i++) {
reed@android.com512a8762009-12-14 15:25:36 +0000554 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000555 SkASSERT(nextIndex < kCache16Count);
556
557 if (nextIndex > prevIndex)
reed@android.com512a8762009-12-14 15:25:36 +0000558 Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000559 prevIndex = nextIndex;
560 }
561 SkASSERT(prevIndex == kCache16Count - 1);
562 }
563
reed@android.com41bccf52009-04-03 13:33:51 +0000564 if (fMapper) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000565 fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000566 uint16_t* linear = fCache16; // just computed linear data
567 uint16_t* mapped = fCache16Storage; // storage for mapped data
568 SkUnitMapper* map = fMapper;
reed@android.com200645d2009-12-14 16:41:57 +0000569 for (int i = 0; i < kCache16Count; i++) {
570 int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000571 mapped[i] = linear[index];
reed@android.com200645d2009-12-14 16:41:57 +0000572 mapped[i + kCache16Count] = linear[index + kCache16Count];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000573 }
574 sk_free(fCache16);
575 fCache16 = fCache16Storage;
576 }
577 }
578 return fCache16;
579}
580
reed@google.com7c2f27d2011-03-07 19:29:00 +0000581const SkPMColor* Gradient_Shader::getCache32() const {
reed@android.com41bccf52009-04-03 13:33:51 +0000582 if (fCache32 == NULL) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000583 // double the count for dither entries
584 const int entryCount = kCache32Count * 2;
585 const size_t allocSize = sizeof(SkPMColor) * entryCount;
586
reed@google.comdc731fd2010-12-23 15:19:47 +0000587 if (NULL == fCache32PixelRef) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000588 fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
589 (NULL, allocSize, NULL));
reed@google.comdc731fd2010-12-23 15:19:47 +0000590 }
591 fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
reed@android.com41bccf52009-04-03 13:33:51 +0000592 if (fColorCount == 2) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000593 Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
594 kCache32Count, fCacheAlpha);
reed@android.com41bccf52009-04-03 13:33:51 +0000595 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 Rec* rec = fRecs;
597 int prevIndex = 0;
reed@android.com41bccf52009-04-03 13:33:51 +0000598 for (int i = 1; i < fColorCount; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000599 int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
600 SkASSERT(nextIndex < kCache32Count);
601
602 if (nextIndex > prevIndex)
reed@google.com55b8e8c2011-01-13 16:22:35 +0000603 Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
604 fOrigColors[i],
605 nextIndex - prevIndex + 1, fCacheAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606 prevIndex = nextIndex;
607 }
608 SkASSERT(prevIndex == kCache32Count - 1);
609 }
610
reed@android.com41bccf52009-04-03 13:33:51 +0000611 if (fMapper) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000612 SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
reed@google.com55b8e8c2011-01-13 16:22:35 +0000613 (NULL, allocSize, NULL));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000614 SkPMColor* linear = fCache32; // just computed linear data
reed@google.comdc731fd2010-12-23 15:19:47 +0000615 SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616 SkUnitMapper* map = fMapper;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000617 for (int i = 0; i < kCache32Count; i++) {
618 int index = map->mapUnit16((i << 8) | i) >> 8;
619 mapped[i] = linear[index];
620 mapped[i + kCache32Count] = linear[index + kCache32Count];
reed@android.com41bccf52009-04-03 13:33:51 +0000621 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000622 fCache32PixelRef->unref();
623 fCache32PixelRef = newPR;
624 fCache32 = (SkPMColor*)newPR->getAddr();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625 }
626 }
627 return fCache32;
628}
629
reed@google.comdc731fd2010-12-23 15:19:47 +0000630/*
631 * Because our caller might rebuild the same (logically the same) gradient
632 * over and over, we'd like to return exactly the same "bitmap" if possible,
633 * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
634 * To do that, we maintain a private cache of built-bitmaps, based on our
635 * colors and positions. Note: we don't try to flatten the fMapper, so if one
636 * is present, we skip the cache for now.
637 */
reed@google.com7c2f27d2011-03-07 19:29:00 +0000638void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
reed@google.comdc731fd2010-12-23 15:19:47 +0000639 // don't have a way to put the mapper into our cache-key yet
640 if (fMapper) {
641 // force our cahce32pixelref to be built
642 (void)this->getCache32();
643 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
644 bitmap->setPixelRef(fCache32PixelRef);
645 return;
646 }
647
648 // build our key: [numColors + colors[] + {positions[]} ]
649 int count = 1 + fColorCount;
650 if (fColorCount > 2) {
651 count += fColorCount - 1; // fRecs[].fPos
652 }
653
654 SkAutoSTMalloc<16, int32_t> storage(count);
655 int32_t* buffer = storage.get();
656
657 *buffer++ = fColorCount;
658 memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
659 buffer += fColorCount;
660 if (fColorCount > 2) {
661 for (int i = 1; i < fColorCount; i++) {
662 *buffer++ = fRecs[i].fPos;
663 }
664 }
665 SkASSERT(buffer - storage.get() == count);
666
667 ///////////////////////////////////
668
669 static SkMutex gMutex;
670 static SkBitmapCache* gCache;
671 // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
672 static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
673 SkAutoMutexAcquire ama(gMutex);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000674
reed@google.comdc731fd2010-12-23 15:19:47 +0000675 if (NULL == gCache) {
676 gCache = new SkBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS);
677 }
678 size_t size = count * sizeof(int32_t);
679
680 if (!gCache->find(storage.get(), size, bitmap)) {
681 // force our cahce32pixelref to be built
682 (void)this->getCache32();
683 bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
684 bitmap->setPixelRef(fCache32PixelRef);
685
686 gCache->add(storage.get(), size, *bitmap);
687 }
688}
689
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000690void Gradient_Shader::commonAsAGradient(GradientInfo* info) const {
691 if (info) {
692 if (info->fColorCount >= fColorCount) {
693 if (info->fColors) {
694 memcpy(info->fColors, fOrigColors,
695 fColorCount * sizeof(SkColor));
696 }
697 if (info->fColorOffsets) {
698 if (fColorCount == 2) {
699 info->fColorOffsets[0] = 0;
700 info->fColorOffsets[1] = SK_Scalar1;
701 } else if (fColorCount > 2) {
702 for (int i = 0; i < fColorCount; i++)
703 info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
704 }
705 }
706 }
707 info->fColorCount = fColorCount;
708 info->fTileMode = fTileMode;
709 }
710}
711
reed@android.com8a1c16f2008-12-17 15:59:43 +0000712///////////////////////////////////////////////////////////////////////////
713
reed@android.com41bccf52009-04-03 13:33:51 +0000714static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715 SkVector vec = pts[1] - pts[0];
716 SkScalar mag = vec.length();
717 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
718
719 vec.scale(inv);
720 matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
721 matrix->postTranslate(-pts[0].fX, -pts[0].fY);
722 matrix->postScale(inv, inv);
723}
724
725///////////////////////////////////////////////////////////////////////////////
726
727class Linear_Gradient : public Gradient_Shader {
728public:
729 Linear_Gradient(const SkPoint pts[2],
730 const SkColor colors[], const SkScalar pos[], int colorCount,
731 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000732 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
733 fStart(pts[0]),
734 fEnd(pts[1])
reed@android.com8a1c16f2008-12-17 15:59:43 +0000735 {
736 pts_to_unit_matrix(pts, &fPtsToUnit);
737 }
reed@android.com9b46e772009-06-05 12:24:41 +0000738
reed@android.com5119bdb2009-06-12 21:27:03 +0000739 virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000740 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
741 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000742 virtual BitmapType asABitmap(SkBitmap*, SkMatrix*,
reed@google.com7c2f27d2011-03-07 19:29:00 +0000743 TileMode*, SkScalar* twoPointRadialParams) const;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000744 virtual GradientType asAGradient(GradientInfo* info) const;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000745
reed@google.com55b8e8c2011-01-13 16:22:35 +0000746 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747 return SkNEW_ARGS(Linear_Gradient, (buffer));
748 }
749
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000750 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
751 this->INHERITED::flatten(buffer);
752 buffer.writeScalar(fStart.fX);
753 buffer.writeScalar(fStart.fY);
754 buffer.writeScalar(fEnd.fX);
755 buffer.writeScalar(fEnd.fY);
756 }
757
reed@android.com8a1c16f2008-12-17 15:59:43 +0000758protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000759 Linear_Gradient(SkFlattenableReadBuffer& buffer)
760 : Gradient_Shader(buffer),
761 fStart(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
762 fEnd(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
763 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000764 virtual Factory getFactory() { return CreateProc; }
765
766private:
767 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000768 const SkPoint fStart;
769 const SkPoint fEnd;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770};
771
reed@android.com5119bdb2009-06-12 21:27:03 +0000772bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
773 const SkMatrix& matrix) {
774 if (!this->INHERITED::setContext(device, paint, matrix)) {
775 return false;
776 }
777
778 unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
779 if ((fDstToIndex.getType() & ~mask) == 0) {
reed@android.com3c9b2a42009-08-27 19:28:37 +0000780 fFlags |= SkShader::kConstInY32_Flag;
781 if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
782 // only claim this if we do have a 16bit mode (i.e. none of our
783 // colors have alpha), and if we are not dithering (which obviously
784 // is not const in Y).
785 fFlags |= SkShader::kConstInY16_Flag;
786 }
reed@android.com5119bdb2009-06-12 21:27:03 +0000787 }
788 return true;
789}
790
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791// Return true if fx, fx+dx, fx+2*dx, ... is always in range
reed@android.comfc25abd2009-01-15 14:38:33 +0000792static inline bool no_need_for_clamp(int fx, int dx, int count)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793{
794 SkASSERT(count > 0);
795 return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
796}
797
798void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
799{
800 SkASSERT(count > 0);
801
802 SkPoint srcPt;
803 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
804 TileProc proc = fTileProc;
805 const SkPMColor* cache = this->getCache32();
reed@google.com55b8e8c2011-01-13 16:22:35 +0000806#ifdef USE_DITHER_32BIT_GRADIENT
807 int toggle = ((x ^ y) & 1) << kCache32Bits;
808 const int TOGGLE_MASK = (1 << kCache32Bits);
809#else
810 int toggle = 0;
811 const int TOGGLE_MASK = 0;
812#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813
reed@android.comc552a432009-06-12 20:02:50 +0000814 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000815 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
816 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
818
reed@android.comc552a432009-06-12 20:02:50 +0000819 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 SkFixed dxStorage[1];
821 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
822 dx = dxStorage[0];
reed@android.comc552a432009-06-12 20:02:50 +0000823 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
825 dx = SkScalarToFixed(fDstToIndex.getScaleX());
826 }
827
reed@android.comc552a432009-06-12 20:02:50 +0000828 if (SkFixedNearlyZero(dx)) {
829 // we're a vertical gradient, so no change in a span
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830 unsigned fi = proc(fx);
831 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000832 // TODO: dither version
reed@android.com8a1c16f2008-12-17 15:59:43 +0000833 sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
reed@android.comc552a432009-06-12 20:02:50 +0000834 } else if (proc == clamp_tileproc) {
reed@google.com55b8e8c2011-01-13 16:22:35 +0000835 do {
836 unsigned fi = SkClampMax(fx >> 8, 0xFF);
837 SkASSERT(fi <= 0xFF);
838 fx += dx;
839 *dstC++ = cache[toggle + fi];
840 toggle ^= TOGGLE_MASK;
841 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000842 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843 do {
844 unsigned fi = mirror_8bits(fx >> 8);
845 SkASSERT(fi <= 0xFF);
846 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000847 *dstC++ = cache[toggle + fi];
848 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849 } while (--count != 0);
reed@android.comc552a432009-06-12 20:02:50 +0000850 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851 SkASSERT(proc == repeat_tileproc);
852 do {
853 unsigned fi = repeat_8bits(fx >> 8);
854 SkASSERT(fi <= 0xFF);
855 fx += dx;
reed@google.com55b8e8c2011-01-13 16:22:35 +0000856 *dstC++ = cache[toggle + fi];
857 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858 } while (--count != 0);
859 }
reed@android.comc552a432009-06-12 20:02:50 +0000860 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861 SkScalar dstX = SkIntToScalar(x);
862 SkScalar dstY = SkIntToScalar(y);
863 do {
864 dstProc(fDstToIndex, dstX, dstY, &srcPt);
865 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
866 SkASSERT(fi <= 0xFFFF);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000867 *dstC++ = cache[toggle + (fi >> (16 - kCache32Bits))];
868 toggle ^= TOGGLE_MASK;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 dstX += SK_Scalar1;
870 } while (--count != 0);
871 }
872}
873
reed@google.com55b8e8c2011-01-13 16:22:35 +0000874SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
reed@google.comdc731fd2010-12-23 15:19:47 +0000875 SkMatrix* matrix,
876 TileMode xy[],
reed@google.com7c2f27d2011-03-07 19:29:00 +0000877 SkScalar* twoPointRadialParams) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 if (bitmap) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000879 this->commonAsABitmap(bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 }
881 if (matrix) {
882 matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
883 matrix->preConcat(fPtsToUnit);
884 }
885 if (xy) {
886 xy[0] = fTileMode;
887 xy[1] = kClamp_TileMode;
888 }
reed@google.comdc731fd2010-12-23 15:19:47 +0000889 return kDefault_BitmapType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890}
891
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +0000892SkShader::GradientType Linear_Gradient::asAGradient(GradientInfo* info) const {
893 if (info) {
894 commonAsAGradient(info);
895 info->fPoint[0] = fStart;
896 info->fPoint[1] = fEnd;
897 }
898 return kLinear_GradientType;
899}
900
reed@android.com3c9b2a42009-08-27 19:28:37 +0000901static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
902 int count) {
903 if (reinterpret_cast<uintptr_t>(dst) & 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 *dst++ = value;
905 count -= 1;
906 SkTSwap(value, other);
907 }
908
909 sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
reed@google.com55b8e8c2011-01-13 16:22:35 +0000910
reed@android.com3c9b2a42009-08-27 19:28:37 +0000911 if (count & 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000912 dst[count - 1] = value;
reed@android.com3c9b2a42009-08-27 19:28:37 +0000913 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000914}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000915
916void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
917{
918 SkASSERT(count > 0);
919
920 SkPoint srcPt;
921 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
922 TileProc proc = fTileProc;
923 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000924 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000925
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000926 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +0000927 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
928 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000929 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
930
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000931 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000932 SkFixed dxStorage[1];
933 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
934 dx = dxStorage[0];
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000935 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000936 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
937 dx = SkScalarToFixed(fDstToIndex.getScaleX());
938 }
939
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000940 if (SkFixedNearlyZero(dx)) {
941 // we're a vertical gradient, so no change in a span
reed@android.com512a8762009-12-14 15:25:36 +0000942 unsigned fi = proc(fx) >> kCache16Shift;
943 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000944 dither_memset16(dstC, cache[toggle + fi], cache[(toggle ^ (1 << kCache16Bits)) + fi], count);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000945 } else if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946 do {
reed@android.com512a8762009-12-14 15:25:36 +0000947 unsigned fi = SkClampMax(fx >> kCache16Shift, kCache16Mask);
948 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000949 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000950 *dstC++ = cache[toggle + fi];
951 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000952 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000953 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954 do {
reed@android.com200645d2009-12-14 16:41:57 +0000955 unsigned fi = mirror_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +0000956 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000957 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000958 *dstC++ = cache[toggle + fi];
959 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000960 } while (--count != 0);
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000961 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962 SkASSERT(proc == repeat_tileproc);
963 do {
reed@android.com200645d2009-12-14 16:41:57 +0000964 unsigned fi = repeat_bits(fx >> kCache16Shift, kCache16Bits);
reed@android.com512a8762009-12-14 15:25:36 +0000965 SkASSERT(fi <= kCache16Mask);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000966 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967 *dstC++ = cache[toggle + fi];
968 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000969 } while (--count != 0);
970 }
reed@android.comb2c5f2d2009-06-12 20:09:24 +0000971 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972 SkScalar dstX = SkIntToScalar(x);
973 SkScalar dstY = SkIntToScalar(y);
974 do {
975 dstProc(fDstToIndex, dstX, dstY, &srcPt);
976 unsigned fi = proc(SkScalarToFixed(srcPt.fX));
977 SkASSERT(fi <= 0xFFFF);
978
reed@android.com512a8762009-12-14 15:25:36 +0000979 int index = fi >> kCache16Shift;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980 *dstC++ = cache[toggle + index];
981 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000982
983 dstX += SK_Scalar1;
984 } while (--count != 0);
985 }
986}
987
988///////////////////////////////////////////////////////////////////////////////
989
990#define kSQRT_TABLE_BITS 11
991#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
992
993#include "SkRadialGradient_Table.h"
994
995#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
996
997#include <stdio.h>
998
999void SkRadialGradient_BuildTable()
1000{
1001 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
1002
1003 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
1004 SkASSERT(file);
1005 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
1006
1007 for (int i = 0; i < kSQRT_TABLE_SIZE; i++)
1008 {
1009 if ((i & 15) == 0)
1010 ::fprintf(file, "\t");
1011
1012 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
1013
1014 ::fprintf(file, "0x%02X", value);
1015 if (i < kSQRT_TABLE_SIZE-1)
1016 ::fprintf(file, ", ");
1017 if ((i & 15) == 15)
1018 ::fprintf(file, "\n");
1019 }
1020 ::fprintf(file, "};\n");
1021 ::fclose(file);
1022}
1023
1024#endif
1025
1026
1027static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius, SkMatrix* matrix)
1028{
1029 SkScalar inv = SkScalarInvert(radius);
1030
1031 matrix->setTranslate(-center.fX, -center.fY);
1032 matrix->postScale(inv, inv);
1033}
1034
1035class Radial_Gradient : public Gradient_Shader {
1036public:
1037 Radial_Gradient(const SkPoint& center, SkScalar radius,
1038 const SkColor colors[], const SkScalar pos[], int colorCount,
1039 SkShader::TileMode mode, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001040 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1041 fCenter(center),
1042 fRadius(radius)
reed@android.com8a1c16f2008-12-17 15:59:43 +00001043 {
1044 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
1045 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
1046
1047 rad_to_unit_matrix(center, radius, &fPtsToUnit);
1048 }
1049 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1050 {
1051 SkASSERT(count > 0);
1052
1053 SkPoint srcPt;
1054 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1055 TileProc proc = fTileProc;
1056 const SkPMColor* cache = this->getCache32();
1057
1058 if (fDstToIndexClass != kPerspective_MatrixClass)
1059 {
reed@google.comdc731fd2010-12-23 15:19:47 +00001060 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1061 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001062 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1063 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1064
1065 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1066 {
1067 SkFixed storage[2];
1068 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1069 dx = storage[0];
1070 dy = storage[1];
1071 }
1072 else
1073 {
1074 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1075 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1076 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1077 }
1078
1079 if (proc == clamp_tileproc)
1080 {
1081 const uint8_t* sqrt_table = gSqrt8Table;
1082 fx >>= 1;
1083 dx >>= 1;
1084 fy >>= 1;
1085 dy >>= 1;
1086 do {
1087 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1088 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1089 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1090 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1091 *dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
1092 fx += dx;
1093 fy += dy;
1094 } while (--count != 0);
1095 }
1096 else if (proc == mirror_tileproc)
1097 {
1098 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001099 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1100 if (magnitudeSquared < 0) // Overflow.
1101 magnitudeSquared = SK_FixedMax;
1102 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001103 unsigned fi = mirror_tileproc(dist);
1104 SkASSERT(fi <= 0xFFFF);
1105 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1106 fx += dx;
1107 fy += dy;
1108 } while (--count != 0);
1109 }
1110 else
1111 {
1112 SkASSERT(proc == repeat_tileproc);
1113 do {
wjmaclean@chromium.org1fbf7602011-04-13 13:31:06 +00001114 SkFixed magnitudeSquared = SkFixedSquare(fx) + SkFixedSquare(fy);
1115 if (magnitudeSquared < 0) // Overflow.
1116 magnitudeSquared = SK_FixedMax;
1117 SkFixed dist = SkFixedSqrt(magnitudeSquared);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118 unsigned fi = repeat_tileproc(dist);
1119 SkASSERT(fi <= 0xFFFF);
1120 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1121 fx += dx;
1122 fy += dy;
1123 } while (--count != 0);
1124 }
1125 }
1126 else // perspective case
1127 {
1128 SkScalar dstX = SkIntToScalar(x);
1129 SkScalar dstY = SkIntToScalar(y);
1130 do {
1131 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1132 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1133 SkASSERT(fi <= 0xFFFF);
1134 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1135 dstX += SK_Scalar1;
1136 } while (--count != 0);
1137 }
1138 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001139
1140 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001141 SkASSERT(count > 0);
1142
1143 SkPoint srcPt;
1144 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1145 TileProc proc = fTileProc;
1146 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001147 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001148
reed@android.com3c9b2a42009-08-27 19:28:37 +00001149 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001150 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1151 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001152 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1153 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1154
reed@android.com3c9b2a42009-08-27 19:28:37 +00001155 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001156 SkFixed storage[2];
1157 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1158 dx = storage[0];
1159 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001160 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1162 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1163 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1164 }
1165
reed@android.com3c9b2a42009-08-27 19:28:37 +00001166 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001167 const uint8_t* sqrt_table = gSqrt8Table;
1168
1169 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1170 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1171 precision, but that appears to be visually OK. If we decide this is OK for
1172 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1173 to avoid having to do these extra shifts each time.
1174 */
1175 fx >>= 1;
1176 dx >>= 1;
1177 fy >>= 1;
1178 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001179 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 +00001180 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1181 fy *= fy;
1182 do {
1183 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1184 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1185 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1186 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001187 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1188 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001190 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001191 do {
1192 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1193 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1194 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1195 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1196 fx += dx;
1197 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001198 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1199 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001200 } while (--count != 0);
1201 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001202 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203 do {
1204 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1205 unsigned fi = mirror_tileproc(dist);
1206 SkASSERT(fi <= 0xFFFF);
1207 fx += dx;
1208 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001209 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1210 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001212 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 SkASSERT(proc == repeat_tileproc);
1214 do {
1215 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1216 unsigned fi = repeat_tileproc(dist);
1217 SkASSERT(fi <= 0xFFFF);
1218 fx += dx;
1219 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1221 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 } while (--count != 0);
1223 }
reed@android.com3c9b2a42009-08-27 19:28:37 +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
1232 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233 *dstC++ = cache[toggle + index];
1234 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235
1236 dstX += SK_Scalar1;
1237 } while (--count != 0);
1238 }
1239 }
1240
reed@google.com55b8e8c2011-01-13 16:22:35 +00001241 virtual BitmapType asABitmap(SkBitmap* bitmap,
1242 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001243 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001244 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001245 if (bitmap) {
1246 this->commonAsABitmap(bitmap);
1247 }
1248 if (matrix) {
1249 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1250 matrix->preConcat(fPtsToUnit);
1251 }
1252 if (xy) {
1253 xy[0] = fTileMode;
1254 xy[1] = kClamp_TileMode;
1255 }
1256 return kRadial_BitmapType;
1257 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001258 virtual GradientType asAGradient(GradientInfo* info) const {
1259 if (info) {
1260 commonAsAGradient(info);
1261 info->fPoint[0] = fCenter;
1262 info->fRadius[0] = fRadius;
1263 }
1264 return kRadial_GradientType;
1265 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001266
1267 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001268 return SkNEW_ARGS(Radial_Gradient, (buffer));
1269 }
1270
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001271 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1272 this->INHERITED::flatten(buffer);
1273 buffer.writeScalar(fCenter.fX);
1274 buffer.writeScalar(fCenter.fY);
1275 buffer.writeScalar(fRadius);
1276 }
1277
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001279 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1280 : Gradient_Shader(buffer),
1281 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1282 fRadius(buffer.readScalar()) {
1283 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284 virtual Factory getFactory() { return CreateProc; }
1285
1286private:
1287 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001288 const SkPoint fCenter;
1289 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290};
1291
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001292/* Two-point radial gradients are specified by two circles, each with a center
1293 point and radius. The gradient can be considered to be a series of
1294 concentric circles, with the color interpolated from the start circle
1295 (at t=0) to the end circle (at t=1).
1296
1297 For each point (x, y) in the span, we want to find the
1298 interpolated circle that intersects that point. The center
1299 of the desired circle (Cx, Cy) falls at some distance t
1300 along the line segment between the start point (Sx, Sy) and
1301 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001302
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001303 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1304 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001305
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001306 The radius of the desired circle (r) is also a linear interpolation t
1307 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001308
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001309 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001310
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001311 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001312
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001313 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001314
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001315 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001316
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001317 (x - ((1 - t) * Sx + t * Ex))^2
1318 + (y - ((1 - t) * Sy + t * Ey))^2
1319 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001320
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001321 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001322
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001323 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1324 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1325 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001326
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001327 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1328
1329 [Dx^2 + Dy^2 - Dr^2)] * t^2
1330 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1331 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001332
1333 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001334 possible circles on which the point may fall. Solving for t yields
1335 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001336
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001337 If a<0, the start circle is entirely contained in the
1338 end circle, and one of the roots will be <0 or >1 (off the line
1339 segment). If a>0, the start circle falls at least partially
1340 outside the end circle (or vice versa), and the gradient
1341 defines a "tube" where a point may be on one circle (on the
1342 inside of the tube) or the other (outside of the tube). We choose
1343 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001344
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001345 In order to keep the math to within the limits of fixed point,
1346 we divide the entire quadratic by Dr^2, and replace
1347 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001348
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001349 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1350 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1351 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001352
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001353 (x' and y' are computed by appending the subtract and scale to the
1354 fDstToIndex matrix in the constructor).
1355
1356 Since the 'A' component of the quadratic is independent of x' and y', it
1357 is precomputed in the constructor. Since the 'B' component is linear in
1358 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001359 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001360 a perspective projection), it must be computed in the loop.
1361
1362*/
1363
reed@google.com84e9c082011-04-13 17:44:24 +00001364static inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
1365 SkScalar sr2d2, SkScalar foura,
1366 SkScalar oneOverTwoA, bool posRoot) {
1367 SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
1368 SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001369 if (discrim < 0) {
1370 discrim = -discrim;
1371 }
reed@google.com84e9c082011-04-13 17:44:24 +00001372 SkScalar rootDiscrim = SkScalarSqrt(discrim);
1373 SkScalar result;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001374 if (posRoot) {
reed@google.com84e9c082011-04-13 17:44:24 +00001375 result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001376 } else {
reed@google.com84e9c082011-04-13 17:44:24 +00001377 result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001378 }
reed@google.com84e9c082011-04-13 17:44:24 +00001379 return SkScalarToFixed(result);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001380}
1381
1382class Two_Point_Radial_Gradient : public Gradient_Shader {
1383public:
1384 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1385 const SkPoint& end, SkScalar endRadius,
1386 const SkColor colors[], const SkScalar pos[],
1387 int colorCount, SkShader::TileMode mode,
1388 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001389 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1390 fCenter1(start),
1391 fCenter2(end),
1392 fRadius1(startRadius),
1393 fRadius2(endRadius) {
1394 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001395 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001396
1397 virtual BitmapType asABitmap(SkBitmap* bitmap,
1398 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001399 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001400 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001401 if (bitmap) {
1402 this->commonAsABitmap(bitmap);
1403 }
1404 SkScalar diffL = 0; // just to avoid gcc warning
1405 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001406 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001407 SkScalarSquare(fDiff.fY));
1408 }
1409 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001410 if (diffL) {
1411 SkScalar invDiffL = SkScalarInvert(diffL);
1412 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1413 SkScalarMul(invDiffL, fDiff.fX));
1414 } else {
1415 matrix->reset();
1416 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001417 matrix->preConcat(fPtsToUnit);
1418 }
1419 if (xy) {
1420 xy[0] = fTileMode;
1421 xy[1] = kClamp_TileMode;
1422 }
1423 if (NULL != twoPointRadialParams) {
1424 twoPointRadialParams[0] = diffL;
1425 twoPointRadialParams[1] = fStartRadius;
1426 twoPointRadialParams[2] = fDiffRadius;
1427 }
1428 return kTwoPointRadial_BitmapType;
1429 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001430
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001431 virtual GradientType asAGradient(GradientInfo* info) const {
1432 if (info) {
1433 commonAsAGradient(info);
1434 info->fPoint[0] = fCenter1;
1435 info->fPoint[1] = fCenter2;
1436 info->fRadius[0] = fRadius1;
1437 info->fRadius[1] = fRadius2;
1438 }
1439 return kRadial2_GradientType;
1440 }
1441
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001442 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1443 {
1444 SkASSERT(count > 0);
1445
1446 // Zero difference between radii: fill with transparent black.
1447 if (fDiffRadius == 0) {
1448 sk_bzero(dstC, count * sizeof(*dstC));
1449 return;
1450 }
1451 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1452 TileProc proc = fTileProc;
1453 const SkPMColor* cache = this->getCache32();
reed@google.com84e9c082011-04-13 17:44:24 +00001454
1455 SkScalar foura = fA * 4;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001456 bool posRoot = fDiffRadius < 0;
1457 if (fDstToIndexClass != kPerspective_MatrixClass)
1458 {
1459 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001460 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1461 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001462 SkScalar dx, fx = srcPt.fX;
1463 SkScalar dy, fy = srcPt.fY;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001464
1465 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1466 {
reed@google.com84e9c082011-04-13 17:44:24 +00001467 SkFixed fixedX, fixedY;
1468 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
1469 dx = SkFixedToScalar(fixedX);
1470 dy = SkFixedToScalar(fixedY);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001471 }
1472 else
1473 {
1474 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
reed@google.com84e9c082011-04-13 17:44:24 +00001475 dx = fDstToIndex.getScaleX();
1476 dy = fDstToIndex.getSkewY();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001477 }
reed@google.com84e9c082011-04-13 17:44:24 +00001478 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1479 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1480 SkScalar db = (SkScalarMul(fDiff.fX, dx) +
1481 SkScalarMul(fDiff.fY, dy)) * 2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001482 if (proc == clamp_tileproc)
1483 {
1484 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001485 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001486 SkFixed index = SkClampMax(t, 0xFFFF);
1487 SkASSERT(index <= 0xFFFF);
1488 *dstC++ = cache[index >> (16 - kCache32Bits)];
1489 fx += dx;
1490 fy += dy;
1491 b += db;
1492 }
1493 }
1494 else if (proc == mirror_tileproc)
1495 {
1496 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001497 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001498 SkFixed index = mirror_tileproc(t);
1499 SkASSERT(index <= 0xFFFF);
1500 *dstC++ = cache[index >> (16 - kCache32Bits)];
1501 fx += dx;
1502 fy += dy;
1503 b += db;
1504 }
1505 }
1506 else
1507 {
1508 SkASSERT(proc == repeat_tileproc);
1509 for (; count > 0; --count) {
reed@google.com84e9c082011-04-13 17:44:24 +00001510 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001511 SkFixed index = repeat_tileproc(t);
1512 SkASSERT(index <= 0xFFFF);
1513 *dstC++ = cache[index >> (16 - kCache32Bits)];
1514 fx += dx;
1515 fy += dy;
1516 b += db;
1517 }
1518 }
1519 }
1520 else // perspective case
1521 {
reed@android.com6c59a172009-09-22 20:24:05 +00001522 SkScalar dstX = SkIntToScalar(x);
1523 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001524 for (; count > 0; --count) {
1525 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001526 dstProc(fDstToIndex, dstX, dstY, &srcPt);
reed@google.com84e9c082011-04-13 17:44:24 +00001527 SkScalar fx = srcPt.fX;
1528 SkScalar fy = srcPt.fY;
1529 SkScalar b = (SkScalarMul(fDiff.fX, fx) +
1530 SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
1531 SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001532 SkFixed index = proc(t);
1533 SkASSERT(index <= 0xFFFF);
1534 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001535 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001536 }
1537 }
1538 }
1539
reed@android.com6c59a172009-09-22 20:24:05 +00001540 virtual bool setContext(const SkBitmap& device,
1541 const SkPaint& paint,
1542 const SkMatrix& matrix) {
1543 if (!this->INHERITED::setContext(device, paint, matrix)) {
1544 return false;
1545 }
1546
1547 // we don't have a span16 proc
1548 fFlags &= ~kHasSpan16_Flag;
1549 return true;
1550 }
1551
reed@google.com55b8e8c2011-01-13 16:22:35 +00001552 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001553 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1554 }
1555
reed@android.combcfc7332009-11-10 16:19:39 +00001556 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1557 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001558 buffer.writeScalar(fCenter1.fX);
1559 buffer.writeScalar(fCenter1.fY);
1560 buffer.writeScalar(fCenter2.fX);
1561 buffer.writeScalar(fCenter2.fY);
1562 buffer.writeScalar(fRadius1);
1563 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001564 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001565
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001566protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001567 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001568 : Gradient_Shader(buffer),
1569 fCenter1(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1570 fCenter2(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1571 fRadius1(buffer.readScalar()),
1572 fRadius2(buffer.readScalar()) {
1573 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001574 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001575 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001576
1577private:
1578 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001579 const SkPoint fCenter1;
1580 const SkPoint fCenter2;
1581 const SkScalar fRadius1;
1582 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001583 SkPoint fDiff;
1584 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001585
1586 void init() {
1587 fDiff = fCenter1 - fCenter2;
1588 fDiffRadius = fRadius2 - fRadius1;
1589 SkScalar inv = SkScalarInvert(fDiffRadius);
1590 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1591 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1592 fStartRadius = SkScalarMul(fRadius1, inv);
1593 fSr2D2 = SkScalarSquare(fStartRadius);
1594 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1595 fOneOverTwoA = SkScalarInvert(fA * 2);
1596
1597 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1598 fPtsToUnit.postScale(inv, inv);
1599 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001600};
1601
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602///////////////////////////////////////////////////////////////////////////////
1603
1604class Sweep_Gradient : public Gradient_Shader {
1605public:
1606 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1607 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001608 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1609 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001610 {
1611 fPtsToUnit.setTranslate(-cx, -cy);
1612 }
1613 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1614 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001615
1616 virtual BitmapType asABitmap(SkBitmap* bitmap,
1617 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001618 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001619 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001620 if (bitmap) {
1621 this->commonAsABitmap(bitmap);
1622 }
1623 if (matrix) {
1624 *matrix = fPtsToUnit;
1625 }
1626 if (xy) {
1627 xy[0] = fTileMode;
1628 xy[1] = kClamp_TileMode;
1629 }
1630 return kSweep_BitmapType;
1631 }
1632
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001633 virtual GradientType asAGradient(GradientInfo* info) const {
1634 if (info) {
1635 commonAsAGradient(info);
1636 info->fPoint[0] = fCenter;
1637 }
1638 return kSweep_GradientType;
1639 }
1640
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1642 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1643 }
1644
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001645 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1646 this->INHERITED::flatten(buffer);
1647 buffer.writeScalar(fCenter.fX);
1648 buffer.writeScalar(fCenter.fY);
1649 }
1650
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001652 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1653 : Gradient_Shader(buffer),
1654 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
1655 }
1656
reed@android.com8a1c16f2008-12-17 15:59:43 +00001657 virtual Factory getFactory() { return CreateProc; }
1658
1659private:
1660 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001661 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662};
1663
1664#ifdef COMPUTE_SWEEP_TABLE
1665#define PI 3.14159265
1666static bool gSweepTableReady;
1667static uint8_t gSweepTable[65];
1668
1669/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1670 We scale the results to [0..32]
1671*/
1672static const uint8_t* build_sweep_table()
1673{
1674 if (!gSweepTableReady)
1675 {
1676 const int N = 65;
1677 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001678
reed@android.com8a1c16f2008-12-17 15:59:43 +00001679 for (int i = 0; i < N; i++)
1680 {
1681 double arg = i / DENOM;
1682 double v = atan(arg);
1683 int iv = (int)round(v * DENOM * 2 / PI);
1684// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1685 printf("%d, ", iv);
1686 gSweepTable[i] = iv;
1687 }
1688 gSweepTableReady = true;
1689 }
1690 return gSweepTable;
1691}
1692#else
1693static const uint8_t gSweepTable[] = {
1694 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1695 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1696 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1697 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1698 32
1699};
1700static const uint8_t* build_sweep_table() { return gSweepTable; }
1701#endif
1702
1703// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1704// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1705// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1706
1707//unsigned div_64(int numer, int denom);
1708static unsigned div_64(int numer, int denom)
1709{
1710 SkASSERT(numer <= denom);
1711 SkASSERT(numer > 0);
1712 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001713
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 int nbits = SkCLZ(numer);
1715 int dbits = SkCLZ(denom);
1716 int bits = 6 - nbits + dbits;
1717 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001718
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719 if (bits < 0) // detect underflow
1720 return 0;
1721
1722 denom <<= dbits - 1;
1723 numer <<= nbits - 1;
1724
1725 unsigned result = 0;
1726
1727 // do the first one
1728 if ((numer -= denom) >= 0)
1729 result = 1;
1730 else
1731 numer += denom;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001732
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733 // Now fall into our switch statement if there are more bits to compute
1734 if (bits > 0)
1735 {
1736 // make room for the rest of the answer bits
1737 result <<= bits;
1738 switch (bits) {
1739 case 6:
1740 if ((numer = (numer << 1) - denom) >= 0)
1741 result |= 32;
1742 else
1743 numer += denom;
1744 case 5:
1745 if ((numer = (numer << 1) - denom) >= 0)
1746 result |= 16;
1747 else
1748 numer += denom;
1749 case 4:
1750 if ((numer = (numer << 1) - denom) >= 0)
1751 result |= 8;
1752 else
1753 numer += denom;
1754 case 3:
1755 if ((numer = (numer << 1) - denom) >= 0)
1756 result |= 4;
1757 else
1758 numer += denom;
1759 case 2:
1760 if ((numer = (numer << 1) - denom) >= 0)
1761 result |= 2;
1762 else
1763 numer += denom;
1764 case 1:
1765 default: // not strictly need, but makes GCC make better ARM code
1766 if ((numer = (numer << 1) - denom) >= 0)
1767 result |= 1;
1768 else
1769 numer += denom;
1770 }
1771 }
1772 return result;
1773}
1774
1775// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1776static unsigned atan_0_90(SkFixed y, SkFixed x)
1777{
1778#ifdef SK_DEBUG
1779 {
1780 static bool gOnce;
1781 if (!gOnce)
1782 {
1783 gOnce = true;
1784 SkASSERT(div_64(55, 55) == 64);
1785 SkASSERT(div_64(128, 256) == 32);
1786 SkASSERT(div_64(2326528, 4685824) == 31);
1787 SkASSERT(div_64(753664, 5210112) == 9);
1788 SkASSERT(div_64(229376, 4882432) == 3);
1789 SkASSERT(div_64(2, 64) == 2);
1790 SkASSERT(div_64(1, 64) == 1);
1791 // test that we handle underflow correctly
1792 SkASSERT(div_64(12345, 0x54321234) == 0);
1793 }
1794 }
1795#endif
1796
1797 SkASSERT(y > 0 && x > 0);
1798 const uint8_t* table = build_sweep_table();
1799
1800 unsigned result;
1801 bool swap = (x < y);
1802 if (swap)
1803 {
1804 // first part of the atan(v) = PI/2 - atan(1/v) identity
1805 // since our div_64 and table want v <= 1, where v = y/x
1806 SkTSwap<SkFixed>(x, y);
1807 }
1808
1809 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001810
reed@android.com8a1c16f2008-12-17 15:59:43 +00001811#ifdef SK_DEBUG
1812 {
1813 unsigned result2 = SkDivBits(y, x, 6);
1814 SkASSERT(result2 == result ||
1815 (result == 1 && result2 == 0));
1816 }
1817#endif
1818
1819 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1820 result = table[result];
1821
1822 if (swap)
1823 {
1824 // complete the atan(v) = PI/2 - atan(1/v) identity
1825 result = 64 - result;
1826 // pin to 63
1827 result -= result >> 6;
1828 }
1829
1830 SkASSERT(result <= 63);
1831 return result;
1832}
1833
1834// returns angle in a circle [0..2PI) -> [0..255]
1835static unsigned SkATan2_255(SkFixed y, SkFixed x)
1836{
1837 if (x == 0)
1838 {
1839 if (y == 0)
1840 return 0;
1841 return y < 0 ? 192 : 64;
1842 }
1843 if (y == 0)
1844 return x < 0 ? 128 : 0;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001845
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 /* Find the right quadrant for x,y
1847 Since atan_0_90 only handles the first quadrant, we rotate x,y
1848 appropriately before calling it, and then add the right amount
1849 to account for the real quadrant.
1850 quadrant 0 : add 0 | x > 0 && y > 0
1851 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1852 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1853 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001854
reed@android.com8a1c16f2008-12-17 15:59:43 +00001855 map x<0 to (1 << 6)
1856 map y<0 to (3 << 6)
1857 add = map_x ^ map_y
1858 */
1859 int xsign = x >> 31;
1860 int ysign = y >> 31;
1861 int add = ((-xsign) ^ (ysign & 3)) << 6;
1862
1863#ifdef SK_DEBUG
1864 if (0 == add)
1865 SkASSERT(x > 0 && y > 0);
1866 else if (64 == add)
1867 SkASSERT(x < 0 && y > 0);
1868 else if (128 == add)
1869 SkASSERT(x < 0 && y < 0);
1870 else if (192 == add)
1871 SkASSERT(x > 0 && y < 0);
1872 else
1873 SkASSERT(!"bad value for add");
1874#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00001875
reed@android.com8a1c16f2008-12-17 15:59:43 +00001876 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1877 where we need to rotate x,y by 90 or -90
1878 */
1879 x = (x ^ xsign) - xsign;
1880 y = (y ^ ysign) - ysign;
1881 if (add & 64) // quads 1 or 3 need to swap x,y
1882 SkTSwap<SkFixed>(x, y);
1883
1884 unsigned result = add + atan_0_90(y, x);
1885 SkASSERT(result < 256);
1886 return result;
1887}
1888
1889void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1890{
1891 SkMatrix::MapXYProc proc = fDstToIndexProc;
1892 const SkMatrix& matrix = fDstToIndex;
1893 const SkPMColor* cache = this->getCache32();
1894 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001895
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896 if (fDstToIndexClass != kPerspective_MatrixClass)
1897 {
1898 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1899 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1900 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1901 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001902
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1904 {
1905 SkFixed storage[2];
1906 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1907 &storage[0], &storage[1]);
1908 dx = storage[0];
1909 dy = storage[1];
1910 }
1911 else
1912 {
1913 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1914 dx = SkScalarToFixed(matrix.getScaleX());
1915 dy = SkScalarToFixed(matrix.getSkewY());
1916 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001917
reed@android.com8a1c16f2008-12-17 15:59:43 +00001918 for (; count > 0; --count)
1919 {
1920 *dstC++ = cache[SkATan2_255(fy, fx)];
1921 fx += dx;
1922 fy += dy;
1923 }
1924 }
1925 else // perspective case
1926 {
1927 for (int stop = x + count; x < stop; x++)
1928 {
1929 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1930 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001931
reed@android.com8a1c16f2008-12-17 15:59:43 +00001932 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1933 SkScalarToFixed(srcPt.fX));
1934 *dstC++ = cache[index];
1935 }
1936 }
1937}
1938
1939void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1940{
1941 SkMatrix::MapXYProc proc = fDstToIndexProc;
1942 const SkMatrix& matrix = fDstToIndex;
1943 const uint16_t* cache = this->getCache16();
1944 int toggle = ((x ^ y) & 1) << kCache16Bits;
1945 SkPoint srcPt;
1946
1947 if (fDstToIndexClass != kPerspective_MatrixClass)
1948 {
1949 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1950 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1951 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1952 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001953
reed@android.com8a1c16f2008-12-17 15:59:43 +00001954 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1955 {
1956 SkFixed storage[2];
1957 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1958 &storage[0], &storage[1]);
1959 dx = storage[0];
1960 dy = storage[1];
1961 }
1962 else
1963 {
1964 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1965 dx = SkScalarToFixed(matrix.getScaleX());
1966 dy = SkScalarToFixed(matrix.getSkewY());
1967 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001968
reed@android.com8a1c16f2008-12-17 15:59:43 +00001969 for (; count > 0; --count)
1970 {
1971 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1972 *dstC++ = cache[toggle + index];
1973 toggle ^= (1 << kCache16Bits);
1974 fx += dx;
1975 fy += dy;
1976 }
1977 }
1978 else // perspective case
1979 {
1980 for (int stop = x + count; x < stop; x++)
1981 {
1982 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1983 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001984
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1986 SkScalarToFixed(srcPt.fX));
1987 index >>= (8 - kCache16Bits);
1988 *dstC++ = cache[toggle + index];
1989 toggle ^= (1 << kCache16Bits);
1990 }
1991 }
1992}
1993
1994///////////////////////////////////////////////////////////////////////////
1995///////////////////////////////////////////////////////////////////////////
1996
1997// assumes colors is SkColor* and pos is SkScalar*
1998#define EXPAND_1_COLOR(count) \
1999 SkColor tmp[2]; \
2000 do { \
2001 if (1 == count) { \
2002 tmp[0] = tmp[1] = colors[0]; \
2003 colors = tmp; \
2004 pos = NULL; \
2005 count = 2; \
2006 } \
2007 } while (0)
2008
2009SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
2010 const SkColor colors[], const SkScalar pos[], int colorCount,
2011 SkShader::TileMode mode, SkUnitMapper* mapper)
2012{
2013 if (NULL == pts || NULL == colors || colorCount < 1) {
2014 return NULL;
2015 }
2016 EXPAND_1_COLOR(colorCount);
2017
reed@android.comab840b82009-07-01 17:00:03 +00002018 return SkNEW_ARGS(Linear_Gradient,
2019 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020}
2021
2022SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
2023 const SkColor colors[], const SkScalar pos[], int colorCount,
2024 SkShader::TileMode mode, SkUnitMapper* mapper)
2025{
2026 if (radius <= 0 || NULL == colors || colorCount < 1) {
2027 return NULL;
2028 }
2029 EXPAND_1_COLOR(colorCount);
2030
reed@android.comab840b82009-07-01 17:00:03 +00002031 return SkNEW_ARGS(Radial_Gradient,
2032 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033}
2034
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002035SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2036 SkScalar startRadius,
2037 const SkPoint& end,
2038 SkScalar endRadius,
2039 const SkColor colors[],
2040 const SkScalar pos[],
2041 int colorCount,
2042 SkShader::TileMode mode,
2043 SkUnitMapper* mapper)
2044{
2045 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2046 return NULL;
2047 }
2048 EXPAND_1_COLOR(colorCount);
2049
2050 return SkNEW_ARGS(Two_Point_Radial_Gradient,
2051 (start, startRadius, end, endRadius, colors, pos, colorCount, mode, mapper));
2052}
2053
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2055 const SkColor colors[],
2056 const SkScalar pos[],
2057 int count, SkUnitMapper* mapper)
2058{
2059 if (NULL == colors || count < 1) {
2060 return NULL;
2061 }
2062 EXPAND_1_COLOR(count);
2063
2064 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2065}
2066
2067static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2068 Linear_Gradient::CreateProc);
2069
2070static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2071 Radial_Gradient::CreateProc);
2072
2073static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2074 Sweep_Gradient::CreateProc);
2075