blob: 34522122abd04bab98fb8105e3ff9d6dadfef7e9 [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) {
1400 SkScalar invDiffL = SkScalarInvert(diffL);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001401 matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
reed@google.comdc731fd2010-12-23 15:19:47 +00001402 SkScalarMul(invDiffL, fDiff.fX));
1403 matrix->preConcat(fPtsToUnit);
1404 }
1405 if (xy) {
1406 xy[0] = fTileMode;
1407 xy[1] = kClamp_TileMode;
1408 }
1409 if (NULL != twoPointRadialParams) {
1410 twoPointRadialParams[0] = diffL;
1411 twoPointRadialParams[1] = fStartRadius;
1412 twoPointRadialParams[2] = fDiffRadius;
1413 }
1414 return kTwoPointRadial_BitmapType;
1415 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001416
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001417 virtual GradientType asAGradient(GradientInfo* info) const {
1418 if (info) {
1419 commonAsAGradient(info);
1420 info->fPoint[0] = fCenter1;
1421 info->fPoint[1] = fCenter2;
1422 info->fRadius[0] = fRadius1;
1423 info->fRadius[1] = fRadius2;
1424 }
1425 return kRadial2_GradientType;
1426 }
1427
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001428 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
1429 {
1430 SkASSERT(count > 0);
1431
1432 // Zero difference between radii: fill with transparent black.
1433 if (fDiffRadius == 0) {
1434 sk_bzero(dstC, count * sizeof(*dstC));
1435 return;
1436 }
1437 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
1438 TileProc proc = fTileProc;
1439 const SkPMColor* cache = this->getCache32();
1440 SkFixed diffx = SkScalarToFixed(fDiff.fX);
1441 SkFixed diffy = SkScalarToFixed(fDiff.fY);
1442 SkFixed foura = SkScalarToFixed(SkScalarMul(fA, 4));
1443 SkFixed startRadius = SkScalarToFixed(fStartRadius);
1444 SkFixed sr2D2 = SkScalarToFixed(fSr2D2);
1445 SkFixed oneOverTwoA = SkScalarToFixed(fOneOverTwoA);
1446 bool posRoot = fDiffRadius < 0;
1447 if (fDstToIndexClass != kPerspective_MatrixClass)
1448 {
1449 SkPoint srcPt;
reed@google.comdc731fd2010-12-23 15:19:47 +00001450 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
1451 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001452 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1453 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
1454
1455 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1456 {
1457 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &dx, &dy);
1458 }
1459 else
1460 {
1461 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1462 dx = SkScalarToFixed(fDstToIndex.getScaleX());
1463 dy = SkScalarToFixed(fDstToIndex.getSkewY());
1464 }
1465 SkFixed b = (SkFixedMul(diffx, fx) +
1466 SkFixedMul(diffy, fy) - startRadius) << 1;
1467 SkFixed db = (SkFixedMul(diffx, dx) +
1468 SkFixedMul(diffy, dy)) << 1;
1469 if (proc == clamp_tileproc)
1470 {
1471 for (; count > 0; --count) {
1472 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1473 SkFixed index = SkClampMax(t, 0xFFFF);
1474 SkASSERT(index <= 0xFFFF);
1475 *dstC++ = cache[index >> (16 - kCache32Bits)];
1476 fx += dx;
1477 fy += dy;
1478 b += db;
1479 }
1480 }
1481 else if (proc == mirror_tileproc)
1482 {
1483 for (; count > 0; --count) {
1484 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1485 SkFixed index = mirror_tileproc(t);
1486 SkASSERT(index <= 0xFFFF);
1487 *dstC++ = cache[index >> (16 - kCache32Bits)];
1488 fx += dx;
1489 fy += dy;
1490 b += db;
1491 }
1492 }
1493 else
1494 {
1495 SkASSERT(proc == repeat_tileproc);
1496 for (; count > 0; --count) {
1497 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1498 SkFixed index = repeat_tileproc(t);
1499 SkASSERT(index <= 0xFFFF);
1500 *dstC++ = cache[index >> (16 - kCache32Bits)];
1501 fx += dx;
1502 fy += dy;
1503 b += db;
1504 }
1505 }
1506 }
1507 else // perspective case
1508 {
reed@android.com6c59a172009-09-22 20:24:05 +00001509 SkScalar dstX = SkIntToScalar(x);
1510 SkScalar dstY = SkIntToScalar(y);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001511 for (; count > 0; --count) {
1512 SkPoint srcPt;
reed@android.com6c59a172009-09-22 20:24:05 +00001513 dstProc(fDstToIndex, dstX, dstY, &srcPt);
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001514 SkFixed fx = SkScalarToFixed(srcPt.fX);
1515 SkFixed fy = SkScalarToFixed(srcPt.fY);
1516 SkFixed b = (SkFixedMul(diffx, fx) +
1517 SkFixedMul(diffy, fy) - startRadius) << 1;
1518 SkFixed t = two_point_radial(b, fx, fy, sr2D2, foura, oneOverTwoA, posRoot);
1519 SkFixed index = proc(t);
1520 SkASSERT(index <= 0xFFFF);
1521 *dstC++ = cache[index >> (16 - kCache32Bits)];
reed@android.com6c59a172009-09-22 20:24:05 +00001522 dstX += SK_Scalar1;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001523 }
1524 }
1525 }
1526
reed@android.com6c59a172009-09-22 20:24:05 +00001527 virtual bool setContext(const SkBitmap& device,
1528 const SkPaint& paint,
1529 const SkMatrix& matrix) {
1530 if (!this->INHERITED::setContext(device, paint, matrix)) {
1531 return false;
1532 }
1533
1534 // we don't have a span16 proc
1535 fFlags &= ~kHasSpan16_Flag;
1536 return true;
1537 }
1538
reed@google.com55b8e8c2011-01-13 16:22:35 +00001539 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001540 return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer));
1541 }
1542
reed@android.combcfc7332009-11-10 16:19:39 +00001543 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1544 this->INHERITED::flatten(buffer);
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001545 buffer.writeScalar(fCenter1.fX);
1546 buffer.writeScalar(fCenter1.fY);
1547 buffer.writeScalar(fCenter2.fX);
1548 buffer.writeScalar(fCenter2.fY);
1549 buffer.writeScalar(fRadius1);
1550 buffer.writeScalar(fRadius2);
reed@android.combcfc7332009-11-10 16:19:39 +00001551 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001552
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001553protected:
reed@android.combcfc7332009-11-10 16:19:39 +00001554 Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001555 : Gradient_Shader(buffer),
1556 fCenter1(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1557 fCenter2(SkPoint::Make(buffer.readScalar(), buffer.readScalar())),
1558 fRadius1(buffer.readScalar()),
1559 fRadius2(buffer.readScalar()) {
1560 init();
reed@android.combcfc7332009-11-10 16:19:39 +00001561 };
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001562 virtual Factory getFactory() { return CreateProc; }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001563
1564private:
1565 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001566 const SkPoint fCenter1;
1567 const SkPoint fCenter2;
1568 const SkScalar fRadius1;
1569 const SkScalar fRadius2;
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001570 SkPoint fDiff;
1571 SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001572
1573 void init() {
1574 fDiff = fCenter1 - fCenter2;
1575 fDiffRadius = fRadius2 - fRadius1;
1576 SkScalar inv = SkScalarInvert(fDiffRadius);
1577 fDiff.fX = SkScalarMul(fDiff.fX, inv);
1578 fDiff.fY = SkScalarMul(fDiff.fY, inv);
1579 fStartRadius = SkScalarMul(fRadius1, inv);
1580 fSr2D2 = SkScalarSquare(fStartRadius);
1581 fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
1582 fOneOverTwoA = SkScalarInvert(fA * 2);
1583
1584 fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
1585 fPtsToUnit.postScale(inv, inv);
1586 }
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00001587};
1588
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589///////////////////////////////////////////////////////////////////////////////
1590
1591class Sweep_Gradient : public Gradient_Shader {
1592public:
1593 Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
1594 const SkScalar pos[], int count, SkUnitMapper* mapper)
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001595 : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
1596 fCenter(SkPoint::Make(cx, cy))
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597 {
1598 fPtsToUnit.setTranslate(-cx, -cy);
1599 }
1600 virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
1601 virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001602
1603 virtual BitmapType asABitmap(SkBitmap* bitmap,
1604 SkMatrix* matrix,
reed@google.comdc731fd2010-12-23 15:19:47 +00001605 TileMode* xy,
reed@google.com7c2f27d2011-03-07 19:29:00 +00001606 SkScalar* twoPointRadialParams) const {
reed@google.comdc731fd2010-12-23 15:19:47 +00001607 if (bitmap) {
1608 this->commonAsABitmap(bitmap);
1609 }
1610 if (matrix) {
1611 *matrix = fPtsToUnit;
1612 }
1613 if (xy) {
1614 xy[0] = fTileMode;
1615 xy[1] = kClamp_TileMode;
1616 }
1617 return kSweep_BitmapType;
1618 }
1619
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001620 virtual GradientType asAGradient(GradientInfo* info) const {
1621 if (info) {
1622 commonAsAGradient(info);
1623 info->fPoint[0] = fCenter;
1624 }
1625 return kSweep_GradientType;
1626 }
1627
reed@android.com8a1c16f2008-12-17 15:59:43 +00001628 static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
1629 return SkNEW_ARGS(Sweep_Gradient, (buffer));
1630 }
1631
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001632 virtual void flatten(SkFlattenableWriteBuffer& buffer) {
1633 this->INHERITED::flatten(buffer);
1634 buffer.writeScalar(fCenter.fX);
1635 buffer.writeScalar(fCenter.fY);
1636 }
1637
reed@android.com8a1c16f2008-12-17 15:59:43 +00001638protected:
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001639 Sweep_Gradient(SkFlattenableReadBuffer& buffer)
1640 : Gradient_Shader(buffer),
1641 fCenter(SkPoint::Make(buffer.readScalar(), buffer.readScalar())) {
1642 }
1643
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 virtual Factory getFactory() { return CreateProc; }
1645
1646private:
1647 typedef Gradient_Shader INHERITED;
vandebo@chromium.orgd3ae7792011-02-24 00:21:06 +00001648 const SkPoint fCenter;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649};
1650
1651#ifdef COMPUTE_SWEEP_TABLE
1652#define PI 3.14159265
1653static bool gSweepTableReady;
1654static uint8_t gSweepTable[65];
1655
1656/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
1657 We scale the results to [0..32]
1658*/
1659static const uint8_t* build_sweep_table()
1660{
1661 if (!gSweepTableReady)
1662 {
1663 const int N = 65;
1664 const double DENOM = N - 1;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001665
reed@android.com8a1c16f2008-12-17 15:59:43 +00001666 for (int i = 0; i < N; i++)
1667 {
1668 double arg = i / DENOM;
1669 double v = atan(arg);
1670 int iv = (int)round(v * DENOM * 2 / PI);
1671// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
1672 printf("%d, ", iv);
1673 gSweepTable[i] = iv;
1674 }
1675 gSweepTableReady = true;
1676 }
1677 return gSweepTable;
1678}
1679#else
1680static const uint8_t gSweepTable[] = {
1681 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
1682 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
1683 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
1684 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
1685 32
1686};
1687static const uint8_t* build_sweep_table() { return gSweepTable; }
1688#endif
1689
1690// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
1691// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
1692// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
1693
1694//unsigned div_64(int numer, int denom);
1695static unsigned div_64(int numer, int denom)
1696{
1697 SkASSERT(numer <= denom);
1698 SkASSERT(numer > 0);
1699 SkASSERT(denom > 0);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001700
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 int nbits = SkCLZ(numer);
1702 int dbits = SkCLZ(denom);
1703 int bits = 6 - nbits + dbits;
1704 SkASSERT(bits <= 6);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001705
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 if (bits < 0) // detect underflow
1707 return 0;
1708
1709 denom <<= dbits - 1;
1710 numer <<= nbits - 1;
1711
1712 unsigned result = 0;
1713
1714 // do the first one
1715 if ((numer -= denom) >= 0)
1716 result = 1;
1717 else
1718 numer += denom;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001719
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720 // Now fall into our switch statement if there are more bits to compute
1721 if (bits > 0)
1722 {
1723 // make room for the rest of the answer bits
1724 result <<= bits;
1725 switch (bits) {
1726 case 6:
1727 if ((numer = (numer << 1) - denom) >= 0)
1728 result |= 32;
1729 else
1730 numer += denom;
1731 case 5:
1732 if ((numer = (numer << 1) - denom) >= 0)
1733 result |= 16;
1734 else
1735 numer += denom;
1736 case 4:
1737 if ((numer = (numer << 1) - denom) >= 0)
1738 result |= 8;
1739 else
1740 numer += denom;
1741 case 3:
1742 if ((numer = (numer << 1) - denom) >= 0)
1743 result |= 4;
1744 else
1745 numer += denom;
1746 case 2:
1747 if ((numer = (numer << 1) - denom) >= 0)
1748 result |= 2;
1749 else
1750 numer += denom;
1751 case 1:
1752 default: // not strictly need, but makes GCC make better ARM code
1753 if ((numer = (numer << 1) - denom) >= 0)
1754 result |= 1;
1755 else
1756 numer += denom;
1757 }
1758 }
1759 return result;
1760}
1761
1762// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
1763static unsigned atan_0_90(SkFixed y, SkFixed x)
1764{
1765#ifdef SK_DEBUG
1766 {
1767 static bool gOnce;
1768 if (!gOnce)
1769 {
1770 gOnce = true;
1771 SkASSERT(div_64(55, 55) == 64);
1772 SkASSERT(div_64(128, 256) == 32);
1773 SkASSERT(div_64(2326528, 4685824) == 31);
1774 SkASSERT(div_64(753664, 5210112) == 9);
1775 SkASSERT(div_64(229376, 4882432) == 3);
1776 SkASSERT(div_64(2, 64) == 2);
1777 SkASSERT(div_64(1, 64) == 1);
1778 // test that we handle underflow correctly
1779 SkASSERT(div_64(12345, 0x54321234) == 0);
1780 }
1781 }
1782#endif
1783
1784 SkASSERT(y > 0 && x > 0);
1785 const uint8_t* table = build_sweep_table();
1786
1787 unsigned result;
1788 bool swap = (x < y);
1789 if (swap)
1790 {
1791 // first part of the atan(v) = PI/2 - atan(1/v) identity
1792 // since our div_64 and table want v <= 1, where v = y/x
1793 SkTSwap<SkFixed>(x, y);
1794 }
1795
1796 result = div_64(y, x);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001797
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798#ifdef SK_DEBUG
1799 {
1800 unsigned result2 = SkDivBits(y, x, 6);
1801 SkASSERT(result2 == result ||
1802 (result == 1 && result2 == 0));
1803 }
1804#endif
1805
1806 SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
1807 result = table[result];
1808
1809 if (swap)
1810 {
1811 // complete the atan(v) = PI/2 - atan(1/v) identity
1812 result = 64 - result;
1813 // pin to 63
1814 result -= result >> 6;
1815 }
1816
1817 SkASSERT(result <= 63);
1818 return result;
1819}
1820
1821// returns angle in a circle [0..2PI) -> [0..255]
1822static unsigned SkATan2_255(SkFixed y, SkFixed x)
1823{
1824 if (x == 0)
1825 {
1826 if (y == 0)
1827 return 0;
1828 return y < 0 ? 192 : 64;
1829 }
1830 if (y == 0)
1831 return x < 0 ? 128 : 0;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001832
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 /* Find the right quadrant for x,y
1834 Since atan_0_90 only handles the first quadrant, we rotate x,y
1835 appropriately before calling it, and then add the right amount
1836 to account for the real quadrant.
1837 quadrant 0 : add 0 | x > 0 && y > 0
1838 quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
1839 quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
1840 quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
reed@google.com55b8e8c2011-01-13 16:22:35 +00001841
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842 map x<0 to (1 << 6)
1843 map y<0 to (3 << 6)
1844 add = map_x ^ map_y
1845 */
1846 int xsign = x >> 31;
1847 int ysign = y >> 31;
1848 int add = ((-xsign) ^ (ysign & 3)) << 6;
1849
1850#ifdef SK_DEBUG
1851 if (0 == add)
1852 SkASSERT(x > 0 && y > 0);
1853 else if (64 == add)
1854 SkASSERT(x < 0 && y > 0);
1855 else if (128 == add)
1856 SkASSERT(x < 0 && y < 0);
1857 else if (192 == add)
1858 SkASSERT(x > 0 && y < 0);
1859 else
1860 SkASSERT(!"bad value for add");
1861#endif
reed@google.com55b8e8c2011-01-13 16:22:35 +00001862
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863 /* This ^ trick makes x, y positive, and the swap<> handles quadrants
1864 where we need to rotate x,y by 90 or -90
1865 */
1866 x = (x ^ xsign) - xsign;
1867 y = (y ^ ysign) - ysign;
1868 if (add & 64) // quads 1 or 3 need to swap x,y
1869 SkTSwap<SkFixed>(x, y);
1870
1871 unsigned result = add + atan_0_90(y, x);
1872 SkASSERT(result < 256);
1873 return result;
1874}
1875
1876void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
1877{
1878 SkMatrix::MapXYProc proc = fDstToIndexProc;
1879 const SkMatrix& matrix = fDstToIndex;
1880 const SkPMColor* cache = this->getCache32();
1881 SkPoint srcPt;
reed@google.com55b8e8c2011-01-13 16:22:35 +00001882
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883 if (fDstToIndexClass != kPerspective_MatrixClass)
1884 {
1885 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1886 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1887 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1888 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001889
reed@android.com8a1c16f2008-12-17 15:59:43 +00001890 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1891 {
1892 SkFixed storage[2];
1893 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1894 &storage[0], &storage[1]);
1895 dx = storage[0];
1896 dy = storage[1];
1897 }
1898 else
1899 {
1900 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1901 dx = SkScalarToFixed(matrix.getScaleX());
1902 dy = SkScalarToFixed(matrix.getSkewY());
1903 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001904
reed@android.com8a1c16f2008-12-17 15:59:43 +00001905 for (; count > 0; --count)
1906 {
1907 *dstC++ = cache[SkATan2_255(fy, fx)];
1908 fx += dx;
1909 fy += dy;
1910 }
1911 }
1912 else // perspective case
1913 {
1914 for (int stop = x + count; x < stop; x++)
1915 {
1916 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1917 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001918
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1920 SkScalarToFixed(srcPt.fX));
1921 *dstC++ = cache[index];
1922 }
1923 }
1924}
1925
1926void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
1927{
1928 SkMatrix::MapXYProc proc = fDstToIndexProc;
1929 const SkMatrix& matrix = fDstToIndex;
1930 const uint16_t* cache = this->getCache16();
1931 int toggle = ((x ^ y) & 1) << kCache16Bits;
1932 SkPoint srcPt;
1933
1934 if (fDstToIndexClass != kPerspective_MatrixClass)
1935 {
1936 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1937 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
1938 SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
1939 SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001940
reed@android.com8a1c16f2008-12-17 15:59:43 +00001941 if (fDstToIndexClass == kFixedStepInX_MatrixClass)
1942 {
1943 SkFixed storage[2];
1944 (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
1945 &storage[0], &storage[1]);
1946 dx = storage[0];
1947 dy = storage[1];
1948 }
1949 else
1950 {
1951 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
1952 dx = SkScalarToFixed(matrix.getScaleX());
1953 dy = SkScalarToFixed(matrix.getSkewY());
1954 }
reed@google.com55b8e8c2011-01-13 16:22:35 +00001955
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956 for (; count > 0; --count)
1957 {
1958 int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
1959 *dstC++ = cache[toggle + index];
1960 toggle ^= (1 << kCache16Bits);
1961 fx += dx;
1962 fy += dy;
1963 }
1964 }
1965 else // perspective case
1966 {
1967 for (int stop = x + count; x < stop; x++)
1968 {
1969 proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
1970 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
reed@google.com55b8e8c2011-01-13 16:22:35 +00001971
reed@android.com8a1c16f2008-12-17 15:59:43 +00001972 int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
1973 SkScalarToFixed(srcPt.fX));
1974 index >>= (8 - kCache16Bits);
1975 *dstC++ = cache[toggle + index];
1976 toggle ^= (1 << kCache16Bits);
1977 }
1978 }
1979}
1980
1981///////////////////////////////////////////////////////////////////////////
1982///////////////////////////////////////////////////////////////////////////
1983
1984// assumes colors is SkColor* and pos is SkScalar*
1985#define EXPAND_1_COLOR(count) \
1986 SkColor tmp[2]; \
1987 do { \
1988 if (1 == count) { \
1989 tmp[0] = tmp[1] = colors[0]; \
1990 colors = tmp; \
1991 pos = NULL; \
1992 count = 2; \
1993 } \
1994 } while (0)
1995
1996SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
1997 const SkColor colors[], const SkScalar pos[], int colorCount,
1998 SkShader::TileMode mode, SkUnitMapper* mapper)
1999{
2000 if (NULL == pts || NULL == colors || colorCount < 1) {
2001 return NULL;
2002 }
2003 EXPAND_1_COLOR(colorCount);
2004
reed@android.comab840b82009-07-01 17:00:03 +00002005 return SkNEW_ARGS(Linear_Gradient,
2006 (pts, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007}
2008
2009SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
2010 const SkColor colors[], const SkScalar pos[], int colorCount,
2011 SkShader::TileMode mode, SkUnitMapper* mapper)
2012{
2013 if (radius <= 0 || NULL == colors || colorCount < 1) {
2014 return NULL;
2015 }
2016 EXPAND_1_COLOR(colorCount);
2017
reed@android.comab840b82009-07-01 17:00:03 +00002018 return SkNEW_ARGS(Radial_Gradient,
2019 (center, radius, colors, pos, colorCount, mode, mapper));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020}
2021
senorblanco@chromium.org7ef071f2009-09-22 17:25:29 +00002022SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
2023 SkScalar startRadius,
2024 const SkPoint& end,
2025 SkScalar endRadius,
2026 const SkColor colors[],
2027 const SkScalar pos[],
2028 int colorCount,
2029 SkShader::TileMode mode,
2030 SkUnitMapper* mapper)
2031{
2032 if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
2033 return NULL;
2034 }
2035 EXPAND_1_COLOR(colorCount);
2036
2037 return SkNEW_ARGS(Two_Point_Radial_Gradient,
2038 (start, startRadius, end, endRadius, colors, pos, colorCount, mode, mapper));
2039}
2040
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
2042 const SkColor colors[],
2043 const SkScalar pos[],
2044 int count, SkUnitMapper* mapper)
2045{
2046 if (NULL == colors || count < 1) {
2047 return NULL;
2048 }
2049 EXPAND_1_COLOR(count);
2050
2051 return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
2052}
2053
2054static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
2055 Linear_Gradient::CreateProc);
2056
2057static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
2058 Radial_Gradient::CreateProc);
2059
2060static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
2061 Sweep_Gradient::CreateProc);
2062