blob: f43cf4b15275a9a86c7a08ab6d9cf5204978b46c [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
1364static inline SkFixed two_point_radial(SkFixed b, SkFixed fx, SkFixed fy, SkFixed sr2d2, SkFixed foura, SkFixed oneOverTwoA, bool posRoot) {
1365 SkFixed c = SkFixedSquare(fx) + SkFixedSquare(fy) - sr2d2;
1366 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1367 if (discrim < 0) {
1368 discrim = -discrim;
1369 }
1370 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1371 if (posRoot) {
1372 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1373 } else {
1374 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1375 }
1376}
1377
1378class Two_Point_Radial_Gradient : public Gradient_Shader {
1379public:
1380 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1381 const SkPoint& end, SkScalar endRadius,
1382 const SkColor colors[], const SkScalar pos[],
1383 int colorCount, SkShader::TileMode mode,
1384 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001385 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1386 fCenter1(start),
1387 fCenter2(end),
1388 fRadius1(startRadius),
1389 fRadius2(endRadius) {
1390 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001391 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001392
1393 virtual BitmapType asABitmap(SkBitmap* bitmap,
1394 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001395 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001396 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001397 if (bitmap) {
1398 this->commonAsABitmap(bitmap);
1399 }
1400 SkScalar diffL = 0; // just to avoid gcc warning
1401 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001402 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001403 SkScalarSquare(fDiff.fY));
1404 }
1405 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001406 if (diffL) {
1407 SkScalar invDiffL = SkScalarInvert(diffL);
1408 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1409 SkScalarMul(invDiffL, fDiff.fX));
1410 } else {
1411 matrix->reset();
1412 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001413 matrix->preConcat(fPtsToUnit);
1414 }
1415 if (xy) {
1416 xy[0] = fTileMode;
1417 xy[1] = kClamp_TileMode;
1418 }
1419 if (NULL != twoPointRadialParams) {
1420 twoPointRadialParams[0] = diffL;
1421 twoPointRadialParams[1] = fStartRadius;
1422 twoPointRadialParams[2] = fDiffRadius;
1423 }
1424 return kTwoPointRadial_BitmapType;
1425 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001426
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001427 virtual GradientType asAGradient(GradientInfo* info) const {
1428 if (info) {
1429 commonAsAGradient(info);
1430 info->fPoint[0] = fCenter1;
1431 info->fPoint[1] = fCenter2;
1432 info->fRadius[0] = fRadius1;
1433 info->fRadius[1] = fRadius2;
1434 }
1435 return kRadial2_GradientType;
1436 }
1437
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001438 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1439 {
1440 SkASSERT(count > 0);
1441
1442 // Zero difference between radii: fill with transparent black.
1443 if (fDiffRadius == 0) {
1444 sk_bzero(dstC, count * sizeof(*dstC));
1445 return;
1446 }
1447 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1448 TileProc proc = fTileProc;
1449 const SkPMColor* cache = this->getCache32();
1450 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1451 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1452 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1453 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1454 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1455 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1456 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);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001462 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1463 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1464
1465 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1466 {
1467 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1468 }
1469 else
1470 {
1471 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1472 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1473 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1474 }
1475 SkFixed b = (SkFixedMul(diffx, fx) +
1476 SkFixedMul(diffy, fy) - startRadius) << 1;
1477 SkFixed db = (SkFixedMul(diffx, dx) +
1478 SkFixedMul(diffy, dy)) << 1;
1479 if (proc == clamp_tileproc)
1480 {
1481 for (; count > 0; --count) {
1482 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1483 SkFixed index = SkClampMax(t, 0xFFFF);
1484 SkASSERT(index <= 0xFFFF);
1485 *dstC++ = cache[index >> (16 - kCache32Bits)];
1486 fx += dx;
1487 fy += dy;
1488 b += db;
1489 }
1490 }
1491 else if (proc == mirror_tileproc)
1492 {
1493 for (; count > 0; --count) {
1494 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1495 SkFixed index = mirror_tileproc(t);
1496 SkASSERT(index <= 0xFFFF);
1497 *dstC++ = cache[index >> (16 - kCache32Bits)];
1498 fx += dx;
1499 fy += dy;
1500 b += db;
1501 }
1502 }
1503 else
1504 {
1505 SkASSERT(proc == repeat_tileproc);
1506 for (; count > 0; --count) {
1507 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1508 SkFixed index = repeat_tileproc(t);
1509 SkASSERT(index <= 0xFFFF);
1510 *dstC++ = cache[index >> (16 - kCache32Bits)];
1511 fx += dx;
1512 fy += dy;
1513 b += db;
1514 }
1515 }
1516 }
1517 else // perspective case
1518 {
reed@android.com6c59a172009-09-22 20:24:05 +00001519 SkScalar dstX = SkIntToScalar(x);
1520 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001521 for (; count > 0; --count) {
1522 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001523 dstProc(fDstToIndex, dstX, dstY, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001524 SkFixed fx = SkScalarToFixed(srcPt.fX);
1525 SkFixed fy = SkScalarToFixed(srcPt.fY);
1526 SkFixed b = (SkFixedMul(diffx, fx) +
1527 SkFixedMul(diffy, fy) - startRadius) << 1;
1528 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1529 SkFixed index = proc(t);
1530 SkASSERT(index <= 0xFFFF);
1531 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001532 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001533 }
1534 }
1535 }
1536
reed@android.com6c59a172009-09-22 20:24:05 +00001537 virtual bool setContext(const SkBitmap& device,
1538 const SkPaint& paint,
1539 const SkMatrix& matrix) {
1540 if (!this->INHERITED::setContext(device, paint, matrix)) {
1541 return false;
1542 }
1543
1544 // we don't have a span16 proc
1545 fFlags &= ~kHasSpan16_Flag;
1546 return true;
1547 }
1548
reed@google.com55b8e8c2011-01-13 16:22:35 +00001549 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001550 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1551 }
1552
reed@android.combcfc7332009-11-10 16:19:39 +00001553 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1554 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001555 buffer.writeScalar(fCenter1.fX);
1556 buffer.writeScalar(fCenter1.fY);
1557 buffer.writeScalar(fCenter2.fX);
1558 buffer.writeScalar(fCenter2.fY);
1559 buffer.writeScalar(fRadius1);
1560 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001561 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001562
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001563protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001564 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001565 : Gradient_Shader(buffer),
1566 fCenter1(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1567 fCenter2(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1568 fRadius1(buffer.readScalar()),
1569 fRadius2(buffer.readScalar()) {
1570 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001571 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001572 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001573
1574private:
1575 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001576 const SkPoint fCenter1;
1577 const SkPoint fCenter2;
1578 const SkScalar fRadius1;
1579 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001580 SkPoint fDiff;
1581 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001582
1583 void init() {
1584 fDiff = fCenter1 - fCenter2;
1585 fDiffRadius = fRadius2 - fRadius1;
1586 SkScalar inv = SkScalarInvert(fDiffRadius);
1587 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1588 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1589 fStartRadius = SkScalarMul(fRadius1, inv);
1590 fSr2D2 = SkScalarSquare(fStartRadius);
1591 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1592 fOneOverTwoA = SkScalarInvert(fA * 2);
1593
1594 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1595 fPtsToUnit.postScale(inv, inv);
1596 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001597};
1598
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599///////////////////////////////////////////////////////////////////////////////
1600
1601class Sweep_Gradient : public Gradient_Shader {
1602public:
1603 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1604 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001605 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1606 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 {
1608 fPtsToUnit.setTranslate(-cx, -cy);
1609 }
1610 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1611 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001612
1613 virtual BitmapType asABitmap(SkBitmap* bitmap,
1614 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001615 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001616 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001617 if (bitmap) {
1618 this->commonAsABitmap(bitmap);
1619 }
1620 if (matrix) {
1621 *matrix = fPtsToUnit;
1622 }
1623 if (xy) {
1624 xy[0] = fTileMode;
1625 xy[1] = kClamp_TileMode;
1626 }
1627 return kSweep_BitmapType;
1628 }
1629
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001630 virtual GradientType asAGradient(GradientInfo* info) const {
1631 if (info) {
1632 commonAsAGradient(info);
1633 info->fPoint[0] = fCenter;
1634 }
1635 return kSweep_GradientType;
1636 }
1637
reed@android.com8a1c16f2008-12-17 15:59:43 +00001638 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1639 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1640 }
1641
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001642 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1643 this->INHERITED::flatten(buffer);
1644 buffer.writeScalar(fCenter.fX);
1645 buffer.writeScalar(fCenter.fY);
1646 }
1647
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001649 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1650 : Gradient_Shader(buffer),
1651 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
1652 }
1653
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 virtual Factory getFactory() { return CreateProc; }
1655
1656private:
1657 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001658 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001659};
1660
1661#ifdef COMPUTE_SWEEP_TABLE
1662#define PI 3.14159265
1663static bool gSweepTableReady;
1664static uint8_t gSweepTable[65];
1665
1666/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1667 We scale the results to [0..32]
1668*/
1669static const uint8_t* build_sweep_table()
1670{
1671 if (!gSweepTableReady)
1672 {
1673 const int N = 65;
1674 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001675
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 for (int i = 0; i < N; i++)
1677 {
1678 double arg = i / DENOM;
1679 double v = atan(arg);
1680 int iv = (int)round(v * DENOM * 2 / PI);
1681// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1682 printf("%d, ", iv);
1683 gSweepTable[i] = iv;
1684 }
1685 gSweepTableReady = true;
1686 }
1687 return gSweepTable;
1688}
1689#else
1690static const uint8_t gSweepTable[] = {
1691 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1692 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1693 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1694 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1695 32
1696};
1697static const uint8_t* build_sweep_table() { return gSweepTable; }
1698#endif
1699
1700// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1701// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1702// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1703
1704//unsigned div_64(int numer, int denom);
1705static unsigned div_64(int numer, int denom)
1706{
1707 SkASSERT(numer <= denom);
1708 SkASSERT(numer > 0);
1709 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001710
reed@android.com8a1c16f2008-12-17 15:59:43 +00001711 int nbits = SkCLZ(numer);
1712 int dbits = SkCLZ(denom);
1713 int bits = 6 - nbits + dbits;
1714 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001715
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716 if (bits < 0) // detect underflow
1717 return 0;
1718
1719 denom <<= dbits - 1;
1720 numer <<= nbits - 1;
1721
1722 unsigned result = 0;
1723
1724 // do the first one
1725 if ((numer -= denom) >= 0)
1726 result = 1;
1727 else
1728 numer += denom;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001729
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 // Now fall into our switch statement if there are more bits to compute
1731 if (bits > 0)
1732 {
1733 // make room for the rest of the answer bits
1734 result <<= bits;
1735 switch (bits) {
1736 case 6:
1737 if ((numer = (numer << 1) - denom) >= 0)
1738 result |= 32;
1739 else
1740 numer += denom;
1741 case 5:
1742 if ((numer = (numer << 1) - denom) >= 0)
1743 result |= 16;
1744 else
1745 numer += denom;
1746 case 4:
1747 if ((numer = (numer << 1) - denom) >= 0)
1748 result |= 8;
1749 else
1750 numer += denom;
1751 case 3:
1752 if ((numer = (numer << 1) - denom) >= 0)
1753 result |= 4;
1754 else
1755 numer += denom;
1756 case 2:
1757 if ((numer = (numer << 1) - denom) >= 0)
1758 result |= 2;
1759 else
1760 numer += denom;
1761 case 1:
1762 default: // not strictly need, but makes GCC make better ARM code
1763 if ((numer = (numer << 1) - denom) >= 0)
1764 result |= 1;
1765 else
1766 numer += denom;
1767 }
1768 }
1769 return result;
1770}
1771
1772// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1773static unsigned atan_0_90(SkFixed y, SkFixed x)
1774{
1775#ifdef SK_DEBUG
1776 {
1777 static bool gOnce;
1778 if (!gOnce)
1779 {
1780 gOnce = true;
1781 SkASSERT(div_64(55, 55) == 64);
1782 SkASSERT(div_64(128, 256) == 32);
1783 SkASSERT(div_64(2326528, 4685824) == 31);
1784 SkASSERT(div_64(753664, 5210112) == 9);
1785 SkASSERT(div_64(229376, 4882432) == 3);
1786 SkASSERT(div_64(2, 64) == 2);
1787 SkASSERT(div_64(1, 64) == 1);
1788 // test that we handle underflow correctly
1789 SkASSERT(div_64(12345, 0x54321234) == 0);
1790 }
1791 }
1792#endif
1793
1794 SkASSERT(y > 0 && x > 0);
1795 const uint8_t* table = build_sweep_table();
1796
1797 unsigned result;
1798 bool swap = (x < y);
1799 if (swap)
1800 {
1801 // first part of the atan(v) = PI/2 - atan(1/v) identity
1802 // since our div_64 and table want v <= 1, where v = y/x
1803 SkTSwap<SkFixed>(x, y);
1804 }
1805
1806 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001807
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808#ifdef SK_DEBUG
1809 {
1810 unsigned result2 = SkDivBits(y, x, 6);
1811 SkASSERT(result2 == result ||
1812 (result == 1 && result2 == 0));
1813 }
1814#endif
1815
1816 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1817 result = table[result];
1818
1819 if (swap)
1820 {
1821 // complete the atan(v) = PI/2 - atan(1/v) identity
1822 result = 64 - result;
1823 // pin to 63
1824 result -= result >> 6;
1825 }
1826
1827 SkASSERT(result <= 63);
1828 return result;
1829}
1830
1831// returns angle in a circle [0..2PI) -> [0..255]
1832static unsigned SkATan2_255(SkFixed y, SkFixed x)
1833{
1834 if (x == 0)
1835 {
1836 if (y == 0)
1837 return 0;
1838 return y < 0 ? 192 : 64;
1839 }
1840 if (y == 0)
1841 return x < 0 ? 128 : 0;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001842
reed@android.com8a1c16f2008-12-17 15:59:43 +00001843 /* Find the right quadrant for x,y
1844 Since atan_0_90 only handles the first quadrant, we rotate x,y
1845 appropriately before calling it, and then add the right amount
1846 to account for the real quadrant.
1847 quadrant 0 : add 0 | x > 0 && y > 0
1848 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1849 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1850 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001851
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 map x<0 to (1 << 6)
1853 map y<0 to (3 << 6)
1854 add = map_x ^ map_y
1855 */
1856 int xsign = x >> 31;
1857 int ysign = y >> 31;
1858 int add = ((-xsign) ^ (ysign & 3)) << 6;
1859
1860#ifdef SK_DEBUG
1861 if (0 == add)
1862 SkASSERT(x > 0 && y > 0);
1863 else if (64 == add)
1864 SkASSERT(x < 0 && y > 0);
1865 else if (128 == add)
1866 SkASSERT(x < 0 && y < 0);
1867 else if (192 == add)
1868 SkASSERT(x > 0 && y < 0);
1869 else
1870 SkASSERT(!"bad value for add");
1871#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00001872
reed@android.com8a1c16f2008-12-17 15:59:43 +00001873 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1874 where we need to rotate x,y by 90 or -90
1875 */
1876 x = (x ^ xsign) - xsign;
1877 y = (y ^ ysign) - ysign;
1878 if (add & 64) // quads 1 or 3 need to swap x,y
1879 SkTSwap<SkFixed>(x, y);
1880
1881 unsigned result = add + atan_0_90(y, x);
1882 SkASSERT(result < 256);
1883 return result;
1884}
1885
1886void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1887{
1888 SkMatrix::MapXYProc proc = fDstToIndexProc;
1889 const SkMatrix& matrix = fDstToIndex;
1890 const SkPMColor* cache = this->getCache32();
1891 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001892
reed@android.com8a1c16f2008-12-17 15:59:43 +00001893 if (fDstToIndexClass != kPerspective_MatrixClass)
1894 {
1895 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1896 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1897 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1898 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001899
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1901 {
1902 SkFixed storage[2];
1903 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1904 &storage[0], &storage[1]);
1905 dx = storage[0];
1906 dy = storage[1];
1907 }
1908 else
1909 {
1910 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1911 dx = SkScalarToFixed(matrix.getScaleX());
1912 dy = SkScalarToFixed(matrix.getSkewY());
1913 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001914
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 for (; count > 0; --count)
1916 {
1917 *dstC++ = cache[SkATan2_255(fy, fx)];
1918 fx += dx;
1919 fy += dy;
1920 }
1921 }
1922 else // perspective case
1923 {
1924 for (int stop = x + count; x < stop; x++)
1925 {
1926 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1927 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001928
reed@android.com8a1c16f2008-12-17 15:59:43 +00001929 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1930 SkScalarToFixed(srcPt.fX));
1931 *dstC++ = cache[index];
1932 }
1933 }
1934}
1935
1936void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1937{
1938 SkMatrix::MapXYProc proc = fDstToIndexProc;
1939 const SkMatrix& matrix = fDstToIndex;
1940 const uint16_t* cache = this->getCache16();
1941 int toggle = ((x ^ y) & 1) << kCache16Bits;
1942 SkPoint srcPt;
1943
1944 if (fDstToIndexClass != kPerspective_MatrixClass)
1945 {
1946 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1947 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1948 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1949 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001950
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1952 {
1953 SkFixed storage[2];
1954 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1955 &storage[0], &storage[1]);
1956 dx = storage[0];
1957 dy = storage[1];
1958 }
1959 else
1960 {
1961 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1962 dx = SkScalarToFixed(matrix.getScaleX());
1963 dy = SkScalarToFixed(matrix.getSkewY());
1964 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001965
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 for (; count > 0; --count)
1967 {
1968 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1969 *dstC++ = cache[toggle + index];
1970 toggle ^= (1 << kCache16Bits);
1971 fx += dx;
1972 fy += dy;
1973 }
1974 }
1975 else // perspective case
1976 {
1977 for (int stop = x + count; x < stop; x++)
1978 {
1979 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1980 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001981
reed@android.com8a1c16f2008-12-17 15:59:43 +00001982 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1983 SkScalarToFixed(srcPt.fX));
1984 index >>= (8 - kCache16Bits);
1985 *dstC++ = cache[toggle + index];
1986 toggle ^= (1 << kCache16Bits);
1987 }
1988 }
1989}
1990
1991///////////////////////////////////////////////////////////////////////////
1992///////////////////////////////////////////////////////////////////////////
1993
1994// assumes colors is SkColor* and pos is SkScalar*
1995#define EXPAND_1_COLOR(count) \
1996 SkColor tmp[2]; \
1997 do { \
1998 if (1 == count) { \
1999 tmp[0] = tmp[1] = colors[0]; \
2000 colors = tmp; \
2001 pos = NULL; \
2002 count = 2; \
2003 } \
2004 } while (0)
2005
2006SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
2007 const SkColor colors[], const SkScalar pos[], int colorCount,
2008 SkShader::TileMode mode, SkUnitMapper* mapper)
2009{
2010 if (NULL == pts || NULL == colors || colorCount < 1) {
2011 return NULL;
2012 }
2013 EXPAND_1_COLOR(colorCount);
2014
reed@android.comab840b82009-07-01 17:00:03 +00002015 return SkNEW_ARGS(Linear_Gradient,
2016 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002017}
2018
2019SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
2020 const SkColor colors[], const SkScalar pos[], int colorCount,
2021 SkShader::TileMode mode, SkUnitMapper* mapper)
2022{
2023 if (radius <= 0 || NULL == colors || colorCount < 1) {
2024 return NULL;
2025 }
2026 EXPAND_1_COLOR(colorCount);
2027
reed@android.comab840b82009-07-01 17:00:03 +00002028 return SkNEW_ARGS(Radial_Gradient,
2029 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030}
2031
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002032SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2033 SkScalar startRadius,
2034 const SkPoint& end,
2035 SkScalar endRadius,
2036 const SkColor colors[],
2037 const SkScalar pos[],
2038 int colorCount,
2039 SkShader::TileMode mode,
2040 SkUnitMapper* mapper)
2041{
2042 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2043 return NULL;
2044 }
2045 EXPAND_1_COLOR(colorCount);
2046
2047 return SkNEW_ARGS(Two_Point_Radial_Gradient,
2048 (start, startRadius, end, endRadius, colors, pos, colorCount, mode, mapper));
2049}
2050
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2052 const SkColor colors[],
2053 const SkScalar pos[],
2054 int count, SkUnitMapper* mapper)
2055{
2056 if (NULL == colors || count < 1) {
2057 return NULL;
2058 }
2059 EXPAND_1_COLOR(count);
2060
2061 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2062}
2063
2064static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2065 Linear_Gradient::CreateProc);
2066
2067static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2068 Radial_Gradient::CreateProc);
2069
2070static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2071 Sweep_Gradient::CreateProc);
2072