Interpolate vertical linear gradients for improved quality.

Consolidate interpolation functions, add new faster more accurate dithering
interpolator.



git-svn-id: http://skia.googlecode.com/svn/trunk@3072 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
tomhudson@google.com 2012-01-18 21:28:01 +00:00
parent 90503184f6
commit 13e812c69a
5 changed files with 213 additions and 93 deletions

View File

@ -215,6 +215,57 @@ static inline SkPMColor SkPackARGB32(U8CPU a, U8CPU r, U8CPU g, U8CPU b) {
(g << SK_G32_SHIFT) | (b << SK_B32_SHIFT); (g << SK_G32_SHIFT) | (b << SK_B32_SHIFT);
} }
/**
* Abstract 4-byte interpolation, implemented on top of SkPMColor
* utility functions. Third parameter controls blending of the first two:
* (src, dst, 0) returns dst
* (src, dst, 0xFF) returns src
*/
static inline SkPMColor SkFourByteInterp(SkPMColor src, SkPMColor dst,
U8CPU srcWeight) {
unsigned scale = SkAlpha255To256(srcWeight);
unsigned a = SkAlphaBlend(SkGetPackedA32(src), SkGetPackedA32(dst), scale);
unsigned r = SkAlphaBlend(SkGetPackedR32(src), SkGetPackedR32(dst), scale);
unsigned g = SkAlphaBlend(SkGetPackedG32(src), SkGetPackedG32(dst), scale);
unsigned b = SkAlphaBlend(SkGetPackedB32(src), SkGetPackedB32(dst), scale);
return SkPackARGB32(a, r, g, b);
}
/**
* 32b optimized version; currently appears to be 10% faster even on 64b
* architectures than an equivalent 64b version and 30% faster than
* SkFourByteInterp(). Third parameter controls blending of the first two:
* (src, dst, 0) returns dst
* (src, dst, 0xFF) returns src
* ** Does not match the results of SkFourByteInterp() because we use
* a more accurate scale computation!
* TODO: migrate Skia function to using an accurate 255->266 alpha
* conversion.
*/
static inline SkPMColor SkFastFourByteInterp(SkPMColor src,
SkPMColor dst,
U8CPU srcWeight) {
SkASSERT(srcWeight < 256);
// Reorders ARGB to AG-RB in order to reduce the number of operations.
const uint32_t mask = 0xFF00FF;
uint32_t src_rb = src & mask;
uint32_t src_ag = (src >> 8) & mask;
uint32_t dst_rb = dst & mask;
uint32_t dst_ag = (dst >> 8) & mask;
// scale = srcWeight + (srcWeight >> 7) is more accurate than
// scale = srcWeight + 1, but 7% slower
int scale = srcWeight + (srcWeight >> 7);
uint32_t ret_rb = src_rb * scale + (256 - scale) * dst_rb;
uint32_t ret_ag = src_ag * scale + (256 - scale) * dst_ag;
return (ret_ag & ~mask) | ((ret_rb & ~mask) >> 8);
}
/** /**
* Same as SkPackARGB32, but this version guarantees to not check that the * Same as SkPackARGB32, but this version guarantees to not check that the
* values are premultiplied in the debug version. * values are premultiplied in the debug version.

View File

@ -12,17 +12,6 @@
#define SkAlphaMulAlpha(a, b) SkMulDiv255Round(a, b) #define SkAlphaMulAlpha(a, b) SkMulDiv255Round(a, b)
static SkPMColor SkFourByteInterp(SkPMColor src, SkPMColor dst, U8CPU alpha) {
unsigned scale = SkAlpha255To256(alpha);
unsigned a = SkAlphaBlend(SkGetPackedA32(src), SkGetPackedA32(dst), scale);
unsigned r = SkAlphaBlend(SkGetPackedR32(src), SkGetPackedR32(dst), scale);
unsigned g = SkAlphaBlend(SkGetPackedG32(src), SkGetPackedG32(dst), scale);
unsigned b = SkAlphaBlend(SkGetPackedB32(src), SkGetPackedB32(dst), scale);
return SkPackARGB32(a, r, g, b);
}
#if 0 #if 0
// idea for higher precision blends in xfer procs (and slightly faster) // idea for higher precision blends in xfer procs (and slightly faster)
// see DstATop as a probable caller // see DstATop as a probable caller

View File

@ -98,16 +98,6 @@ static int scale_dist_14(int dist, uint32_t mul, uint32_t sub)
return result; return result;
} }
static SkPMColor SkFourByteInterp(SkPMColor src, SkPMColor dst, unsigned scale)
{
unsigned a = SkAlphaBlend(SkGetPackedA32(src), SkGetPackedA32(dst), scale);
unsigned r = SkAlphaBlend(SkGetPackedR32(src), SkGetPackedR32(dst), scale);
unsigned g = SkAlphaBlend(SkGetPackedG32(src), SkGetPackedG32(dst), scale);
unsigned b = SkAlphaBlend(SkGetPackedB32(src), SkGetPackedB32(dst), scale);
return SkPackARGB32(a, r, g, b);
}
static inline unsigned Accurate255To256(unsigned x) { static inline unsigned Accurate255To256(unsigned x) {
return x + (x >> 7); return x + (x >> 7);
} }

View File

@ -119,28 +119,41 @@ public:
virtual bool isOpaque() const SK_OVERRIDE; virtual bool isOpaque() const SK_OVERRIDE;
enum { enum {
kCache16Bits = 8, // seems like enough for visual accuracy /// Seems like enough for visual accuracy. TODO: if pos[] deserves
kCache16Count = 1 << kCache16Bits, /// it, use a larger cache.
kCache16Mask = kCache16Count - 1, kCache16Bits = 8,
kGradient16Length = (1 << kCache16Bits),
/// Each cache gets 1 extra entry at the end so we don't have to
/// test for end-of-cache in lerps. This is also the value used
/// to stride *writes* into the dither cache; it must not be zero.
/// Total space for a cache is 2x kCache16Count entries: one
/// regular cache, one for dithering.
kCache16Count = kGradient16Length + 1,
kCache16Shift = 16 - kCache16Bits, kCache16Shift = 16 - kCache16Bits,
kSqrt16Shift = 8 - kCache16Bits, kSqrt16Shift = 8 - kCache16Bits,
kCache32Bits = 8, // pretty much should always be 8 /// Seems like enough for visual accuracy. TODO: if pos[] deserves
kCache32Count = 1 << kCache32Bits, /// it, use a larger cache.
kCache32Mask = kCache32Count - 1, kCache32Bits = 8,
kGradient32Length = (1 << kCache32Bits),
/// Each cache gets 1 extra entry at the end so we don't have to
/// test for end-of-cache in lerps. This is also the value used
/// to stride *writes* into the dither cache; it must not be zero.
/// Total space for a cache is 2x kCache32Count entries: one
/// regular cache, one for dithering.
kCache32Count = kGradient32Length + 1,
kCache32Shift = 16 - kCache32Bits, kCache32Shift = 16 - kCache32Bits,
kSqrt32Shift = 8 - kCache32Bits, kSqrt32Shift = 8 - kCache32Bits,
/** ToggleMasks are used in dithering to switch between the two /// This value is used to *read* the dither cache; it may be 0
halves of the gradient cache; they should be equal to the size /// if dithering is disabled.
of a half-cache.
*/
#ifdef USE_DITHER_32BIT_GRADIENT #ifdef USE_DITHER_32BIT_GRADIENT
kToggleMask32 = kCache32Count, kDitherStride32 = kCache32Count,
#else #else
kToggleMask32 = 0, kDitherStride32 = 0,
#endif #endif
kToggleMask16 = kCache16Count kDitherStride16 = kCache16Count,
kLerpRemainderMask32 = (1 << (16 - kCache32Bits)) - 1
}; };
@ -557,10 +570,11 @@ void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
do { do {
cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16); cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16);
cache[kCache32Count] = SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a), cache[kCache32Count] =
dither_fixed_to_8(r), SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a),
dither_fixed_to_8(g), dither_fixed_to_8(r),
dither_fixed_to_8(b)); dither_fixed_to_8(g),
dither_fixed_to_8(b));
cache += 1; cache += 1;
a += da; a += da;
r += dr; r += dr;
@ -586,6 +600,14 @@ static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
return 0; return 0;
} }
/** We duplicate the last value in each half of the cache so that
interpolation doesn't have to special-case being at the last point.
*/
static void complete_16bit_cache(uint16_t* cache, int stride) {
cache[stride - 1] = cache[stride - 2];
cache[2 * stride - 1] = cache[2 * stride - 2];
}
const uint16_t* Gradient_Shader::getCache16() const { const uint16_t* Gradient_Shader::getCache16() const {
if (fCache16 == NULL) { if (fCache16 == NULL) {
// double the count for dither entries // double the count for dither entries
@ -597,7 +619,8 @@ const uint16_t* Gradient_Shader::getCache16() const {
} }
fCache16 = fCache16Storage; fCache16 = fCache16Storage;
if (fColorCount == 2) { if (fColorCount == 2) {
Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1], kCache16Count); Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
kGradient16Length);
} else { } else {
Rec* rec = fRecs; Rec* rec = fRecs;
int prevIndex = 0; int prevIndex = 0;
@ -609,7 +632,8 @@ const uint16_t* Gradient_Shader::getCache16() const {
Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1); Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
prevIndex = nextIndex; prevIndex = nextIndex;
} }
SkASSERT(prevIndex == kCache16Count - 1); // one extra space left over at the end for complete_16bit_cache()
SkASSERT(prevIndex == kGradient16Length - 1);
} }
if (fMapper) { if (fMapper) {
@ -617,7 +641,7 @@ const uint16_t* Gradient_Shader::getCache16() const {
uint16_t* linear = fCache16; // just computed linear data uint16_t* linear = fCache16; // just computed linear data
uint16_t* mapped = fCache16Storage; // storage for mapped data uint16_t* mapped = fCache16Storage; // storage for mapped data
SkUnitMapper* map = fMapper; SkUnitMapper* map = fMapper;
for (int i = 0; i < kCache16Count; i++) { for (int i = 0; i < kGradient16Length; i++) {
int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift; int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
mapped[i] = linear[index]; mapped[i] = linear[index];
mapped[i + kCache16Count] = linear[index + kCache16Count]; mapped[i + kCache16Count] = linear[index + kCache16Count];
@ -625,10 +649,19 @@ const uint16_t* Gradient_Shader::getCache16() const {
sk_free(fCache16); sk_free(fCache16);
fCache16 = fCache16Storage; fCache16 = fCache16Storage;
} }
complete_16bit_cache(fCache16, kCache16Count);
} }
return fCache16; return fCache16;
} }
/** We duplicate the last value in each half of the cache so that
interpolation doesn't have to special-case being at the last point.
*/
static void complete_32bit_cache(SkPMColor* cache, int stride) {
cache[stride - 1] = cache[stride - 2];
cache[2 * stride - 1] = cache[2 * stride - 2];
}
const SkPMColor* Gradient_Shader::getCache32() const { const SkPMColor* Gradient_Shader::getCache32() const {
if (fCache32 == NULL) { if (fCache32 == NULL) {
// double the count for dither entries // double the count for dither entries
@ -642,13 +675,13 @@ const SkPMColor* Gradient_Shader::getCache32() const {
fCache32 = (SkPMColor*)fCache32PixelRef->getAddr(); fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
if (fColorCount == 2) { if (fColorCount == 2) {
Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1], Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
kCache32Count, fCacheAlpha); kGradient32Length, fCacheAlpha);
} else { } else {
Rec* rec = fRecs; Rec* rec = fRecs;
int prevIndex = 0; int prevIndex = 0;
for (int i = 1; i < fColorCount; i++) { for (int i = 1; i < fColorCount; i++) {
int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift; int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
SkASSERT(nextIndex < kCache32Count); SkASSERT(nextIndex < kGradient32Length);
if (nextIndex > prevIndex) if (nextIndex > prevIndex)
Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1], Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
@ -656,7 +689,7 @@ const SkPMColor* Gradient_Shader::getCache32() const {
nextIndex - prevIndex + 1, fCacheAlpha); nextIndex - prevIndex + 1, fCacheAlpha);
prevIndex = nextIndex; prevIndex = nextIndex;
} }
SkASSERT(prevIndex == kCache32Count - 1); SkASSERT(prevIndex == kGradient32Length - 1);
} }
if (fMapper) { if (fMapper) {
@ -665,7 +698,7 @@ const SkPMColor* Gradient_Shader::getCache32() const {
SkPMColor* linear = fCache32; // just computed linear data SkPMColor* linear = fCache32; // just computed linear data
SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
SkUnitMapper* map = fMapper; SkUnitMapper* map = fMapper;
for (int i = 0; i < kCache32Count; i++) { for (int i = 0; i < kGradient32Length; i++) {
int index = map->mapUnit16((i << 8) | i) >> 8; int index = map->mapUnit16((i << 8) | i) >> 8;
mapped[i] = linear[index]; mapped[i] = linear[index];
mapped[i + kCache32Count] = linear[index + kCache32Count]; mapped[i + kCache32Count] = linear[index + kCache32Count];
@ -674,6 +707,7 @@ const SkPMColor* Gradient_Shader::getCache32() const {
fCache32PixelRef = newPR; fCache32PixelRef = newPR;
fCache32 = (SkPMColor*)newPR->getAddr(); fCache32 = (SkPMColor*)newPR->getAddr();
} }
complete_32bit_cache(fCache32, kCache32Count);
} }
return fCache32; return fCache32;
} }
@ -695,7 +729,7 @@ void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
if (fMapper) { if (fMapper) {
// force our cahce32pixelref to be built // force our cahce32pixelref to be built
(void)this->getCache32(); (void)this->getCache32();
bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1); bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
bitmap->setPixelRef(fCache32PixelRef); bitmap->setPixelRef(fCache32PixelRef);
return; return;
} }
@ -735,7 +769,9 @@ void Gradient_Shader::commonAsABitmap(SkBitmap* bitmap) const {
if (!gCache->find(storage.get(), size, bitmap)) { if (!gCache->find(storage.get(), size, bitmap)) {
// force our cahce32pixelref to be built // force our cahce32pixelref to be built
(void)this->getCache32(); (void)this->getCache32();
bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1); // Only expose the linear section of the cache; don't let the caller
// know about the padding at the end to make interpolation faster.
bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1);
bitmap->setPixelRef(fCache32PixelRef); bitmap->setPixelRef(fCache32PixelRef);
gCache->add(storage.get(), size, *bitmap); gCache->add(storage.get(), size, *bitmap);
@ -851,7 +887,7 @@ bool Linear_Gradient::setContext(const SkBitmap& device, const SkPaint& paint,
SkASSERT(fi <= 0xFF); \ SkASSERT(fi <= 0xFF); \
fx += dx; \ fx += dx; \
*dstC++ = cache[toggle + fi]; \ *dstC++ = cache[toggle + fi]; \
toggle ^= Gradient_Shader::kToggleMask32; \ toggle ^= Gradient_Shader::kDitherStride32; \
} while (0) } while (0)
namespace { namespace {
@ -860,15 +896,28 @@ typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx,
SkPMColor* dstC, const SkPMColor* cache, SkPMColor* dstC, const SkPMColor* cache,
int toggle, int count); int toggle, int count);
void shadeSpan_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx, // lerp is unnecessary if there are no sharp discontinuities in the
SkPMColor* SK_RESTRICT dstC, // gradient - which must be true if there are only 2 colors - but for
const SkPMColor* SK_RESTRICT cache, // vertical gradients it's so cheap we do it anyway.
int toggle, int count) { void shadeSpan_linear_vertical_lerp(TileProc proc, SkFixed dx, SkFixed fx,
// we're a vertical gradient, so no change in a span SkPMColor* SK_RESTRICT dstC,
unsigned fi = proc(fx) >> Gradient_Shader::kCache32Shift; const SkPMColor* SK_RESTRICT cache,
sk_memset32_dither(dstC, cache[toggle + fi], int toggle, int count) {
cache[(toggle ^ Gradient_Shader::kToggleMask32) + fi], count); // We're a vertical gradient, so no change in a span.
// If colors change sharply across the gradient, dithering is
// insufficient (it subsamples the color space) and we need to lerp.
unsigned fullIndex = proc(fx);
unsigned fi = fullIndex >> (16 - Gradient_Shader::kCache32Bits);
unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32;
SkPMColor lerp =
SkFastFourByteInterp(
cache[toggle + fi + 1],
cache[toggle + fi], remainder);
SkPMColor dlerp =
SkFastFourByteInterp(
cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1],
cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], remainder);
sk_memset32_dither(dstC, lerp, dlerp, count);
} }
void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx, void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
@ -876,12 +925,12 @@ void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
const SkPMColor* SK_RESTRICT cache, const SkPMColor* SK_RESTRICT cache,
int toggle, int count) { int toggle, int count) {
SkClampRange range; SkClampRange range;
range.init(fx, dx, count, 0, 0xFF); range.init(fx, dx, count, 0, Gradient_Shader::kGradient32Length);
if ((count = range.fCount0) > 0) { if ((count = range.fCount0) > 0) {
sk_memset32_dither(dstC, sk_memset32_dither(dstC,
cache[toggle + range.fV0], cache[toggle + range.fV0],
cache[(toggle ^ Gradient_Shader::kToggleMask32) + range.fV0], cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV0],
count); count);
dstC += count; dstC += count;
} }
@ -903,13 +952,11 @@ void shadeSpan_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
if ((count = range.fCount2) > 0) { if ((count = range.fCount2) > 0) {
sk_memset32_dither(dstC, sk_memset32_dither(dstC,
cache[toggle + range.fV1], cache[toggle + range.fV1],
cache[(toggle ^ Gradient_Shader::kToggleMask32) + range.fV1], cache[(toggle ^ Gradient_Shader::kDitherStride32) + range.fV1],
count); count);
} }
} }
// TODO: we could merge mirror and repeat if we passed in a pointer to the
// *_8bits proc, but that'd lose inlining, which might be significant here.
void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx, void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
SkPMColor* SK_RESTRICT dstC, SkPMColor* SK_RESTRICT dstC,
const SkPMColor* SK_RESTRICT cache, const SkPMColor* SK_RESTRICT cache,
@ -919,7 +966,7 @@ void shadeSpan_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
SkASSERT(fi <= 0xFF); SkASSERT(fi <= 0xFF);
fx += dx; fx += dx;
*dstC++ = cache[toggle + fi]; *dstC++ = cache[toggle + fi];
toggle ^= Gradient_Shader::kToggleMask32; toggle ^= Gradient_Shader::kDitherStride32;
} while (--count != 0); } while (--count != 0);
} }
@ -932,9 +979,10 @@ void shadeSpan_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
SkASSERT(fi <= 0xFF); SkASSERT(fi <= 0xFF);
fx += dx; fx += dx;
*dstC++ = cache[toggle + fi]; *dstC++ = cache[toggle + fi];
toggle ^= Gradient_Shader::kToggleMask32; toggle ^= Gradient_Shader::kDitherStride32;
} while (--count != 0); } while (--count != 0);
} }
} }
void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
@ -946,7 +994,7 @@ void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
TileProc proc = fTileProc; TileProc proc = fTileProc;
const SkPMColor* SK_RESTRICT cache = this->getCache32(); const SkPMColor* SK_RESTRICT cache = this->getCache32();
#ifdef USE_DITHER_32BIT_GRADIENT #ifdef USE_DITHER_32BIT_GRADIENT
int toggle = ((x ^ y) & 1) << kCache32Bits; int toggle = ((x ^ y) & 1) * kDitherStride32;
#else #else
int toggle = 0; int toggle = 0;
#endif #endif
@ -967,7 +1015,7 @@ void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
LinearShadeProc shadeProc = shadeSpan_linear_repeat; LinearShadeProc shadeProc = shadeSpan_linear_repeat;
if (SkFixedNearlyZero(dx)) { if (SkFixedNearlyZero(dx)) {
shadeProc = shadeSpan_linear_vertical; shadeProc = shadeSpan_linear_vertical_lerp;
} else if (proc == clamp_tileproc) { } else if (proc == clamp_tileproc) {
shadeProc = shadeSpan_linear_clamp; shadeProc = shadeSpan_linear_clamp;
} else if (proc == mirror_tileproc) { } else if (proc == mirror_tileproc) {
@ -984,7 +1032,7 @@ void Linear_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
unsigned fi = proc(SkScalarToFixed(srcPt.fX)); unsigned fi = proc(SkScalarToFixed(srcPt.fX));
SkASSERT(fi <= 0xFFFF); SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[toggle + (fi >> kCache32Shift)]; *dstC++ = cache[toggle + (fi >> kCache32Shift)];
toggle ^= Gradient_Shader::kToggleMask32; toggle ^= Gradient_Shader::kDitherStride32;
dstX += SK_Scalar1; dstX += SK_Scalar1;
} while (--count != 0); } while (--count != 0);
} }
@ -998,7 +1046,7 @@ SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap,
this->commonAsABitmap(bitmap); this->commonAsABitmap(bitmap);
} }
if (matrix) { if (matrix) {
matrix->setScale(SkIntToScalar(kCache32Count), SK_Scalar1); matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1);
matrix->preConcat(fPtsToUnit); matrix->preConcat(fPtsToUnit);
} }
if (xy) { if (xy) {
@ -1035,10 +1083,10 @@ static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
#define NO_CHECK_ITER_16 \ #define NO_CHECK_ITER_16 \
do { \ do { \
unsigned fi = fx >> Gradient_Shader::kCache16Shift; \ unsigned fi = fx >> Gradient_Shader::kCache16Shift; \
SkASSERT(fi <= Gradient_Shader::kCache16Mask); \ SkASSERT(fi < Gradient_Shader::kCache16Count); \
fx += dx; \ fx += dx; \
*dstC++ = cache[toggle + fi]; \ *dstC++ = cache[toggle + fi]; \
toggle ^= Gradient_Shader::kToggleMask16; \ toggle ^= Gradient_Shader::kDitherStride16; \
} while (0) } while (0)
namespace { namespace {
@ -1053,9 +1101,9 @@ void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
int toggle, int count) { int toggle, int count) {
// we're a vertical gradient, so no change in a span // we're a vertical gradient, so no change in a span
unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift; unsigned fi = proc(fx) >> Gradient_Shader::kCache16Shift;
SkASSERT(fi <= Gradient_Shader::kCache16Mask); SkASSERT(fi < Gradient_Shader::kCache16Count);
dither_memset16(dstC, cache[toggle + fi], dither_memset16(dstC, cache[toggle + fi],
cache[(toggle ^ Gradient_Shader::kToggleMask16) + fi], count); cache[(toggle ^ Gradient_Shader::kDitherStride16) + fi], count);
} }
@ -1064,12 +1112,12 @@ void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
const uint16_t* SK_RESTRICT cache, const uint16_t* SK_RESTRICT cache,
int toggle, int count) { int toggle, int count) {
SkClampRange range; SkClampRange range;
range.init(fx, dx, count, 0, Gradient_Shader::kCache16Mask); range.init(fx, dx, count, 0, Gradient_Shader::kGradient16Length);
if ((count = range.fCount0) > 0) { if ((count = range.fCount0) > 0) {
dither_memset16(dstC, dither_memset16(dstC,
cache[toggle + range.fV0], cache[toggle + range.fV0],
cache[(toggle ^ Gradient_Shader::kToggleMask16) + range.fV0], cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV0],
count); count);
dstC += count; dstC += count;
} }
@ -1091,7 +1139,7 @@ void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
if ((count = range.fCount2) > 0) { if ((count = range.fCount2) > 0) {
dither_memset16(dstC, dither_memset16(dstC,
cache[toggle + range.fV1], cache[toggle + range.fV1],
cache[(toggle ^ Gradient_Shader::kToggleMask16) + range.fV1], cache[(toggle ^ Gradient_Shader::kDitherStride16) + range.fV1],
count); count);
} }
} }
@ -1103,10 +1151,10 @@ void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
do { do {
unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift, unsigned fi = mirror_bits(fx >> Gradient_Shader::kCache16Shift,
Gradient_Shader::kCache16Bits); Gradient_Shader::kCache16Bits);
SkASSERT(fi <= Gradient_Shader::kCache16Mask); SkASSERT(fi < Gradient_Shader::kCache16Count);
fx += dx; fx += dx;
*dstC++ = cache[toggle + fi]; *dstC++ = cache[toggle + fi];
toggle ^= Gradient_Shader::kToggleMask16; toggle ^= Gradient_Shader::kDitherStride16;
} while (--count != 0); } while (--count != 0);
} }
@ -1118,10 +1166,10 @@ void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
do { do {
unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift, unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift,
Gradient_Shader::kCache16Bits); Gradient_Shader::kCache16Bits);
SkASSERT(fi <= Gradient_Shader::kCache16Mask); SkASSERT(fi < Gradient_Shader::kCache16Count);
fx += dx; fx += dx;
*dstC++ = cache[toggle + fi]; *dstC++ = cache[toggle + fi];
toggle ^= Gradient_Shader::kToggleMask16; toggle ^= Gradient_Shader::kDitherStride16;
} while (--count != 0); } while (--count != 0);
} }
} }
@ -1134,7 +1182,7 @@ void Linear_Gradient::shadeSpan16(int x, int y,
SkMatrix::MapXYProc dstProc = fDstToIndexProc; SkMatrix::MapXYProc dstProc = fDstToIndexProc;
TileProc proc = fTileProc; TileProc proc = fTileProc;
const uint16_t* SK_RESTRICT cache = this->getCache16(); const uint16_t* SK_RESTRICT cache = this->getCache16();
int toggle = ((x ^ y) & 1) << kCache16Bits; int toggle = ((x ^ y) & 1) * kDitherStride16;
if (fDstToIndexClass != kPerspective_MatrixClass) { if (fDstToIndexClass != kPerspective_MatrixClass) {
dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
@ -1171,7 +1219,7 @@ void Linear_Gradient::shadeSpan16(int x, int y,
int index = fi >> kCache16Shift; int index = fi >> kCache16Shift;
*dstC++ = cache[toggle + index]; *dstC++ = cache[toggle + index];
toggle ^= Gradient_Shader::kToggleMask16; toggle ^= Gradient_Shader::kDitherStride16;
dstX += SK_Scalar1; dstX += SK_Scalar1;
} while (--count != 0); } while (--count != 0);
@ -1263,7 +1311,7 @@ void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
fx += dx; fx += dx;
*dstC++ = cache[toggle + *dstC++ = cache[toggle +
(sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)]; (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
toggle ^= Gradient_Shader::kToggleMask16; toggle ^= Gradient_Shader::kDitherStride16;
} while (--count != 0); } while (--count != 0);
} else { } else {
do { do {
@ -1275,7 +1323,7 @@ void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
fy += dy; fy += dy;
*dstC++ = cache[toggle + *dstC++ = cache[toggle +
(sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)]; (sqrt_table[fi] >> Gradient_Shader::kSqrt16Shift)];
toggle ^= Gradient_Shader::kToggleMask16; toggle ^= Gradient_Shader::kDitherStride16;
} while (--count != 0); } while (--count != 0);
} }
} }
@ -1298,7 +1346,7 @@ void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
unsigned fi = mirror_tileproc(dist); unsigned fi = mirror_tileproc(dist);
SkASSERT(fi <= 0xFFFF); SkASSERT(fi <= 0xFFFF);
*dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)]; *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
toggle ^= Gradient_Shader::kToggleMask16; toggle ^= Gradient_Shader::kDitherStride16;
sfx += sdx; sfx += sdx;
sfy += sdy; sfy += sdy;
} while (--count != 0); } while (--count != 0);
@ -1319,7 +1367,7 @@ void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
fx += dx; fx += dx;
fy += dy; fy += dy;
*dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)]; *dstC++ = cache[toggle + (fi >> Gradient_Shader::kCache16Shift)];
toggle ^= Gradient_Shader::kToggleMask16; toggle ^= Gradient_Shader::kDitherStride16;
} while (--count != 0); } while (--count != 0);
} }
@ -1350,7 +1398,7 @@ public:
SkMatrix::MapXYProc dstProc = fDstToIndexProc; SkMatrix::MapXYProc dstProc = fDstToIndexProc;
TileProc proc = fTileProc; TileProc proc = fTileProc;
const uint16_t* SK_RESTRICT cache = this->getCache16(); const uint16_t* SK_RESTRICT cache = this->getCache16();
int toggle = ((x ^ y) & 1) << kCache16Bits; int toggle = ((x ^ y) & 1) * kDitherStride16;
if (fDstToIndexClass != kPerspective_MatrixClass) { if (fDstToIndexClass != kPerspective_MatrixClass) {
dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
@ -1389,7 +1437,7 @@ public:
int index = fi >> (16 - kCache16Bits); int index = fi >> (16 - kCache16Bits);
*dstC++ = cache[toggle + index]; *dstC++ = cache[toggle + index];
toggle ^= (1 << kCache16Bits); toggle ^= kDitherStride16;
dstX += SK_Scalar1; dstX += SK_Scalar1;
} while (--count != 0); } while (--count != 0);
@ -1405,8 +1453,8 @@ public:
this->commonAsABitmap(bitmap); this->commonAsABitmap(bitmap);
} }
if (matrix) { if (matrix) {
matrix->setScale(SkIntToScalar(kCache32Count), matrix->setScale(SkIntToScalar(kGradient32Length),
SkIntToScalar(kCache32Count)); SkIntToScalar(kGradient32Length));
matrix->preConcat(fPtsToUnit); matrix->preConcat(fPtsToUnit);
} }
if (xy) { if (xy) {
@ -1506,7 +1554,7 @@ void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
SkFixed fy = SkScalarToFixed(sfy) >> 1; SkFixed fy = SkScalarToFixed(sfy) >> 1;
SkFixed dy = SkScalarToFixed(sdy) >> 1; SkFixed dy = SkScalarToFixed(sdy) >> 1;
if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) { if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
sk_memset32(dstC, cache[Gradient_Shader::kCache32Count - 1], count); sk_memset32(dstC, cache[Gradient_Shader::kGradient32Length], count);
} else if ((count > 4) && } else if ((count > 4) &&
no_need_for_radial_pin(fx, dx, fy, dy, count)) { no_need_for_radial_pin(fx, dx, fy, dy, count)) {
unsigned fi; unsigned fi;
@ -2338,7 +2386,7 @@ void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
SkMatrix::MapXYProc proc = fDstToIndexProc; SkMatrix::MapXYProc proc = fDstToIndexProc;
const SkMatrix& matrix = fDstToIndex; const SkMatrix& matrix = fDstToIndex;
const uint16_t* SK_RESTRICT cache = this->getCache16(); const uint16_t* SK_RESTRICT cache = this->getCache16();
int toggle = ((x ^ y) & 1) << kCache16Bits; int toggle = ((x ^ y) & 1) * kDitherStride16;
SkPoint srcPt; SkPoint srcPt;
if (fDstToIndexClass != kPerspective_MatrixClass) { if (fDstToIndexClass != kPerspective_MatrixClass) {
@ -2362,7 +2410,7 @@ void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
for (; count > 0; --count) { for (; count > 0; --count) {
int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits); int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
*dstC++ = cache[toggle + index]; *dstC++ = cache[toggle + index];
toggle ^= (1 << kCache16Bits); toggle ^= kDitherStride16;
fx += dx; fx += dx;
fy += dy; fy += dy;
} }
@ -2374,7 +2422,7 @@ void Sweep_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
int index = SkATan2_255(srcPt.fY, srcPt.fX); int index = SkATan2_255(srcPt.fY, srcPt.fX);
index >>= (8 - kCache16Bits); index >>= (8 - kCache16Bits);
*dstC++ = cache[toggle + index]; *dstC++ = cache[toggle + index];
toggle ^= (1 << kCache16Bits); toggle ^= kDitherStride16;
} }
} }
} }

