blob: 860e902dd239373ee2f655e0fa1dd0c2108807dd [file] [log] [blame]
/* libs/graphics/effects/SkGradientShader.cpp
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#include "SkGradientShader.h"
#include "SkColorPriv.h"
#include "SkUnitMapper.h"
#include "SkUtils.h"
///////////////////////////////////////////////////////////////////////////
typedef SkFixed (*TileProc)(SkFixed);
static SkFixed clamp_tileproc(SkFixed x) {
return SkClampMax(x, 0xFFFF);
}
static SkFixed repeat_tileproc(SkFixed x) {
return x & 0xFFFF;
}
static inline SkFixed mirror_tileproc(SkFixed x) {
int s = x << 15 >> 31;
return (x ^ s) & 0xFFFF;
}
static const TileProc gTileProcs[] = {
clamp_tileproc,
repeat_tileproc,
mirror_tileproc
};
//////////////////////////////////////////////////////////////////////////////
static inline int repeat_6bits(int x) {
return x & 63;
}
static inline int mirror_6bits(int x) {
#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
if (x & 64)
x = ~x;
return x & 63;
#else
int s = x << 25 >> 31;
return (x ^ s) & 63;
#endif
}
static inline int repeat_8bits(int x) {
return x & 0xFF;
}
static inline int mirror_8bits(int x) {
#ifdef SK_CPU_HAS_CONDITIONAL_INSTR
if (x & 256) {
x = ~x;
}
return x & 255;
#else
int s = x << 23 >> 31;
return (x ^ s) & 0xFF;
#endif
}
//////////////////////////////////////////////////////////////////////////////
class Gradient_Shader : public SkShader {
public:
Gradient_Shader(const SkColor colors[], const SkScalar pos[],
int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper);
virtual ~Gradient_Shader();
// overrides
virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
virtual uint32_t getFlags() { return fFlags; }
protected:
Gradient_Shader(SkFlattenableReadBuffer& );
SkUnitMapper* fMapper;
SkMatrix fPtsToUnit; // set by subclass
SkMatrix fDstToIndex;
SkMatrix::MapXYProc fDstToIndexProc;
TileMode fTileMode;
TileProc fTileProc;
int fColorCount;
uint8_t fDstToIndexClass;
uint8_t fFlags;
struct Rec {
SkFixed fPos; // 0...1
uint32_t fScale; // (1 << 24) / range
};
Rec* fRecs;
enum {
kCache16Bits = 6, // seems like enough for visual accuracy
kCache16Count = 1 << kCache16Bits,
kCache32Bits = 8, // pretty much should always be 8
kCache32Count = 1 << kCache32Bits
};
virtual void flatten(SkFlattenableWriteBuffer& );
const uint16_t* getCache16();
const SkPMColor* getCache32();
// called when we kill our cached colors (to be rebuilt later on demand)
virtual void onCacheReset() = 0;
private:
enum {
kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
};
SkColor fStorage[(kStorageSize + 3) >> 2];
SkColor* fOrigColors;
uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
SkPMColor* fCache32Storage; // storage for fCache32, allocated on demand
unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
typedef SkShader INHERITED;
};
static inline unsigned scalarToU16(SkScalar x) {
SkASSERT(x >= 0 && x <= SK_Scalar1);
#ifdef SK_SCALAR_IS_FLOAT
return (unsigned)(x * 0xFFFF);
#else
return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower
#endif
}
Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[],
int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) {
SkASSERT(colorCount > 1);
fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
fMapper = mapper;
mapper->safeRef();
SkASSERT((unsigned)mode < SkShader::kTileModeCount);
SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
fTileMode = mode;
fTileProc = gTileProcs[mode];
fCache16 = fCache16Storage = NULL;
fCache32 = fCache32Storage = NULL;
/* Note: we let the caller skip the first and/or last position.
i.e. pos[0] = 0.3, pos[1] = 0.7
In these cases, we insert dummy entries to ensure that the final data
will be bracketed by [0, 1].
i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
Thus colorCount (the caller's value, and fColorCount (our value) may
differ by up to 2. In the above example:
colorCount = 2
fColorCount = 4
*/
fColorCount = colorCount;
// check if we need to add in dummy start and/or end position/colors
bool dummyFirst = false;
bool dummyLast = false;
if (pos) {
dummyFirst = pos[0] != 0;
dummyLast = pos[colorCount - 1] != SK_Scalar1;
fColorCount += dummyFirst + dummyLast;
}
if (fColorCount > kColorStorageCount) {
size_t size = sizeof(SkColor) + sizeof(Rec);
fOrigColors = reinterpret_cast<SkColor*>(
sk_malloc_throw(size * fColorCount));
}
else {
fOrigColors = fStorage;
}
// Now copy over the colors, adding the dummies as needed
{
SkColor* origColors = fOrigColors;
if (dummyFirst) {
*origColors++ = colors[0];
}
memcpy(origColors, colors, colorCount * sizeof(SkColor));
if (dummyLast) {
origColors += colorCount;
*origColors = colors[colorCount - 1];
}
}
fRecs = (Rec*)(fOrigColors + fColorCount);
if (fColorCount > 2) {
Rec* recs = fRecs;
recs->fPos = 0;
// recs->fScale = 0; // unused;
recs += 1;
if (pos) {
/* We need to convert the user's array of relative positions into
fixed-point positions and scale factors. We need these results
to be strictly monotonic (no two values equal or out of order).
Hence this complex loop that just jams a zero for the scale
value if it sees a segment out of order, and it assures that
we start at 0 and end at 1.0
*/
SkFixed prev = 0;
int startIndex = dummyFirst ? 0 : 1;
int count = colorCount + dummyLast;
for (int i = startIndex; i < count; i++) {
// force the last value to be 1.0
SkFixed curr;
if (i == colorCount) { // we're really at the dummyLast
curr = SK_Fixed1;
} else {
curr = SkScalarToFixed(pos[i]);
}
// pin curr withing range
if (curr < 0) {
curr = 0;
} else if (curr > SK_Fixed1) {
curr = SK_Fixed1;
}
recs->fPos = curr;
if (curr > prev) {
recs->fScale = (1 << 24) / (curr - prev);
} else {
recs->fScale = 0; // ignore this segment
}
// get ready for the next value
prev = curr;
recs += 1;
}
} else { // assume even distribution
SkFixed dp = SK_Fixed1 / (colorCount - 1);
SkFixed p = dp;
SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp
for (int i = 1; i < colorCount; i++) {
recs->fPos = p;
recs->fScale = scale;
recs += 1;
p += dp;
}
}
}
fFlags = 0;
}
Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) :
INHERITED(buffer) {
fCacheAlpha = 256;
fMapper = static_cast<SkUnitMapper*>(buffer.readFlattenable());
fCache16 = fCache16Storage = NULL;
fCache32 = fCache32Storage = NULL;
int colorCount = fColorCount = buffer.readU32();
if (colorCount > kColorStorageCount) {
size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
} else {
fOrigColors = fStorage;
}
buffer.read(fOrigColors, colorCount * sizeof(SkColor));
fTileMode = (TileMode)buffer.readU8();
fTileProc = gTileProcs[fTileMode];
fRecs = (Rec*)(fOrigColors + colorCount);
if (colorCount > 2) {
Rec* recs = fRecs;
recs[0].fPos = 0;
for (int i = 1; i < colorCount; i++) {
recs[i].fPos = buffer.readS32();
recs[i].fScale = buffer.readU32();
}
}
buffer.read(&fPtsToUnit, sizeof(SkMatrix));
fFlags = 0;
}
Gradient_Shader::~Gradient_Shader() {
if (fCache16Storage) {
sk_free(fCache16Storage);
}
if (fCache32Storage) {
sk_free(fCache32Storage);
}
if (fOrigColors != fStorage) {
sk_free(fOrigColors);
}
fMapper->safeUnref();
}
void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) {
this->INHERITED::flatten(buffer);
buffer.writeFlattenable(fMapper);
buffer.write32(fColorCount);
buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor));
buffer.write8(fTileMode);
if (fColorCount > 2) {
Rec* recs = fRecs;
for (int i = 1; i < fColorCount; i++) {
buffer.write32(recs[i].fPos);
buffer.write32(recs[i].fScale);
}
}
buffer.writeMul4(&fPtsToUnit, sizeof(SkMatrix));
}
bool Gradient_Shader::setContext(const SkBitmap& device,
const SkPaint& paint,
const SkMatrix& matrix) {
if (!this->INHERITED::setContext(device, paint, matrix)) {
return false;
}
const SkMatrix& inverse = this->getTotalInverse();
if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
return false;
}
fDstToIndexProc = fDstToIndex.getMapXYProc();
fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
// now convert our colors in to PMColors
unsigned paintAlpha = this->getPaintAlpha();
unsigned colorAlpha = 0xFF;
// FIXME: record colorAlpha in constructor, since this is not affected
// by setContext()
for (int i = 0; i < fColorCount; i++) {
SkColor src = fOrigColors[i];
unsigned sa = SkColorGetA(src);
colorAlpha &= sa;
}
fFlags = this->INHERITED::getFlags();
if ((colorAlpha & paintAlpha) == 0xFF) {
fFlags |= kOpaqueAlpha_Flag;
}
// we can do span16 as long as our individual colors are opaque,
// regardless of the paint's alpha
if (0xFF == colorAlpha) {
fFlags |= kHasSpan16_Flag;
}
// if the new alpha differs from the previous time we were called, inval our cache
// this will trigger the cache to be rebuilt.
// we don't care about the first time, since the cache ptrs will already be NULL
if (fCacheAlpha != paintAlpha) {
fCache16 = NULL; // inval the cache
fCache32 = NULL; // inval the cache
fCacheAlpha = paintAlpha; // record the new alpha
// inform our subclasses
this->onCacheReset();
}
return true;
}
static inline int blend8(int a, int b, int scale) {
SkASSERT(a == SkToU8(a));
SkASSERT(b == SkToU8(b));
SkASSERT(scale >= 0 && scale <= 256);
return a + ((b - a) * scale >> 8);
}
static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1,
int blend) {
#if 0
int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend);
int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend);
int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend);
int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend);
return SkPackARGB32(a, r, g, b);
#else
int otherBlend = 256 - blend;
#if 0
U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF;
U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00;
SkASSERT((t0 & t1) == 0);
return t0 | t1;
#else
return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) |
((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00);
#endif
#endif
}
#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
/** We take the original colors, not our premultiplied PMColors, since we can
build a 16bit table as long as the original colors are opaque, even if the
paint specifies a non-opaque alpha.
*/
static void build_16bit_cache(uint16_t cache[], SkColor c0, SkColor c1,
int count) {
SkASSERT(count > 1);
SkASSERT(SkColorGetA(c0) == 0xFF);
SkASSERT(SkColorGetA(c1) == 0xFF);
SkFixed r = SkColorGetR(c0);
SkFixed g = SkColorGetG(c0);
SkFixed b = SkColorGetB(c0);
SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
r = SkIntToFixed(r) + 0x8000;
g = SkIntToFixed(g) + 0x8000;
b = SkIntToFixed(b) + 0x8000;
do {
unsigned rr = r >> 16;
unsigned gg = g >> 16;
unsigned bb = b >> 16;
cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
cache[64] = SkDitherPack888ToRGB16(rr, gg, bb);
cache += 1;
r += dr;
g += dg;
b += db;
} while (--count != 0);
}
static void build_32bit_cache(SkPMColor cache[], SkColor c0, SkColor c1,
int count, U8CPU paintAlpha) {
SkASSERT(count > 1);
// need to apply paintAlpha to our two endpoints
SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
SkFixed da;
{
int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
da = SkIntToFixed(tmp - a) / (count - 1);
}
SkFixed r = SkColorGetR(c0);
SkFixed g = SkColorGetG(c0);
SkFixed b = SkColorGetB(c0);
SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
a = SkIntToFixed(a) + 0x8000;
r = SkIntToFixed(r) + 0x8000;
g = SkIntToFixed(g) + 0x8000;
b = SkIntToFixed(b) + 0x8000;
do {
*cache++ = SkPreMultiplyARGB(a >> 16, r >> 16, g >> 16, b >> 16);
a += da;
r += dr;
g += dg;
b += db;
} while (--count != 0);
}
static inline int SkFixedToFFFF(SkFixed x) {
SkASSERT((unsigned)x <= SK_Fixed1);
return x - (x >> 16);
}
static inline U16CPU dot6to16(unsigned x) {
SkASSERT(x < 64);
return (x << 10) | (x << 4) | (x >> 2);
}
const uint16_t* Gradient_Shader::getCache16() {
if (fCache16 == NULL) {
if (fCache16Storage == NULL) { // set the storage and our working ptr
fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count * 2);
}
fCache16 = fCache16Storage;
if (fColorCount == 2) {
build_16bit_cache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count);
} else {
Rec* rec = fRecs;
int prevIndex = 0;
for (int i = 1; i < fColorCount; i++) {
int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache16Bits);
SkASSERT(nextIndex < kCache16Count);
if (nextIndex > prevIndex)
build_16bit_cache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
prevIndex = nextIndex;
}
SkASSERT(prevIndex == kCache16Count - 1);
}
if (fMapper) {
fCache16Storage = (uint16_t*)sk_malloc_throw(sizeof(uint16_t) * kCache16Count * 2);
uint16_t* linear = fCache16; // just computed linear data
uint16_t* mapped = fCache16Storage; // storage for mapped data
SkUnitMapper* map = fMapper;
for (int i = 0; i < 64; i++) {
int index = map->mapUnit16(dot6to16(i)) >> 10;
mapped[i] = linear[index];
mapped[i + 64] = linear[index + 64];
}
sk_free(fCache16);
fCache16 = fCache16Storage;
}
}
return fCache16;
}
const SkPMColor* Gradient_Shader::getCache32() {
if (fCache32 == NULL) {
if (fCache32Storage == NULL) // set the storage and our working ptr
fCache32Storage = (SkPMColor*)sk_malloc_throw(sizeof(SkPMColor) * kCache32Count);
fCache32 = fCache32Storage;
if (fColorCount == 2) {
build_32bit_cache(fCache32, fOrigColors[0], fOrigColors[1],
kCache32Count, fCacheAlpha);
} else {
Rec* rec = fRecs;
int prevIndex = 0;
for (int i = 1; i < fColorCount; i++) {
int nextIndex = SkFixedToFFFF(rec[i].fPos) >> (16 - kCache32Bits);
SkASSERT(nextIndex < kCache32Count);
if (nextIndex > prevIndex)
build_32bit_cache(fCache32 + prevIndex, fOrigColors[i-1],
fOrigColors[i],
nextIndex - prevIndex + 1, fCacheAlpha);
prevIndex = nextIndex;
}
SkASSERT(prevIndex == kCache32Count - 1);
}
if (fMapper) {
fCache32Storage = (SkPMColor*)sk_malloc_throw(sizeof(SkPMColor) * kCache32Count);
SkPMColor* linear = fCache32; // just computed linear data
SkPMColor* mapped = fCache32Storage; // storage for mapped data
SkUnitMapper* map = fMapper;
for (int i = 0; i < 256; i++) {
mapped[i] = linear[map->mapUnit16((i << 8) | i) >> 8];
}
sk_free(fCache32);
fCache32 = fCache32Storage;
}
}
return fCache32;
}
///////////////////////////////////////////////////////////////////////////
static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
SkVector vec = pts[1] - pts[0];
SkScalar mag = vec.length();
SkScalar inv = mag ? SkScalarInvert(mag) : 0;
vec.scale(inv);
matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
matrix->postTranslate(-pts[0].fX, -pts[0].fY);
matrix->postScale(inv, inv);
}
///////////////////////////////////////////////////////////////////////////////
class Linear_Gradient : public Gradient_Shader {
public:
Linear_Gradient(const SkPoint pts[2],
const SkColor colors[], const SkScalar pos[], int colorCount,
SkShader::TileMode mode, SkUnitMapper* mapper)
: Gradient_Shader(colors, pos, colorCount, mode, mapper)
{
fCachedBitmap = NULL;
pts_to_unit_matrix(pts, &fPtsToUnit);
}
virtual ~Linear_Gradient() {
if (fCachedBitmap) {
SkDELETE(fCachedBitmap);
}
}
virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&);
virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
virtual bool asABitmap(SkBitmap*, SkMatrix*, TileMode*);
virtual void onCacheReset() {
if (fCachedBitmap) {
SkDELETE(fCachedBitmap);
fCachedBitmap = NULL;
}
}
static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
return SkNEW_ARGS(Linear_Gradient, (buffer));
}
protected:
Linear_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {
fCachedBitmap = NULL;
}
virtual Factory getFactory() { return CreateProc; }
private:
SkBitmap* fCachedBitmap; // allocated on demand
typedef Gradient_Shader INHERITED;
};
bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
const SkMatrix& matrix) {
if (!this->INHERITED::setContext(device, paint, matrix)) {
return false;
}
unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask;
if ((fDstToIndex.getType() & ~mask) == 0) {
fFlags |= SkShader::kConstInY32_Flag;
if ((fFlags & SkShader::kHasSpan16_Flag) && !paint.isDither()) {
// only claim this if we do have a 16bit mode (i.e. none of our
// colors have alpha), and if we are not dithering (which obviously
// is not const in Y).
fFlags |= SkShader::kConstInY16_Flag;
}
}
return true;
}
// Return true if fx, fx+dx, fx+2*dx, ... is always in range
static inline bool no_need_for_clamp(int fx, int dx, int count)
{
SkASSERT(count > 0);
return (unsigned)((fx | (fx + (count - 1) * dx)) >> 8) <= 0xFF;
}
void Linear_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
{
SkASSERT(count > 0);
SkPoint srcPt;
SkMatrix::MapXYProc dstProc = fDstToIndexProc;
TileProc proc = fTileProc;
const SkPMColor* cache = this->getCache32();
if (fDstToIndexClass != kPerspective_MatrixClass) {
dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
// preround fx by half the amount we throw away
fx += 1 << 7;
if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
SkFixed dxStorage[1];
(void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
dx = dxStorage[0];
} else {
SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
dx = SkScalarToFixed(fDstToIndex.getScaleX());
}
if (SkFixedNearlyZero(dx)) {
// we're a vertical gradient, so no change in a span
unsigned fi = proc(fx);
SkASSERT(fi <= 0xFFFF);
sk_memset32(dstC, cache[fi >> (16 - kCache32Bits)], count);
} else if (proc == clamp_tileproc) {
#if 0
if (no_need_for_clamp(fx, dx, count))
{
unsigned fi;
while ((count -= 4) >= 0)
{
fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
fi = fx >> 8; SkASSERT(fi <= 0xFF); fx += dx; *dstC++ = cache[fi];
}
SkASSERT(count <= -1 && count >= -4);
count += 4;
while (--count >= 0)
{
fi = fx >> 8;
SkASSERT(fi <= 0xFF);
fx += dx;
*dstC++ = cache[fi];
}
}
else
#endif
do {
unsigned fi = SkClampMax(fx >> 8, 0xFF);
SkASSERT(fi <= 0xFF);
fx += dx;
*dstC++ = cache[fi];
} while (--count != 0);
} else if (proc == mirror_tileproc) {
do {
unsigned fi = mirror_8bits(fx >> 8);
SkASSERT(fi <= 0xFF);
fx += dx;
*dstC++ = cache[fi];
} while (--count != 0);
} else {
SkASSERT(proc == repeat_tileproc);
do {
unsigned fi = repeat_8bits(fx >> 8);
SkASSERT(fi <= 0xFF);
fx += dx;
*dstC++ = cache[fi];
} while (--count != 0);
}
} else {
SkScalar dstX = SkIntToScalar(x);
SkScalar dstY = SkIntToScalar(y);
do {
dstProc(fDstToIndex, dstX, dstY, &srcPt);
unsigned fi = proc(SkScalarToFixed(srcPt.fX));
SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[fi >> (16 - kCache32Bits)];
dstX += SK_Scalar1;
} while (--count != 0);
}
}
bool Linear_Gradient::asABitmap(SkBitmap* bitmap, SkMatrix* matrix,
TileMode xy[]) {
// we cache our "bitmap", so it's generationID will be const on subsequent
// calls to asABitmap
if (NULL == fCachedBitmap) {
fCachedBitmap = SkNEW(SkBitmap);
fCachedBitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
fCachedBitmap->setPixels((void*)this->getCache32(), NULL);
}
if (bitmap) {
*bitmap = *fCachedBitmap;
}
if (matrix) {
matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1);
matrix->preConcat(fPtsToUnit);
}
if (xy) {
xy[0] = fTileMode;
xy[1] = kClamp_TileMode;
}
return true;
}
static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
int count) {
if (reinterpret_cast<uintptr_t>(dst) & 2) {
*dst++ = value;
count -= 1;
SkTSwap(value, other);
}
sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
if (count & 1) {
dst[count - 1] = value;
}
}
void Linear_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
{
SkASSERT(count > 0);
SkPoint srcPt;
SkMatrix::MapXYProc dstProc = fDstToIndexProc;
TileProc proc = fTileProc;
const uint16_t* cache = this->getCache16();
int toggle = ((x ^ y) & 1) << kCache16Bits;
if (fDstToIndexClass != kPerspective_MatrixClass) {
dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
// preround fx by half the amount we throw away
fx += 1 << 7;
if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
SkFixed dxStorage[1];
(void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
dx = dxStorage[0];
} else {
SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
dx = SkScalarToFixed(fDstToIndex.getScaleX());
}
if (SkFixedNearlyZero(dx)) {
// we're a vertical gradient, so no change in a span
unsigned fi = proc(fx) >> 10;
SkASSERT(fi <= 63);
dither_memset16(dstC, cache[toggle + fi], cache[(toggle ^ (1 << kCache16Bits)) + fi], count);
} else if (proc == clamp_tileproc) {
do {
unsigned fi = SkClampMax(fx >> 10, 63);
SkASSERT(fi <= 63);
fx += dx;
*dstC++ = cache[toggle + fi];
toggle ^= (1 << kCache16Bits);
} while (--count != 0);
} else if (proc == mirror_tileproc) {
do {
unsigned fi = mirror_6bits(fx >> 10);
SkASSERT(fi <= 0x3F);
fx += dx;
*dstC++ = cache[toggle + fi];
toggle ^= (1 << kCache16Bits);
} while (--count != 0);
} else {
SkASSERT(proc == repeat_tileproc);
do {
unsigned fi = repeat_6bits(fx >> 10);
SkASSERT(fi <= 0x3F);
fx += dx;
*dstC++ = cache[toggle + fi];
toggle ^= (1 << kCache16Bits);
} while (--count != 0);
}
} else {
SkScalar dstX = SkIntToScalar(x);
SkScalar dstY = SkIntToScalar(y);
do {
dstProc(fDstToIndex, dstX, dstY, &srcPt);
unsigned fi = proc(SkScalarToFixed(srcPt.fX));
SkASSERT(fi <= 0xFFFF);
int index = fi >> (16 - kCache16Bits);
*dstC++ = cache[toggle + index];
toggle ^= (1 << kCache16Bits);
dstX += SK_Scalar1;
} while (--count != 0);
}
}
///////////////////////////////////////////////////////////////////////////////
#define kSQRT_TABLE_BITS 11
#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
#include "SkRadialGradient_Table.h"
#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
#include <stdio.h>
void SkRadialGradient_BuildTable()
{
// build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
SkASSERT(file);
::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
for (int i = 0; i < kSQRT_TABLE_SIZE; i++)
{
if ((i & 15) == 0)
::fprintf(file, "\t");
uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
::fprintf(file, "0x%02X", value);
if (i < kSQRT_TABLE_SIZE-1)
::fprintf(file, ", ");
if ((i & 15) == 15)
::fprintf(file, "\n");
}
::fprintf(file, "};\n");
::fclose(file);
}
#endif
static void rad_to_unit_matrix(const SkPoint& center, SkScalar radius, SkMatrix* matrix)
{
SkScalar inv = SkScalarInvert(radius);
matrix->setTranslate(-center.fX, -center.fY);
matrix->postScale(inv, inv);
}
class Radial_Gradient : public Gradient_Shader {
public:
Radial_Gradient(const SkPoint& center, SkScalar radius,
const SkColor colors[], const SkScalar pos[], int colorCount,
SkShader::TileMode mode, SkUnitMapper* mapper)
: Gradient_Shader(colors, pos, colorCount, mode, mapper)
{
// make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
rad_to_unit_matrix(center, radius, &fPtsToUnit);
}
virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count)
{
SkASSERT(count > 0);
SkPoint srcPt;
SkMatrix::MapXYProc dstProc = fDstToIndexProc;
TileProc proc = fTileProc;
const SkPMColor* cache = this->getCache32();
if (fDstToIndexClass != kPerspective_MatrixClass)
{
dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
if (fDstToIndexClass == kFixedStepInX_MatrixClass)
{
SkFixed storage[2];
(void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
dx = storage[0];
dy = storage[1];
}
else
{
SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
dx = SkScalarToFixed(fDstToIndex.getScaleX());
dy = SkScalarToFixed(fDstToIndex.getSkewY());
}
if (proc == clamp_tileproc)
{
const uint8_t* sqrt_table = gSqrt8Table;
fx >>= 1;
dx >>= 1;
fy >>= 1;
dy >>= 1;
do {
unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
*dstC++ = cache[sqrt_table[fi] >> (8 - kCache32Bits)];
fx += dx;
fy += dy;
} while (--count != 0);
}
else if (proc == mirror_tileproc)
{
do {
SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
unsigned fi = mirror_tileproc(dist);
SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[fi >> (16 - kCache32Bits)];
fx += dx;
fy += dy;
} while (--count != 0);
}
else
{
SkASSERT(proc == repeat_tileproc);
do {
SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
unsigned fi = repeat_tileproc(dist);
SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[fi >> (16 - kCache32Bits)];
fx += dx;
fy += dy;
} while (--count != 0);
}
}
else // perspective case
{
SkScalar dstX = SkIntToScalar(x);
SkScalar dstY = SkIntToScalar(y);
do {
dstProc(fDstToIndex, dstX, dstY, &srcPt);
unsigned fi = proc(SkScalarToFixed(srcPt.length()));
SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[fi >> (16 - kCache32Bits)];
dstX += SK_Scalar1;
} while (--count != 0);
}
}
virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) {
SkASSERT(count > 0);
SkPoint srcPt;
SkMatrix::MapXYProc dstProc = fDstToIndexProc;
TileProc proc = fTileProc;
const uint16_t* cache = this->getCache16();
int toggle = ((x ^ y) & 1) << kCache16Bits;
if (fDstToIndexClass != kPerspective_MatrixClass) {
dstProc(fDstToIndex, SkIntToScalar(x), SkIntToScalar(y), &srcPt);
SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
SkFixed storage[2];
(void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
dx = storage[0];
dy = storage[1];
} else {
SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
dx = SkScalarToFixed(fDstToIndex.getScaleX());
dy = SkScalarToFixed(fDstToIndex.getSkewY());
}
if (proc == clamp_tileproc) {
const uint8_t* sqrt_table = gSqrt8Table;
/* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
rather than 0xFFFF which is slower. This is a compromise, since it reduces our
precision, but that appears to be visually OK. If we decide this is OK for
all of our cases, we could (it seems) put this scale-down into fDstToIndex,
to avoid having to do these extra shifts each time.
*/
fx >>= 1;
dx >>= 1;
fy >>= 1;
dy >>= 1;
if (dy == 0) { // might perform this check for the other modes, but the win will be a smaller % of the total
fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
fy *= fy;
do {
unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
fx += dx;
*dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
toggle ^= (1 << kCache16Bits);
} while (--count != 0);
} else {
do {
unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
fx += dx;
fy += dy;
*dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
toggle ^= (1 << kCache16Bits);
} while (--count != 0);
}
} else if (proc == mirror_tileproc) {
do {
SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
unsigned fi = mirror_tileproc(dist);
SkASSERT(fi <= 0xFFFF);
fx += dx;
fy += dy;
*dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
toggle ^= (1 << kCache16Bits);
} while (--count != 0);
} else {
SkASSERT(proc == repeat_tileproc);
do {
SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
unsigned fi = repeat_tileproc(dist);
SkASSERT(fi <= 0xFFFF);
fx += dx;
fy += dy;
*dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
toggle ^= (1 << kCache16Bits);
} while (--count != 0);
}
} else { // perspective case
SkScalar dstX = SkIntToScalar(x);
SkScalar dstY = SkIntToScalar(y);
do {
dstProc(fDstToIndex, dstX, dstY, &srcPt);
unsigned fi = proc(SkScalarToFixed(srcPt.length()));
SkASSERT(fi <= 0xFFFF);
int index = fi >> (16 - kCache16Bits);
*dstC++ = cache[toggle + index];
toggle ^= (1 << kCache16Bits);
dstX += SK_Scalar1;
} while (--count != 0);
}
}
static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
return SkNEW_ARGS(Radial_Gradient, (buffer));
}
protected:
Radial_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {};
virtual Factory getFactory() { return CreateProc; }
virtual void onCacheReset() {}
private:
typedef Gradient_Shader INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
class Sweep_Gradient : public Gradient_Shader {
public:
Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
const SkScalar pos[], int count, SkUnitMapper* mapper)
: Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper)
{
fPtsToUnit.setTranslate(-cx, -cy);
}
virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
return SkNEW_ARGS(Sweep_Gradient, (buffer));
}
protected:
Sweep_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer) {}
virtual Factory getFactory() { return CreateProc; }
virtual void onCacheReset() {}
private:
typedef Gradient_Shader INHERITED;
};
#ifdef COMPUTE_SWEEP_TABLE
#define PI 3.14159265
static bool gSweepTableReady;
static uint8_t gSweepTable[65];
/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
We scale the results to [0..32]
*/
static const uint8_t* build_sweep_table()
{
if (!gSweepTableReady)
{
const int N = 65;
const double DENOM = N - 1;
for (int i = 0; i < N; i++)
{
double arg = i / DENOM;
double v = atan(arg);
int iv = (int)round(v * DENOM * 2 / PI);
// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
printf("%d, ", iv);
gSweepTable[i] = iv;
}
gSweepTableReady = true;
}
return gSweepTable;
}
#else
static const uint8_t gSweepTable[] = {
0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
32
};
static const uint8_t* build_sweep_table() { return gSweepTable; }
#endif
// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
//unsigned div_64(int numer, int denom);
static unsigned div_64(int numer, int denom)
{
SkASSERT(numer <= denom);
SkASSERT(numer > 0);
SkASSERT(denom > 0);
int nbits = SkCLZ(numer);
int dbits = SkCLZ(denom);
int bits = 6 - nbits + dbits;
SkASSERT(bits <= 6);
if (bits < 0) // detect underflow
return 0;
denom <<= dbits - 1;
numer <<= nbits - 1;
unsigned result = 0;
// do the first one
if ((numer -= denom) >= 0)
result = 1;
else
numer += denom;
// Now fall into our switch statement if there are more bits to compute
if (bits > 0)
{
// make room for the rest of the answer bits
result <<= bits;
switch (bits) {
case 6:
if ((numer = (numer << 1) - denom) >= 0)
result |= 32;
else
numer += denom;
case 5:
if ((numer = (numer << 1) - denom) >= 0)
result |= 16;
else
numer += denom;
case 4:
if ((numer = (numer << 1) - denom) >= 0)
result |= 8;
else
numer += denom;
case 3:
if ((numer = (numer << 1) - denom) >= 0)
result |= 4;
else
numer += denom;
case 2:
if ((numer = (numer << 1) - denom) >= 0)
result |= 2;
else
numer += denom;
case 1:
default: // not strictly need, but makes GCC make better ARM code
if ((numer = (numer << 1) - denom) >= 0)
result |= 1;
else
numer += denom;
}
}
return result;
}
// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
static unsigned atan_0_90(SkFixed y, SkFixed x)
{
#ifdef SK_DEBUG
{
static bool gOnce;
if (!gOnce)
{
gOnce = true;
SkASSERT(div_64(55, 55) == 64);
SkASSERT(div_64(128, 256) == 32);
SkASSERT(div_64(2326528, 4685824) == 31);
SkASSERT(div_64(753664, 5210112) == 9);
SkASSERT(div_64(229376, 4882432) == 3);
SkASSERT(div_64(2, 64) == 2);
SkASSERT(div_64(1, 64) == 1);
// test that we handle underflow correctly
SkASSERT(div_64(12345, 0x54321234) == 0);
}
}
#endif
SkASSERT(y > 0 && x > 0);
const uint8_t* table = build_sweep_table();
unsigned result;
bool swap = (x < y);
if (swap)
{
// first part of the atan(v) = PI/2 - atan(1/v) identity
// since our div_64 and table want v <= 1, where v = y/x
SkTSwap<SkFixed>(x, y);
}
result = div_64(y, x);
#ifdef SK_DEBUG
{
unsigned result2 = SkDivBits(y, x, 6);
SkASSERT(result2 == result ||
(result == 1 && result2 == 0));
}
#endif
SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
result = table[result];
if (swap)
{
// complete the atan(v) = PI/2 - atan(1/v) identity
result = 64 - result;
// pin to 63
result -= result >> 6;
}
SkASSERT(result <= 63);
return result;
}
// returns angle in a circle [0..2PI) -> [0..255]
static unsigned SkATan2_255(SkFixed y, SkFixed x)
{
if (x == 0)
{
if (y == 0)
return 0;
return y < 0 ? 192 : 64;
}
if (y == 0)
return x < 0 ? 128 : 0;
/* Find the right quadrant for x,y
Since atan_0_90 only handles the first quadrant, we rotate x,y
appropriately before calling it, and then add the right amount
to account for the real quadrant.
quadrant 0 : add 0 | x > 0 && y > 0
quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
map x<0 to (1 << 6)
map y<0 to (3 << 6)
add = map_x ^ map_y
*/
int xsign = x >> 31;
int ysign = y >> 31;
int add = ((-xsign) ^ (ysign & 3)) << 6;
#ifdef SK_DEBUG
if (0 == add)
SkASSERT(x > 0 && y > 0);
else if (64 == add)
SkASSERT(x < 0 && y > 0);
else if (128 == add)
SkASSERT(x < 0 && y < 0);
else if (192 == add)
SkASSERT(x > 0 && y < 0);
else
SkASSERT(!"bad value for add");
#endif
/* This ^ trick makes x, y positive, and the swap<> handles quadrants
where we need to rotate x,y by 90 or -90
*/
x = (x ^ xsign) - xsign;
y = (y ^ ysign) - ysign;
if (add & 64) // quads 1 or 3 need to swap x,y
SkTSwap<SkFixed>(x, y);
unsigned result = add + atan_0_90(y, x);
SkASSERT(result < 256);
return result;
}
void Sweep_Gradient::shadeSpan(int x, int y, SkPMColor dstC[], int count)
{
SkMatrix::MapXYProc proc = fDstToIndexProc;
const SkMatrix& matrix = fDstToIndex;
const SkPMColor* cache = this->getCache32();
SkPoint srcPt;
if (fDstToIndexClass != kPerspective_MatrixClass)
{
proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
if (fDstToIndexClass == kFixedStepInX_MatrixClass)
{
SkFixed storage[2];
(void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
&storage[0], &storage[1]);
dx = storage[0];
dy = storage[1];
}
else
{
SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
dx = SkScalarToFixed(matrix.getScaleX());
dy = SkScalarToFixed(matrix.getSkewY());
}
for (; count > 0; --count)
{
*dstC++ = cache[SkATan2_255(fy, fx)];
fx += dx;
fy += dy;
}
}
else // perspective case
{
for (int stop = x + count; x < stop; x++)
{
proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
SkScalarToFixed(srcPt.fX));
*dstC++ = cache[index];
}
}
}
void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t dstC[], int count)
{
SkMatrix::MapXYProc proc = fDstToIndexProc;
const SkMatrix& matrix = fDstToIndex;
const uint16_t* cache = this->getCache16();
int toggle = ((x ^ y) & 1) << kCache16Bits;
SkPoint srcPt;
if (fDstToIndexClass != kPerspective_MatrixClass)
{
proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
if (fDstToIndexClass == kFixedStepInX_MatrixClass)
{
SkFixed storage[2];
(void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
&storage[0], &storage[1]);
dx = storage[0];
dy = storage[1];
}
else
{
SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
dx = SkScalarToFixed(matrix.getScaleX());
dy = SkScalarToFixed(matrix.getSkewY());
}
for (; count > 0; --count)
{
int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
*dstC++ = cache[toggle + index];
toggle ^= (1 << kCache16Bits);
fx += dx;
fy += dy;
}
}
else // perspective case
{
for (int stop = x + count; x < stop; x++)
{
proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
int index = SkATan2_255(SkScalarToFixed(srcPt.fY),
SkScalarToFixed(srcPt.fX));
index >>= (8 - kCache16Bits);
*dstC++ = cache[toggle + index];
toggle ^= (1 << kCache16Bits);
}
}
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// assumes colors is SkColor* and pos is SkScalar*
#define EXPAND_1_COLOR(count) \
SkColor tmp[2]; \
do { \
if (1 == count) { \
tmp[0] = tmp[1] = colors[0]; \
colors = tmp; \
pos = NULL; \
count = 2; \
} \
} while (0)
SkShader* SkGradientShader::CreateLinear( const SkPoint pts[2],
const SkColor colors[], const SkScalar pos[], int colorCount,
SkShader::TileMode mode, SkUnitMapper* mapper)
{
if (NULL == pts || NULL == colors || colorCount < 1) {
return NULL;
}
EXPAND_1_COLOR(colorCount);
return SkNEW_ARGS(Linear_Gradient,
(pts, colors, pos, colorCount, mode, mapper));
}
SkShader* SkGradientShader::CreateRadial( const SkPoint& center, SkScalar radius,
const SkColor colors[], const SkScalar pos[], int colorCount,
SkShader::TileMode mode, SkUnitMapper* mapper)
{
if (radius <= 0 || NULL == colors || colorCount < 1) {
return NULL;
}
EXPAND_1_COLOR(colorCount);
return SkNEW_ARGS(Radial_Gradient,
(center, radius, colors, pos, colorCount, mode, mapper));
}
SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
const SkColor colors[],
const SkScalar pos[],
int count, SkUnitMapper* mapper)
{
if (NULL == colors || count < 1) {
return NULL;
}
EXPAND_1_COLOR(count);
return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper));
}
static SkFlattenable::Registrar gLinearGradientReg("Linear_Gradient",
Linear_Gradient::CreateProc);
static SkFlattenable::Registrar gRadialGradientReg("Radial_Gradient",
Radial_Gradient::CreateProc);
static SkFlattenable::Registrar gSweepGradientReg("Sweep_Gradient",
Sweep_Gradient::CreateProc);