blob: dca87b01781910538c5d98aba616d36845e5e683 [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 {
1099 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1100 unsigned fi = mirror_tileproc(dist);
1101 SkASSERT(fi <= 0xFFFF);
1102 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1103 fx += dx;
1104 fy += dy;
1105 } while (--count != 0);
1106 }
1107 else
1108 {
1109 SkASSERT(proc == repeat_tileproc);
1110 do {
1111 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1112 unsigned fi = repeat_tileproc(dist);
1113 SkASSERT(fi <= 0xFFFF);
1114 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1115 fx += dx;
1116 fy += dy;
1117 } while (--count != 0);
1118 }
1119 }
1120 else // perspective case
1121 {
1122 SkScalar dstX = SkIntToScalar(x);
1123 SkScalar dstY = SkIntToScalar(y);
1124 do {
1125 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1126 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1127 SkASSERT(fi <= 0xFFFF);
1128 *dstC++ = cache[fi >> (16 - kCache32Bits)];
1129 dstX += SK_Scalar1;
1130 } while (--count != 0);
1131 }
1132 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001133
1134 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001135 SkASSERT(count > 0);
1136
1137 SkPoint srcPt;
1138 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1139 TileProc proc = fTileProc;
1140 const uint16_t* cache = this->getCache16();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001141 int toggle = ((x ^ y) & 1) << kCache16Bits;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001142
reed@android.com3c9b2a42009-08-27 19:28:37 +00001143 if (fDstToIndexClass != kPerspective_MatrixClass) {
reed@google.comdc731fd2010-12-23 15:19:47 +00001144 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1145 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001146 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1147 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1148
reed@android.com3c9b2a42009-08-27 19:28:37 +00001149 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001150 SkFixed storage[2];
1151 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
1152 dx = storage[0];
1153 dy = storage[1];
reed@android.com3c9b2a42009-08-27 19:28:37 +00001154 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1156 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1157 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1158 }
1159
reed@android.com3c9b2a42009-08-27 19:28:37 +00001160 if (proc == clamp_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161 const uint8_t* sqrt_table = gSqrt8Table;
1162
1163 /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
1164 rather than 0xFFFF which is slower. This is a compromise, since it reduces our
1165 precision, but that appears to be visually OK. If we decide this is OK for
1166 all of our cases, we could (it seems) put this scale-down into fDstToIndex,
1167 to avoid having to do these extra shifts each time.
1168 */
1169 fx >>= 1;
1170 dx >>= 1;
1171 fy >>= 1;
1172 dy >>= 1;
reed@android.com3c9b2a42009-08-27 19:28:37 +00001173 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 +00001174 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1175 fy *= fy;
1176 do {
1177 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1178 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
1179 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1180 fx += dx;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001181 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1182 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001183 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001184 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001185 do {
1186 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
1187 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
1188 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
1189 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
1190 fx += dx;
1191 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001192 *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
1193 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194 } while (--count != 0);
1195 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001196 } else if (proc == mirror_tileproc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197 do {
1198 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1199 unsigned fi = mirror_tileproc(dist);
1200 SkASSERT(fi <= 0xFFFF);
1201 fx += dx;
1202 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1204 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205 } while (--count != 0);
reed@android.com3c9b2a42009-08-27 19:28:37 +00001206 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001207 SkASSERT(proc == repeat_tileproc);
1208 do {
1209 SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
1210 unsigned fi = repeat_tileproc(dist);
1211 SkASSERT(fi <= 0xFFFF);
1212 fx += dx;
1213 fy += dy;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214 *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
1215 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001216 } while (--count != 0);
1217 }
reed@android.com3c9b2a42009-08-27 19:28:37 +00001218 } else { // perspective case
reed@android.com8a1c16f2008-12-17 15:59:43 +00001219 SkScalar dstX = SkIntToScalar(x);
1220 SkScalar dstY = SkIntToScalar(y);
1221 do {
1222 dstProc(fDstToIndex, dstX, dstY, &srcPt);
1223 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
1224 SkASSERT(fi <= 0xFFFF);
1225
1226 int index = fi >> (16 - kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227 *dstC++ = cache[toggle + index];
1228 toggle ^= (1 << kCache16Bits);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229
1230 dstX += SK_Scalar1;
1231 } while (--count != 0);
1232 }
1233 }
1234
reed@google.com55b8e8c2011-01-13 16:22:35 +00001235 virtual BitmapType asABitmap(SkBitmap* bitmap,
1236 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001237 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001238 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001239 if (bitmap) {
1240 this->commonAsABitmap(bitmap);
1241 }
1242 if (matrix) {
1243 matrix->setScale(SkIntToScalar(kCache32Count), SkIntToScalar(kCache32Count));
1244 matrix->preConcat(fPtsToUnit);
1245 }
1246 if (xy) {
1247 xy[0] = fTileMode;
1248 xy[1] = kClamp_TileMode;
1249 }
1250 return kRadial_BitmapType;
1251 }
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001252 virtual GradientType asAGradient(GradientInfo* info) const {
1253 if (info) {
1254 commonAsAGradient(info);
1255 info->fPoint[0] = fCenter;
1256 info->fRadius[0] = fRadius;
1257 }
1258 return kRadial_GradientType;
1259 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001260
1261 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262 return SkNEW_ARGS(Radial_Gradient, (buffer));
1263 }
1264
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001265 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1266 this->INHERITED::flatten(buffer);
1267 buffer.writeScalar(fCenter.fX);
1268 buffer.writeScalar(fCenter.fY);
1269 buffer.writeScalar(fRadius);
1270 }
1271
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001273 Radial_Gradient(SkFlattenableReadBuffer& buffer)
1274 : Gradient_Shader(buffer),
1275 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1276 fRadius(buffer.readScalar()) {
1277 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278 virtual Factory getFactory() { return CreateProc; }
1279
1280private:
1281 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001282 const SkPoint fCenter;
1283 const SkScalar fRadius;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284};
1285
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001286/* Two-point radial gradients are specified by two circles, each with a center
1287 point and radius. The gradient can be considered to be a series of
1288 concentric circles, with the color interpolated from the start circle
1289 (at t=0) to the end circle (at t=1).
1290
1291 For each point (x, y) in the span, we want to find the
1292 interpolated circle that intersects that point. The center
1293 of the desired circle (Cx, Cy) falls at some distance t
1294 along the line segment between the start point (Sx, Sy) and
1295 end point (Ex, Ey):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001296
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001297 Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
1298 Cy = (1 - t) * Sy + t * Ey
reed@google.com55b8e8c2011-01-13 16:22:35 +00001299
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001300 The radius of the desired circle (r) is also a linear interpolation t
1301 between the start and end radii (Sr and Er):
reed@google.com55b8e8c2011-01-13 16:22:35 +00001302
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001303 r = (1 - t) * Sr + t * Er
reed@google.com55b8e8c2011-01-13 16:22:35 +00001304
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001305 But
reed@google.com55b8e8c2011-01-13 16:22:35 +00001306
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001307 (x - Cx)^2 + (y - Cy)^2 = r^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001308
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001309 so
reed@google.com55b8e8c2011-01-13 16:22:35 +00001310
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001311 (x - ((1 - t) * Sx + t * Ex))^2
1312 + (y - ((1 - t) * Sy + t * Ey))^2
1313 = ((1 - t) * Sr + t * Er)^2
reed@google.com55b8e8c2011-01-13 16:22:35 +00001314
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001315 Solving for t yields
reed@google.com55b8e8c2011-01-13 16:22:35 +00001316
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001317 [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
1318 + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
1319 + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001320
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001321 To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
1322
1323 [Dx^2 + Dy^2 - Dr^2)] * t^2
1324 + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
1325 + [dx^2 + dy^2 - Sr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001326
1327 A quadratic in t. The two roots of the quadratic reflect the two
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001328 possible circles on which the point may fall. Solving for t yields
1329 the gradient value to use.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001330
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001331 If a<0, the start circle is entirely contained in the
1332 end circle, and one of the roots will be <0 or >1 (off the line
1333 segment). If a>0, the start circle falls at least partially
1334 outside the end circle (or vice versa), and the gradient
1335 defines a "tube" where a point may be on one circle (on the
1336 inside of the tube) or the other (outside of the tube). We choose
1337 one arbitrarily.
reed@google.com55b8e8c2011-01-13 16:22:35 +00001338
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001339 In order to keep the math to within the limits of fixed point,
1340 we divide the entire quadratic by Dr^2, and replace
1341 (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
reed@google.com55b8e8c2011-01-13 16:22:35 +00001342
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001343 [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
1344 + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
1345 + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001346
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001347 (x' and y' are computed by appending the subtract and scale to the
1348 fDstToIndex matrix in the constructor).
1349
1350 Since the 'A' component of the quadratic is independent of x' and y', it
1351 is precomputed in the constructor. Since the 'B' component is linear in
1352 x' and y', if x and y are linear in the span, 'B' can be computed
reed@google.com55b8e8c2011-01-13 16:22:35 +00001353 incrementally with a simple delta (db below). If it is not (e.g.,
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001354 a perspective projection), it must be computed in the loop.
1355
1356*/
1357
1358static inline SkFixed two_point_radial(SkFixed b, SkFixed fx, SkFixed fy, SkFixed sr2d2, SkFixed foura, SkFixed oneOverTwoA, bool posRoot) {
1359 SkFixed c = SkFixedSquare(fx) + SkFixedSquare(fy) - sr2d2;
1360 SkFixed discrim = SkFixedSquare(b) - SkFixedMul(foura, c);
1361 if (discrim < 0) {
1362 discrim = -discrim;
1363 }
1364 SkFixed rootDiscrim = SkFixedSqrt(discrim);
1365 if (posRoot) {
1366 return SkFixedMul(-b + rootDiscrim, oneOverTwoA);
1367 } else {
1368 return SkFixedMul(-b - rootDiscrim, oneOverTwoA);
1369 }
1370}
1371
1372class Two_Point_Radial_Gradient : public Gradient_Shader {
1373public:
1374 Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius,
1375 const SkPoint& end, SkScalar endRadius,
1376 const SkColor colors[], const SkScalar pos[],
1377 int colorCount, SkShader::TileMode mode,
1378 SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001379 : Gradient_Shader(colors, pos, colorCount, mode, mapper),
1380 fCenter1(start),
1381 fCenter2(end),
1382 fRadius1(startRadius),
1383 fRadius2(endRadius) {
1384 init();
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001385 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001386
1387 virtual BitmapType asABitmap(SkBitmap* bitmap,
1388 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001389 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001390 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001391 if (bitmap) {
1392 this->commonAsABitmap(bitmap);
1393 }
1394 SkScalar diffL = 0; // just to avoid gcc warning
1395 if (matrix || twoPointRadialParams) {
reed@google.com55b8e8c2011-01-13 16:22:35 +00001396 diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
reed@google.comdc731fd2010-12-23 15:19:47 +00001397 SkScalarSquare(fDiff.fY));
1398 }
1399 if (matrix) {
bsalomon@google.comdc66c422011-04-06 19:35:37 +00001400 if (diffL) {
1401 SkScalar invDiffL = SkScalarInvert(diffL);
1402 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
1403 SkScalarMul(invDiffL, fDiff.fX));
1404 } else {
1405 matrix->reset();
1406 }
reed@google.comdc731fd2010-12-23 15:19:47 +00001407 matrix->preConcat(fPtsToUnit);
1408 }
1409 if (xy) {
1410 xy[0] = fTileMode;
1411 xy[1] = kClamp_TileMode;
1412 }
1413 if (NULL != twoPointRadialParams) {
1414 twoPointRadialParams[0] = diffL;
1415 twoPointRadialParams[1] = fStartRadius;
1416 twoPointRadialParams[2] = fDiffRadius;
1417 }
1418 return kTwoPointRadial_BitmapType;
1419 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001420
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001421 virtual GradientType asAGradient(GradientInfo* info) const {
1422 if (info) {
1423 commonAsAGradient(info);
1424 info->fPoint[0] = fCenter1;
1425 info->fPoint[1] = fCenter2;
1426 info->fRadius[0] = fRadius1;
1427 info->fRadius[1] = fRadius2;
1428 }
1429 return kRadial2_GradientType;
1430 }
1431
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001432 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1433 {
1434 SkASSERT(count > 0);
1435
1436 // Zero difference between radii: fill with transparent black.
1437 if (fDiffRadius == 0) {
1438 sk_bzero(dstC, count * sizeof(*dstC));
1439 return;
1440 }
1441 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1442 TileProc proc = fTileProc;
1443 const SkPMColor* cache = this->getCache32();
1444 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1445 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1446 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1447 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1448 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1449 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1450 bool posRoot = fDiffRadius < 0;
1451 if (fDstToIndexClass != kPerspective_MatrixClass)
1452 {
1453 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001454 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1455 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001456 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1457 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1458
1459 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1460 {
1461 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1462 }
1463 else
1464 {
1465 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1466 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1467 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1468 }
1469 SkFixed b = (SkFixedMul(diffx, fx) +
1470 SkFixedMul(diffy, fy) - startRadius) << 1;
1471 SkFixed db = (SkFixedMul(diffx, dx) +
1472 SkFixedMul(diffy, dy)) << 1;
1473 if (proc == clamp_tileproc)
1474 {
1475 for (; count > 0; --count) {
1476 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1477 SkFixed index = SkClampMax(t, 0xFFFF);
1478 SkASSERT(index <= 0xFFFF);
1479 *dstC++ = cache[index >> (16 - kCache32Bits)];
1480 fx += dx;
1481 fy += dy;
1482 b += db;
1483 }
1484 }
1485 else if (proc == mirror_tileproc)
1486 {
1487 for (; count > 0; --count) {
1488 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1489 SkFixed index = mirror_tileproc(t);
1490 SkASSERT(index <= 0xFFFF);
1491 *dstC++ = cache[index >> (16 - kCache32Bits)];
1492 fx += dx;
1493 fy += dy;
1494 b += db;
1495 }
1496 }
1497 else
1498 {
1499 SkASSERT(proc == repeat_tileproc);
1500 for (; count > 0; --count) {
1501 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1502 SkFixed index = repeat_tileproc(t);
1503 SkASSERT(index <= 0xFFFF);
1504 *dstC++ = cache[index >> (16 - kCache32Bits)];
1505 fx += dx;
1506 fy += dy;
1507 b += db;
1508 }
1509 }
1510 }
1511 else // perspective case
1512 {
reed@android.com6c59a172009-09-22 20:24:05 +00001513 SkScalar dstX = SkIntToScalar(x);
1514 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001515 for (; count > 0; --count) {
1516 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001517 dstProc(fDstToIndex, dstX, dstY, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001518 SkFixed fx = SkScalarToFixed(srcPt.fX);
1519 SkFixed fy = SkScalarToFixed(srcPt.fY);
1520 SkFixed b = (SkFixedMul(diffx, fx) +
1521 SkFixedMul(diffy, fy) - startRadius) << 1;
1522 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1523 SkFixed index = proc(t);
1524 SkASSERT(index <= 0xFFFF);
1525 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001526 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001527 }
1528 }
1529 }
1530
reed@android.com6c59a172009-09-22 20:24:05 +00001531 virtual bool setContext(const SkBitmap& device,
1532 const SkPaint& paint,
1533 const SkMatrix& matrix) {
1534 if (!this->INHERITED::setContext(device, paint, matrix)) {
1535 return false;
1536 }
1537
1538 // we don't have a span16 proc
1539 fFlags &= ~kHasSpan16_Flag;
1540 return true;
1541 }
1542
reed@google.com55b8e8c2011-01-13 16:22:35 +00001543 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001544 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1545 }
1546
reed@android.combcfc7332009-11-10 16:19:39 +00001547 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1548 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001549 buffer.writeScalar(fCenter1.fX);
1550 buffer.writeScalar(fCenter1.fY);
1551 buffer.writeScalar(fCenter2.fX);
1552 buffer.writeScalar(fCenter2.fY);
1553 buffer.writeScalar(fRadius1);
1554 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001555 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001556
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001557protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001558 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001559 : Gradient_Shader(buffer),
1560 fCenter1(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1561 fCenter2(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1562 fRadius1(buffer.readScalar()),
1563 fRadius2(buffer.readScalar()) {
1564 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001565 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001566 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001567
1568private:
1569 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001570 const SkPoint fCenter1;
1571 const SkPoint fCenter2;
1572 const SkScalar fRadius1;
1573 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001574 SkPoint fDiff;
1575 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001576
1577 void init() {
1578 fDiff = fCenter1 - fCenter2;
1579 fDiffRadius = fRadius2 - fRadius1;
1580 SkScalar inv = SkScalarInvert(fDiffRadius);
1581 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1582 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1583 fStartRadius = SkScalarMul(fRadius1, inv);
1584 fSr2D2 = SkScalarSquare(fStartRadius);
1585 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1586 fOneOverTwoA = SkScalarInvert(fA * 2);
1587
1588 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1589 fPtsToUnit.postScale(inv, inv);
1590 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001591};
1592
reed@android.com8a1c16f2008-12-17 15:59:43 +00001593///////////////////////////////////////////////////////////////////////////////
1594
1595class Sweep_Gradient : public Gradient_Shader {
1596public:
1597 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1598 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001599 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1600 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601 {
1602 fPtsToUnit.setTranslate(-cx, -cy);
1603 }
1604 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1605 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001606
1607 virtual BitmapType asABitmap(SkBitmap* bitmap,
1608 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001609 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001610 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001611 if (bitmap) {
1612 this->commonAsABitmap(bitmap);
1613 }
1614 if (matrix) {
1615 *matrix = fPtsToUnit;
1616 }
1617 if (xy) {
1618 xy[0] = fTileMode;
1619 xy[1] = kClamp_TileMode;
1620 }
1621 return kSweep_BitmapType;
1622 }
1623
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001624 virtual GradientType asAGradient(GradientInfo* info) const {
1625 if (info) {
1626 commonAsAGradient(info);
1627 info->fPoint[0] = fCenter;
1628 }
1629 return kSweep_GradientType;
1630 }
1631
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1633 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1634 }
1635
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001636 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1637 this->INHERITED::flatten(buffer);
1638 buffer.writeScalar(fCenter.fX);
1639 buffer.writeScalar(fCenter.fY);
1640 }
1641
reed@android.com8a1c16f2008-12-17 15:59:43 +00001642protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001643 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1644 : Gradient_Shader(buffer),
1645 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
1646 }
1647
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 virtual Factory getFactory() { return CreateProc; }
1649
1650private:
1651 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001652 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653};
1654
1655#ifdef COMPUTE_SWEEP_TABLE
1656#define PI 3.14159265
1657static bool gSweepTableReady;
1658static uint8_t gSweepTable[65];
1659
1660/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1661 We scale the results to [0..32]
1662*/
1663static const uint8_t* build_sweep_table()
1664{
1665 if (!gSweepTableReady)
1666 {
1667 const int N = 65;
1668 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001669
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 for (int i = 0; i < N; i++)
1671 {
1672 double arg = i / DENOM;
1673 double v = atan(arg);
1674 int iv = (int)round(v * DENOM * 2 / PI);
1675// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1676 printf("%d, ", iv);
1677 gSweepTable[i] = iv;
1678 }
1679 gSweepTableReady = true;
1680 }
1681 return gSweepTable;
1682}
1683#else
1684static const uint8_t gSweepTable[] = {
1685 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1686 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1687 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1688 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1689 32
1690};
1691static const uint8_t* build_sweep_table() { return gSweepTable; }
1692#endif
1693
1694// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1695// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1696// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1697
1698//unsigned div_64(int numer, int denom);
1699static unsigned div_64(int numer, int denom)
1700{
1701 SkASSERT(numer <= denom);
1702 SkASSERT(numer > 0);
1703 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001704
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705 int nbits = SkCLZ(numer);
1706 int dbits = SkCLZ(denom);
1707 int bits = 6 - nbits + dbits;
1708 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001709
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710 if (bits < 0) // detect underflow
1711 return 0;
1712
1713 denom <<= dbits - 1;
1714 numer <<= nbits - 1;
1715
1716 unsigned result = 0;
1717
1718 // do the first one
1719 if ((numer -= denom) >= 0)
1720 result = 1;
1721 else
1722 numer += denom;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001723
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724 // Now fall into our switch statement if there are more bits to compute
1725 if (bits > 0)
1726 {
1727 // make room for the rest of the answer bits
1728 result <<= bits;
1729 switch (bits) {
1730 case 6:
1731 if ((numer = (numer << 1) - denom) >= 0)
1732 result |= 32;
1733 else
1734 numer += denom;
1735 case 5:
1736 if ((numer = (numer << 1) - denom) >= 0)
1737 result |= 16;
1738 else
1739 numer += denom;
1740 case 4:
1741 if ((numer = (numer << 1) - denom) >= 0)
1742 result |= 8;
1743 else
1744 numer += denom;
1745 case 3:
1746 if ((numer = (numer << 1) - denom) >= 0)
1747 result |= 4;
1748 else
1749 numer += denom;
1750 case 2:
1751 if ((numer = (numer << 1) - denom) >= 0)
1752 result |= 2;
1753 else
1754 numer += denom;
1755 case 1:
1756 default: // not strictly need, but makes GCC make better ARM code
1757 if ((numer = (numer << 1) - denom) >= 0)
1758 result |= 1;
1759 else
1760 numer += denom;
1761 }
1762 }
1763 return result;
1764}
1765
1766// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1767static unsigned atan_0_90(SkFixed y, SkFixed x)
1768{
1769#ifdef SK_DEBUG
1770 {
1771 static bool gOnce;
1772 if (!gOnce)
1773 {
1774 gOnce = true;
1775 SkASSERT(div_64(55, 55) == 64);
1776 SkASSERT(div_64(128, 256) == 32);
1777 SkASSERT(div_64(2326528, 4685824) == 31);
1778 SkASSERT(div_64(753664, 5210112) == 9);
1779 SkASSERT(div_64(229376, 4882432) == 3);
1780 SkASSERT(div_64(2, 64) == 2);
1781 SkASSERT(div_64(1, 64) == 1);
1782 // test that we handle underflow correctly
1783 SkASSERT(div_64(12345, 0x54321234) == 0);
1784 }
1785 }
1786#endif
1787
1788 SkASSERT(y > 0 && x > 0);
1789 const uint8_t* table = build_sweep_table();
1790
1791 unsigned result;
1792 bool swap = (x < y);
1793 if (swap)
1794 {
1795 // first part of the atan(v) = PI/2 - atan(1/v) identity
1796 // since our div_64 and table want v <= 1, where v = y/x
1797 SkTSwap<SkFixed>(x, y);
1798 }
1799
1800 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001801
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802#ifdef SK_DEBUG
1803 {
1804 unsigned result2 = SkDivBits(y, x, 6);
1805 SkASSERT(result2 == result ||
1806 (result == 1 && result2 == 0));
1807 }
1808#endif
1809
1810 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1811 result = table[result];
1812
1813 if (swap)
1814 {
1815 // complete the atan(v) = PI/2 - atan(1/v) identity
1816 result = 64 - result;
1817 // pin to 63
1818 result -= result >> 6;
1819 }
1820
1821 SkASSERT(result <= 63);
1822 return result;
1823}
1824
1825// returns angle in a circle [0..2PI) -> [0..255]
1826static unsigned SkATan2_255(SkFixed y, SkFixed x)
1827{
1828 if (x == 0)
1829 {
1830 if (y == 0)
1831 return 0;
1832 return y < 0 ? 192 : 64;
1833 }
1834 if (y == 0)
1835 return x < 0 ? 128 : 0;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001836
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 /* Find the right quadrant for x,y
1838 Since atan_0_90 only handles the first quadrant, we rotate x,y
1839 appropriately before calling it, and then add the right amount
1840 to account for the real quadrant.
1841 quadrant 0 : add 0 | x > 0 && y > 0
1842 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1843 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1844 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001845
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 map x<0 to (1 << 6)
1847 map y<0 to (3 << 6)
1848 add = map_x ^ map_y
1849 */
1850 int xsign = x >> 31;
1851 int ysign = y >> 31;
1852 int add = ((-xsign) ^ (ysign & 3)) << 6;
1853
1854#ifdef SK_DEBUG
1855 if (0 == add)
1856 SkASSERT(x > 0 && y > 0);
1857 else if (64 == add)
1858 SkASSERT(x < 0 && y > 0);
1859 else if (128 == add)
1860 SkASSERT(x < 0 && y < 0);
1861 else if (192 == add)
1862 SkASSERT(x > 0 && y < 0);
1863 else
1864 SkASSERT(!"bad value for add");
1865#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00001866
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1868 where we need to rotate x,y by 90 or -90
1869 */
1870 x = (x ^ xsign) - xsign;
1871 y = (y ^ ysign) - ysign;
1872 if (add & 64) // quads 1 or 3 need to swap x,y
1873 SkTSwap<SkFixed>(x, y);
1874
1875 unsigned result = add + atan_0_90(y, x);
1876 SkASSERT(result < 256);
1877 return result;
1878}
1879
1880void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1881{
1882 SkMatrix::MapXYProc proc = fDstToIndexProc;
1883 const SkMatrix& matrix = fDstToIndex;
1884 const SkPMColor* cache = this->getCache32();
1885 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001886
reed@android.com8a1c16f2008-12-17 15:59:43 +00001887 if (fDstToIndexClass != kPerspective_MatrixClass)
1888 {
1889 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1890 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1891 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1892 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001893
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1895 {
1896 SkFixed storage[2];
1897 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1898 &storage[0], &storage[1]);
1899 dx = storage[0];
1900 dy = storage[1];
1901 }
1902 else
1903 {
1904 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1905 dx = SkScalarToFixed(matrix.getScaleX());
1906 dy = SkScalarToFixed(matrix.getSkewY());
1907 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001908
reed@android.com8a1c16f2008-12-17 15:59:43 +00001909 for (; count > 0; --count)
1910 {
1911 *dstC++ = cache[SkATan2_255(fy, fx)];
1912 fx += dx;
1913 fy += dy;
1914 }
1915 }
1916 else // perspective case
1917 {
1918 for (int stop = x + count; x < stop; x++)
1919 {
1920 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1921 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001922
reed@android.com8a1c16f2008-12-17 15:59:43 +00001923 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1924 SkScalarToFixed(srcPt.fX));
1925 *dstC++ = cache[index];
1926 }
1927 }
1928}
1929
1930void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1931{
1932 SkMatrix::MapXYProc proc = fDstToIndexProc;
1933 const SkMatrix& matrix = fDstToIndex;
1934 const uint16_t* cache = this->getCache16();
1935 int toggle = ((x ^ y) & 1) << kCache16Bits;
1936 SkPoint srcPt;
1937
1938 if (fDstToIndexClass != kPerspective_MatrixClass)
1939 {
1940 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1941 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1942 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1943 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001944
reed@android.com8a1c16f2008-12-17 15:59:43 +00001945 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1946 {
1947 SkFixed storage[2];
1948 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1949 &storage[0], &storage[1]);
1950 dx = storage[0];
1951 dy = storage[1];
1952 }
1953 else
1954 {
1955 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1956 dx = SkScalarToFixed(matrix.getScaleX());
1957 dy = SkScalarToFixed(matrix.getSkewY());
1958 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001959
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 for (; count > 0; --count)
1961 {
1962 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1963 *dstC++ = cache[toggle + index];
1964 toggle ^= (1 << kCache16Bits);
1965 fx += dx;
1966 fy += dy;
1967 }
1968 }
1969 else // perspective case
1970 {
1971 for (int stop = x + count; x < stop; x++)
1972 {
1973 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1974 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001975
reed@android.com8a1c16f2008-12-17 15:59:43 +00001976 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1977 SkScalarToFixed(srcPt.fX));
1978 index >>= (8 - kCache16Bits);
1979 *dstC++ = cache[toggle + index];
1980 toggle ^= (1 << kCache16Bits);
1981 }
1982 }
1983}
1984
1985///////////////////////////////////////////////////////////////////////////
1986///////////////////////////////////////////////////////////////////////////
1987
1988// assumes colors is SkColor* and pos is SkScalar*
1989#define EXPAND_1_COLOR(count) \
1990 SkColor tmp[2]; \
1991 do { \
1992 if (1 == count) { \
1993 tmp[0] = tmp[1] = colors[0]; \
1994 colors = tmp; \
1995 pos = NULL; \
1996 count = 2; \
1997 } \
1998 } while (0)
1999
2000SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
2001 const SkColor colors[], const SkScalar pos[], int colorCount,
2002 SkShader::TileMode mode, SkUnitMapper* mapper)
2003{
2004 if (NULL == pts || NULL == colors || colorCount < 1) {
2005 return NULL;
2006 }
2007 EXPAND_1_COLOR(colorCount);
2008
reed@android.comab840b82009-07-01 17:00:03 +00002009 return SkNEW_ARGS(Linear_Gradient,
2010 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002011}
2012
2013SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
2014 const SkColor colors[], const SkScalar pos[], int colorCount,
2015 SkShader::TileMode mode, SkUnitMapper* mapper)
2016{
2017 if (radius <= 0 || NULL == colors || colorCount < 1) {
2018 return NULL;
2019 }
2020 EXPAND_1_COLOR(colorCount);
2021
reed@android.comab840b82009-07-01 17:00:03 +00002022 return SkNEW_ARGS(Radial_Gradient,
2023 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024}
2025
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002026SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2027 SkScalar startRadius,
2028 const SkPoint& end,
2029 SkScalar endRadius,
2030 const SkColor colors[],
2031 const SkScalar pos[],
2032 int colorCount,
2033 SkShader::TileMode mode,
2034 SkUnitMapper* mapper)
2035{
2036 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2037 return NULL;
2038 }
2039 EXPAND_1_COLOR(colorCount);
2040
2041 return SkNEW_ARGS(Two_Point_Radial_Gradient,
2042 (start, startRadius, end, endRadius, colors, pos, colorCount, mode, mapper));
2043}
2044
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2046 const SkColor colors[],
2047 const SkScalar pos[],
2048 int count, SkUnitMapper* mapper)
2049{
2050 if (NULL == colors || count < 1) {
2051 return NULL;
2052 }
2053 EXPAND_1_COLOR(count);
2054
2055 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2056}
2057
2058static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2059 Linear_Gradient::CreateProc);
2060
2061static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2062 Radial_Gradient::CreateProc);
2063
2064static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2065 Sweep_Gradient::CreateProc);
2066