View File

@ -7,7 +7,9 @@
*/ */
#include "Test.h" #include "Test.h"
#include "SkColor.h" #include "SkColor.h"
#include "SkColorPriv.h"
#include "SkMath.h" #include "SkMath.h"
#include "SkRandom.h"
#include "SkUnPreMultiply.h" #include "SkUnPreMultiply.h"
static void test_premul(skiatest::Reporter* reporter) { static void test_premul(skiatest::Reporter* reporter) {
@ -31,9 +33,49 @@ static void test_premul(skiatest::Reporter* reporter) {
} }
} }
/**
This test fails: SkFourByteInterp does *not* preserve opaque destinations.
SkAlpha255To256 implemented as (alpha + 1) is faster than
(alpha + (alpha >> 7)), but inaccurate, and Skia intends to phase it out.
*/
/*
static void test_interp(skiatest::Reporter* reporter) {
SkRandom r;
U8CPU a0 = 0;
U8CPU a255 = 255;
for (int i = 0; i < 200; i++) {
SkColor colorSrc = r.nextU();
SkColor colorDst = r.nextU();
SkPMColor src = SkPreMultiplyColor(colorSrc);
SkPMColor dst = SkPreMultiplyColor(colorDst);
REPORTER_ASSERT(reporter, SkFourByteInterp(src, dst, a0) == dst);
REPORTER_ASSERT(reporter, SkFourByteInterp(src, dst, a255) == src);
}
}
*/
static void test_fast_interp(skiatest::Reporter* reporter) {
SkRandom r;
U8CPU a0 = 0;
U8CPU a255 = 255;
for (int i = 0; i < 200; i++) {
SkColor colorSrc = r.nextU();
SkColor colorDst = r.nextU();
SkPMColor src = SkPreMultiplyColor(colorSrc);
SkPMColor dst = SkPreMultiplyColor(colorDst);
REPORTER_ASSERT(reporter, SkFastFourByteInterp(src, dst, a0) == dst);
REPORTER_ASSERT(reporter, SkFastFourByteInterp(src, dst, a255) == src);
}
}
static void TestColor(skiatest::Reporter* reporter) { static void TestColor(skiatest::Reporter* reporter) {
test_premul(reporter); test_premul(reporter);
//test_interp(reporter);
test_fast_interp(reporter);
} }
#include "TestClassDef.h" #include "TestClassDef.h"