diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/SkMaskGamma.cpp | 91 | ||||
-rw-r--r-- | src/core/SkMaskGamma.h | 211 | ||||
-rw-r--r-- | src/core/SkPaint.cpp | 197 | ||||
-rw-r--r-- | src/core/SkScalerContext.cpp | 167 |
4 files changed, 506 insertions, 160 deletions
diff --git a/src/core/SkMaskGamma.cpp b/src/core/SkMaskGamma.cpp new file mode 100644 index 0000000000..47903fbdbb --- /dev/null +++ b/src/core/SkMaskGamma.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkTypes.h" + +#include "SkColor.h" +#include "SkFloatingPoint.h" +#include "SkMaskGamma.h" + +SkScalar SkSRGBLuminance::toLuma(SkScalar luminance) const { + //The magic numbers are derived from the sRGB specification. + //See http://www.color.org/chardata/rgb/srgb.xalter . + if (luminance <= SkFloatToScalar(0.04045f)) { + return luminance / SkFloatToScalar(12.92f); + } + return SkScalarPow((luminance + SkFloatToScalar(0.055f)) / SkFloatToScalar(1.055f), + SkFloatToScalar(2.4f)); +} + +SkScalar SkSRGBLuminance::fromLuma(SkScalar luma) const { + //The magic numbers are derived from the sRGB specification. + //See http://www.color.org/chardata/rgb/srgb.xalter . + if (luma <= SkFloatToScalar(0.0031308f)) { + return luma * SkFloatToScalar(12.92f); + } + return SkFloatToScalar(1.055f) * SkScalarPow(luma, SkScalarInvert(SkFloatToScalar(2.4f))) + - SkFloatToScalar(0.055f); +} + +SkGammaLuminance::SkGammaLuminance(SkScalar gamma) + : fGamma(gamma) + , fGammaInverse(SkScalarInvert(gamma)) { +} + +float SkGammaLuminance::toLuma(SkScalar luminance) const { + return SkScalarPow(luminance, fGamma); +} + +float SkGammaLuminance::fromLuma(SkScalar luma) const { + return SkScalarPow(luma, fGammaInverse); +} + +static float apply_contrast(float srca, float contrast) { + return srca + ((1.0f - srca) * contrast * srca); +} + +void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast, + const SkColorSpaceLuminance& srcConvert, + const SkColorSpaceLuminance& dstConvert) { + const float src = (float)srcI / 255.0f; + const float linSrc = srcConvert.toLuma(src); + //Guess at the dst. + const float linDst = 1.0f - linSrc; + const float dst = dstConvert.fromLuma(linDst); + + //Contrast value tapers off to 0 as the src luminance becomes white + const float adjustedContrast = SkScalarToFloat(contrast) * linDst; + const float step = 1.0f / 255.0f; + + //Remove discontinuity and instability when src is close to dst. + //The value 1/256 is arbitrary and appears to contain the instability. + if (fabs(src - dst) < (1.0f / 256.0f)) { + float rawSrca = 0.0f; + for (int i = 0; i < 256; ++i, rawSrca += step) { + float srca = apply_contrast(rawSrca, adjustedContrast); + table[i] = SkToU8(sk_float_round2int(255.0f * srca)); + } + } else { + float rawSrca = 0.0f; + for (int i = 0; i < 256; ++i, rawSrca += step) { + float srca = apply_contrast(rawSrca, adjustedContrast); + SkASSERT(srca <= 1.0f); + float dsta = 1.0f - srca; + + //Calculate the output we want. + float linOut = (linSrc * srca + dsta * linDst); + SkASSERT(linOut <= 1.0f); + float out = dstConvert.fromLuma(linOut); + + //Undo what the blit blend will do. + float result = (out - dst) / (src - dst); + SkASSERT(sk_float_round2int(255.0f * result) <= 255); + + table[i] = SkToU8(sk_float_round2int(255.0f * result)); + } + } +} diff --git a/src/core/SkMaskGamma.h b/src/core/SkMaskGamma.h new file mode 100644 index 0000000000..e8fa703f36 --- /dev/null +++ b/src/core/SkMaskGamma.h @@ -0,0 +1,211 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMaskGamma_DEFINED +#define SkMaskGamma_DEFINED + +#include "SkTypes.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkRefCnt.h" + +/** + * SkColorSpaceLuminance is used to convert luminances to and from linear and + * perceptual color spaces. + * + * Luma is used to specify a linear luminance value [0.0, 1.0]. + * Luminance is used to specify a luminance value in an arbitrary color space [0.0, 1.0]. + */ +class SkColorSpaceLuminance : SkNoncopyable { +public: + /** Converts a color component luminance in the color space to a linear luma. */ + virtual SkScalar toLuma(SkScalar luminance) const = 0; + /** Converts a linear luma to a color component luminance in the color space. */ + virtual SkScalar fromLuma(SkScalar luma) const = 0; + + /** Converts a color to a luminance value. */ + U8CPU computeLuminance(SkColor c) const { + SkScalar r = toLuma(SkIntToScalar(SkColorGetR(c)) / 255); + SkScalar g = toLuma(SkIntToScalar(SkColorGetG(c)) / 255); + SkScalar b = toLuma(SkIntToScalar(SkColorGetB(c)) / 255); + SkScalar luma = r * SkFloatToScalar(SK_LUM_COEFF_R) + + g * SkFloatToScalar(SK_LUM_COEFF_G) + + b * SkFloatToScalar(SK_LUM_COEFF_B); + SkASSERT(luma <= SK_Scalar1); + return SkScalarRoundToInt(fromLuma(luma) * 255); + } +}; + +class SkSRGBLuminance : public SkColorSpaceLuminance { +public: + SkScalar toLuma(SkScalar luminance) const SK_OVERRIDE; + SkScalar fromLuma(SkScalar luma) const SK_OVERRIDE; +}; + +class SkGammaLuminance : public SkColorSpaceLuminance { +public: + SkGammaLuminance(SkScalar gamma); + SkScalar toLuma(SkScalar luminance) const SK_OVERRIDE; + SkScalar fromLuma(SkScalar luma) const SK_OVERRIDE; +private: + SkScalar fGamma; + SkScalar fGammaInverse; +}; + +///@{ +/** + * Scales base <= 2^N-1 to 2^8-1 + * @param N [1, 8] the number of bits used by base. + * @param base the number to be scaled to [0, 255]. + */ +template<U8CPU N> static inline U8CPU sk_t_scale255(U8CPU base) { + base <<= (8 - N); + U8CPU lum = base; + for (unsigned int i = N; i < 8; i += N) { + lum |= base >> i; + } + return lum; +} +template<> /*static*/ inline U8CPU sk_t_scale255<1>(U8CPU base) { + return base * 0xFF; +} +template<> /*static*/ inline U8CPU sk_t_scale255<2>(U8CPU base) { + return base * 0x55; +} +template<> /*static*/ inline U8CPU sk_t_scale255<4>(U8CPU base) { + return base * 0x11; +} +template<> /*static*/ inline U8CPU sk_t_scale255<8>(U8CPU base) { + return base; +} +///@} + +template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskPreBlend; + +void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast, + const SkColorSpaceLuminance& srcConvert, + const SkColorSpaceLuminance& dstConvert); + +/** + * A regular mask contains linear alpha values. A gamma correcting mask + * contains non-linear alpha values in an attempt to create gamma correct blits + * in the presence of a gamma incorrect (linear) blend in the blitter. + * + * SkMaskGamma creates and maintains tables which convert linear alpha values + * to gamma correcting alpha values. + * @param R The number of luminance bits to use [1, 8] from the red channel. + * @param G The number of luminance bits to use [1, 8] from the green channel. + * @param B The number of luminance bits to use [1, 8] from the blue channel. + */ +template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskGamma : public SkRefCnt { +public: + /** + * Creates tables to convert linear alpha values to gamma correcting alpha + * values. + * + * @param contrast A value in the range [0.0, 1.0] which indicates the + * amount of artificial contrast to add. + * @param paint The color space in which the paint color was chosen. + * @param device The color space of the target device. + */ + SkTMaskGamma(SkScalar contrast, + const SkColorSpaceLuminance& paint, + const SkColorSpaceLuminance& device) { + for (U8CPU i = 0; i < (1 << kLuminanceBits_Max); ++i) { + U8CPU lum = sk_t_scale255<kLuminanceBits_Max>(i); + SkTMaskGamma_build_correcting_lut(fGammaTables[i], lum, contrast, paint, device); + } + } + + /** Given a color, returns the closest cannonical color. */ + SkColor cannonicalColor(SkColor color) { + return SkColorSetRGB( + sk_t_scale255<kLuminanceBits_R>(SkColorGetR(color) >> (8 - kLuminanceBits_R)), + sk_t_scale255<kLuminanceBits_G>(SkColorGetG(color) >> (8 - kLuminanceBits_G)), + sk_t_scale255<kLuminanceBits_B>(SkColorGetB(color) >> (8 - kLuminanceBits_B))); + } + + /** The type of the mask pre-blend which will be returned from preBlend(SkColor). */ + typedef SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> PreBlend; + + /** + * Provides access to the tables appropriate for converting linear alpha + * values into gamma correcting alpha values when drawing the given color + * through the mask. The destination color will be approximated. + */ + PreBlend preBlend(SkColor color); + +private: + enum LuminanceBits { + kLuminanceBits_R = R_LUM_BITS, + kLuminanceBits_G = G_LUM_BITS, + kLuminanceBits_B = B_LUM_BITS, + kLuminanceBits_Max = B_LUM_BITS > (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS) + ? B_LUM_BITS + : (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS) + }; + uint8_t fGammaTables[1 << kLuminanceBits_Max][256]; +}; + +/** + * SkTMaskPreBlend is a tear-off of SkTMaskGamma. It provides the tables to + * convert a linear alpha value for a given channel to a gamma correcting alpha + * value for that channel. This class is immutable. + */ +template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskPreBlend { +private: + SkTMaskPreBlend(SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>* parent, + const uint8_t* r, + const uint8_t* g, + const uint8_t* b) + : fParent(parent), fR(r), fG(g), fB(b) { + parent->ref(); + } + SkAutoTUnref<SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> > fParent; + friend class SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>; +public: + /** + * This copy contructor exists for correctness, but should never be called + * when return value optimization is enabled. + */ + SkTMaskPreBlend(const SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>& that) + : fParent(that.fParent.get()), fR(that.fR), fG(that.fG), fB(that.fB) { + fParent.get()->ref(); + } + ~SkTMaskPreBlend() { } + const uint8_t* fR; + const uint8_t* fG; + const uint8_t* fB; +}; + +template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> +SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> +SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>::preBlend(SkColor color) { + return SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>( + this, + fGammaTables[SkColorGetR(color) >> (8 - kLuminanceBits_Max)], + fGammaTables[SkColorGetG(color) >> (8 - kLuminanceBits_Max)], + fGammaTables[SkColorGetB(color) >> (8 - kLuminanceBits_Max)]); +} + +///@{ +/** + * If APPLY_LUT is false, returns component unchanged. + * If APPLY_LUT is true, returns lut[component]. + * @param APPLY_LUT whether or not the look-up table should be applied to component. + * @component the initial component. + * @lut a look-up table which transforms the component. + */ +template<bool APPLY_LUT> static inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t*) { + return component; +} +template<> /*static*/ inline U8CPU sk_apply_lut_if<true>(U8CPU component, const uint8_t* lut) { + return lut[component]; +} +///@} + +#endif diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp index f240737f7e..04a8f5a756 100644 --- a/src/core/SkPaint.cpp +++ b/src/core/SkPaint.cpp @@ -12,6 +12,7 @@ #include "SkFontHost.h" #include "SkImageFilter.h" #include "SkMaskFilter.h" +#include "SkMaskGamma.h" #include "SkPathEffect.h" #include "SkRasterizer.h" #include "SkShader.h" @@ -1432,7 +1433,6 @@ static bool justAColor(const SkPaint& paint, SkColor* color) { return true; } -#ifdef SK_USE_COLOR_LUMINANCE static SkColor computeLuminanceColor(const SkPaint& paint) { SkColor c; if (!justAColor(paint, &c)) { @@ -1443,53 +1443,6 @@ static SkColor computeLuminanceColor(const SkPaint& paint) { #define assert_byte(x) SkASSERT(0 == ((x) >> 8)) -static U8CPU reduce_lumbits(U8CPU x) { - static const uint8_t gReduceBits[] = { - 0x0, 0x55, 0xAA, 0xFF - }; - assert_byte(x); - return gReduceBits[x >> 6]; -} - -static unsigned computeLuminance(SkColor c) { - int r = SkColorGetR(c); - int g = SkColorGetG(c); - int b = SkColorGetB(c); - // compute luminance - // R=0.2126 G=0.7152 B=0.0722 - // scaling by 127 yields 27, 92, 9 - int luminance = r * 27 + g * 92 + b * 9; - luminance >>= 7; - assert_byte(luminance); - return luminance; -} - -#else -// returns 0..kLuminance_Max -static unsigned computeLuminance(const SkPaint& paint) { - SkColor c; - if (justAColor(paint, &c)) { - int r = SkColorGetR(c); - int g = SkColorGetG(c); - int b = SkColorGetB(c); - // compute luminance - // R=0.2126 G=0.7152 B=0.0722 - // scaling by 127 yields 27, 92, 9 -#if 1 - int luminance = r * 27 + g * 92 + b * 9; - luminance >>= 15 - SkScalerContext::kLuminance_Bits; -#else - int luminance = r * 2 + g * 5 + b * 1; - luminance >>= 11 - SkScalerContext::kLuminance_Bits; -#endif - SkASSERT(luminance <= SkScalerContext::kLuminance_Max); - return luminance; - } - // if we're not a single color, return the middle of the luminance range - return SkScalerContext::kLuminance_Max >> 1; -} -#endif - // Beyond this size, LCD doesn't appreciably improve quality, but it always // cost more RAM and draws slower, so we set a cap. #ifndef SK_MAX_SIZE_FOR_LCDTEXT @@ -1518,6 +1471,20 @@ static SkScalar sk_relax(SkScalar x) { #endif } +//#define SK_GAMMA_SRGB +#ifndef SK_GAMMA_CONTRAST + /** + * A value of 0.5 for SK_GAMMA_CONTRAST appears to be a good compromise. + * With lower values small text appears washed out (though correctly so). + * With higher values lcd fringing is worse and the smoothing effect of + * partial coverage is diminished. + */ + #define SK_GAMMA_CONTRAST (0.5f) +#endif +#ifndef SK_GAMMA_EXPONENT + #define SK_GAMMA_EXPONENT (2.2f) +#endif + void SkScalerContext::MakeRec(const SkPaint& paint, const SkMatrix* deviceMatrix, Rec* rec) { SkASSERT(deviceMatrix == NULL || !deviceMatrix->hasPerspective()); @@ -1619,12 +1586,17 @@ void SkScalerContext::MakeRec(const SkPaint& paint, // these modify fFlags, so do them after assigning fFlags rec->setHinting(computeHinting(paint)); -#ifdef SK_USE_COLOR_LUMINANCE + rec->setLuminanceColor(computeLuminanceColor(paint)); +#ifdef SK_GAMMA_SRGB + rec->setDeviceGamma(0); + rec->setPaintGamma(0); #else - rec->setLuminanceBits(computeLuminance(paint)); + rec->setDeviceGamma(SkFloatToScalar(SK_GAMMA_EXPONENT)); + rec->setPaintGamma(SkFloatToScalar(SK_GAMMA_EXPONENT)); #endif - + rec->setContrast(SkFloatToScalar(SK_GAMMA_CONTRAST)); + /* Allow the fonthost to modify our rec before we use it as a key into the cache. This way if we're asking for something that they will ignore, they can modify our rec up front, so we don't create duplicate cache @@ -1636,46 +1608,111 @@ void SkScalerContext::MakeRec(const SkPaint& paint, } /** - * We ensure that the rec is self-consistent and efficient (where possible) + * In order to call cachedDeviceLuminance, cachedPaintLuminance, or + * cachedMaskGamma the caller must hold the gMaskGammaCacheMutex and continue + * to hold it until the returned pointer is refed or forgotten. */ -void SkScalerContext::PostMakeRec(SkScalerContext::Rec* rec) { +SK_DECLARE_STATIC_MUTEX(gMaskGammaCacheMutex); +/** + * The caller must hold the gMaskGammaCacheMutex and continue to hold it until + * the returned SkColorSpaceLuminance pointer is refed or forgotten. + */ +static SkColorSpaceLuminance* cachedDeviceLuminance(SkScalar gammaExponent) { + static SkColorSpaceLuminance* gDeviceLuminance = NULL; + static SkScalar gGammaExponent = SK_ScalarMin; + if (gGammaExponent != gammaExponent) { + if (0 == gammaExponent) { + gDeviceLuminance = SkNEW(SkSRGBLuminance); + } else { + gDeviceLuminance = SkNEW_ARGS(SkGammaLuminance, (gammaExponent)); + } + gGammaExponent = gammaExponent; + } + return gDeviceLuminance; +} + +/** + * The caller must hold the gMaskGammaCacheMutex and continue to hold it until + * the returned SkColorSpaceLuminance pointer is refed or forgotten. + */ +static SkColorSpaceLuminance* cachedPaintLuminance(SkScalar gammaExponent) { + static SkColorSpaceLuminance* gPaintLuminance = NULL; + static SkScalar gGammaExponent = SK_ScalarMin; + if (gGammaExponent != gammaExponent) { + if (0 == gammaExponent) { + gPaintLuminance = SkNEW(SkSRGBLuminance); + } else { + gPaintLuminance = SkNEW_ARGS(SkGammaLuminance, (gammaExponent)); + } + gGammaExponent = gammaExponent; + } + return gPaintLuminance; +} + +/** + * The caller must hold the gMaskGammaCacheMutex and continue to hold it until + * the returned SkMaskGamma pointer is refed or forgotten. + */ +static SkMaskGamma* cachedMaskGamma(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma) { + static SkMaskGamma* gMaskGamma = NULL; + static SkScalar gContrast = SK_ScalarMin; + static SkScalar gPaintGamma = SK_ScalarMin; + static SkScalar gDeviceGamma = SK_ScalarMin; + if (gContrast != contrast || gPaintGamma != paintGamma || gDeviceGamma != deviceGamma) { + SkSafeUnref(gMaskGamma); + SkColorSpaceLuminance* paintLuminance = cachedPaintLuminance(paintGamma); + SkColorSpaceLuminance* deviceLuminance = cachedDeviceLuminance(deviceGamma); + gMaskGamma = SkNEW_ARGS(SkMaskGamma, (contrast, *paintLuminance, *deviceLuminance)); + gContrast = contrast; + gPaintGamma = paintGamma; + gDeviceGamma = deviceGamma; + } + return gMaskGamma; +} + +/** + * We ensure that the rec is self-consistent and efficient (where possible) + */ +void SkScalerContext::PostMakeRec(const SkPaint& paint, SkScalerContext::Rec* rec) { /** * If we're asking for A8, we force the colorlum to be gray, since that - * that limits the number of unique entries, and the scaler will only - * look at the lum of one of them. + * limits the number of unique entries, and the scaler will only look at + * the lum of one of them. */ switch (rec->fMaskFormat) { case SkMask::kLCD16_Format: case SkMask::kLCD32_Format: { -#ifdef SK_USE_COLOR_LUMINANCE // filter down the luminance color to a finite number of bits - SkColor c = rec->getLuminanceColor(); - c = SkColorSetRGB(reduce_lumbits(SkColorGetR(c)), - reduce_lumbits(SkColorGetG(c)), - reduce_lumbits(SkColorGetB(c))); - rec->setLuminanceColor(c); -#endif + SkColor color = rec->getLuminanceColor(); + SkAutoMutexAcquire ama(gMaskGammaCacheMutex); + SkMaskGamma* maskGamma = cachedMaskGamma(rec->getContrast(), + rec->getPaintGamma(), + rec->getDeviceGamma()); + rec->setLuminanceColor(maskGamma->cannonicalColor(color)); break; } case SkMask::kA8_Format: { -#ifdef SK_USE_COLOR_LUMINANCE // filter down the luminance to a single component, since A8 can't // use per-component information - unsigned lum = computeLuminance(rec->getLuminanceColor()); + + SkColor color = rec->getLuminanceColor(); + SkAutoMutexAcquire ama(gMaskGammaCacheMutex); + U8CPU lum = cachedPaintLuminance(rec->getPaintGamma())->computeLuminance(color); + // HACK: Prevents green from being pre-blended as white. + lum -= ((255 - lum) * lum) / 255; + // reduce to our finite number of bits - lum = reduce_lumbits(lum); - rec->setLuminanceColor(SkColorSetRGB(lum, lum, lum)); -#endif + SkMaskGamma* maskGamma = cachedMaskGamma(rec->getContrast(), + rec->getPaintGamma(), + rec->getDeviceGamma()); + color = SkColorSetRGB(lum, lum, lum); + rec->setLuminanceColor(maskGamma->cannonicalColor(color)); break; } case SkMask::kBW_Format: // No need to differentiate gamma if we're BW -#ifdef SK_USE_COLOR_LUMINANCE rec->setLuminanceColor(0); -#else - rec->setLuminanceBits(0); -#endif break; } } @@ -1700,11 +1737,7 @@ void SkPaint::descriptorProc(const SkMatrix* deviceMatrix, SkScalerContext::MakeRec(*this, deviceMatrix, &rec); if (ignoreGamma) { -#ifdef SK_USE_COLOR_LUMINANCE rec.setLuminanceColor(0); -#else - rec.setLuminanceBits(0); -#endif } size_t descSize = sizeof(rec); @@ -1739,7 +1772,7 @@ void SkPaint::descriptorProc(const SkMatrix* deviceMatrix, /////////////////////////////////////////////////////////////////////////// // Now that we're done tweaking the rec, call the PostMakeRec cleanup - SkScalerContext::PostMakeRec(&rec); + SkScalerContext::PostMakeRec(*this, &rec); descSize += SkDescriptor::ComputeOverhead(entryCount); @@ -1814,6 +1847,18 @@ SkGlyphCache* SkPaint::detachCache(const SkMatrix* deviceMatrix) const { return cache; } +/** + * Expands fDeviceGamma, fPaintGamma, fContrast, and fLumBits into a mask pre-blend. + */ +//static +SkMaskGamma::PreBlend SkScalerContext::GetMaskPreBlend(const SkScalerContext::Rec& rec) { + SkAutoMutexAcquire ama(gMaskGammaCacheMutex); + SkMaskGamma* maskGamma = cachedMaskGamma(rec.getContrast(), + rec.getPaintGamma(), + rec.getDeviceGamma()); + return maskGamma->preBlend(rec.getLuminanceColor()); +} + /////////////////////////////////////////////////////////////////////////////// #include "SkStream.h" diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp index 7c26921bfb..9d99713c40 100644 --- a/src/core/SkScalerContext.cpp +++ b/src/core/SkScalerContext.cpp @@ -14,6 +14,7 @@ #include "SkFontHost.h" #include "SkGlyph.h" #include "SkMaskFilter.h" +#include "SkMaskGamma.h" #include "SkOrderedReadBuffer.h" #include "SkPathEffect.h" #include "SkRasterizer.h" @@ -74,16 +75,16 @@ static SkFlattenable* load_flattenable(const SkDescriptor* desc, uint32_t tag) { } SkScalerContext::SkScalerContext(const SkDescriptor* desc) - : fPathEffect(NULL), fMaskFilter(NULL) + : fRec(*static_cast<const Rec*>(desc->findEntry(kRec_SkDescriptorTag, NULL))) + , fBaseGlyphCount(0) + , fPathEffect(static_cast<SkPathEffect*>(load_flattenable(desc, kPathEffect_SkDescriptorTag))) + , fMaskFilter(static_cast<SkMaskFilter*>(load_flattenable(desc, kMaskFilter_SkDescriptorTag))) + , fRasterizer(static_cast<SkRasterizer*>(load_flattenable(desc, kRasterizer_SkDescriptorTag))) + // initialize based on our settings. subclasses can also force this + , fGenerateImageFromPath(fRec.fFrameWidth > 0 || fPathEffect != NULL || fRasterizer != NULL) + , fNextContext(NULL) + , fMaskPreBlend(SkScalerContext::GetMaskPreBlend(fRec)) { - fBaseGlyphCount = 0; - fNextContext = NULL; - - const Rec* rec = (const Rec*)desc->findEntry(kRec_SkDescriptorTag, NULL); - SkASSERT(rec); - - fRec = *rec; - #ifdef DUMP_REC desc->assertChecksum(); SkDebugf("SkScalarContext checksum %x count %d length %d\n", @@ -98,14 +99,6 @@ SkScalerContext::SkScalerContext(const SkDescriptor* desc) desc->findEntry(kPathEffect_SkDescriptorTag, NULL), desc->findEntry(kMaskFilter_SkDescriptorTag, NULL)); #endif - - fPathEffect = (SkPathEffect*)load_flattenable(desc, kPathEffect_SkDescriptorTag); - fMaskFilter = (SkMaskFilter*)load_flattenable(desc, kMaskFilter_SkDescriptorTag); - fRasterizer = (SkRasterizer*)load_flattenable(desc, kRasterizer_SkDescriptorTag); - - // initialize based on our settings. subclasses can also force this - fGenerateImageFromPath = fRec.fFrameWidth > 0 || fPathEffect != NULL || - fRasterizer != NULL; } SkScalerContext::~SkScalerContext() { @@ -342,77 +335,39 @@ SK_ERROR: glyph->fMaskFormat = fRec.fMaskFormat; } -#if 0 // UNUSED -static bool isLCD(const SkScalerContext::Rec& rec) { - return SkMask::kLCD16_Format == rec.fMaskFormat || - SkMask::kLCD32_Format == rec.fMaskFormat; -} -#endif - -#if 0 // UNUSED -static uint16_t a8_to_rgb565(unsigned a8) { - return SkPackRGB16(a8 >> 3, a8 >> 2, a8 >> 3); -} - -static void copyToLCD16(const SkBitmap& src, const SkMask& dst) { +template<bool APPLY_PREBLEND> +static void pack3xHToLCD16(const SkBitmap& src, const SkMask& dst, SkMaskGamma::PreBlend* maskPreBlend) { SkASSERT(SkBitmap::kA8_Config == src.config()); SkASSERT(SkMask::kLCD16_Format == dst.fFormat); - + const int width = dst.fBounds.width(); const int height = dst.fBounds.height(); - const uint8_t* srcP = src.getAddr8(0, 0); - size_t srcRB = src.rowBytes(); uint16_t* dstP = (uint16_t*)dst.fImage; size_t dstRB = dst.fRowBytes; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - dstP[x] = a8_to_rgb565(srcP[x]); - } - srcP += srcRB; - dstP = (uint16_t*)((char*)dstP + dstRB); - } -} -#endif - -#define SK_FREETYPE_LCD_LERP 160 - -static int lerp(int start, int end) { - SkASSERT((unsigned)SK_FREETYPE_LCD_LERP <= 256); - return start + ((end - start) * (SK_FREETYPE_LCD_LERP) >> 8); -} - -static uint16_t packLCD16(unsigned r, unsigned g, unsigned b) { - if (SK_FREETYPE_LCD_LERP) { - // want (a+b+c)/3, but we approx to avoid the divide - unsigned ave = (5 * (r + g + b) + g) >> 4; - r = lerp(r, ave); - g = lerp(g, ave); - b = lerp(b, ave); + + const uint8_t* maskPreBlendR = NULL; + const uint8_t* maskPreBlendG = NULL; + const uint8_t* maskPreBlendB = NULL; + if (APPLY_PREBLEND) { + maskPreBlendR = maskPreBlend->fR; + maskPreBlendG = maskPreBlend->fG; + maskPreBlendB = maskPreBlend->fB; } - return SkPackRGB16(r >> 3, g >> 2, b >> 3); -} -static void pack3xHToLCD16(const SkBitmap& src, const SkMask& dst) { - SkASSERT(SkBitmap::kA8_Config == src.config()); - SkASSERT(SkMask::kLCD16_Format == dst.fFormat); - - const int width = dst.fBounds.width(); - const int height = dst.fBounds.height(); - uint16_t* dstP = (uint16_t*)dst.fImage; - size_t dstRB = dst.fRowBytes; for (int y = 0; y < height; ++y) { const uint8_t* srcP = src.getAddr8(0, y); for (int x = 0; x < width; ++x) { - unsigned r = *srcP++; - unsigned g = *srcP++; - unsigned b = *srcP++; - dstP[x] = packLCD16(r, g, b); + U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendR); + U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendG); + U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendB); + dstP[x] = SkPack888ToRGB16(r, g, b); } dstP = (uint16_t*)((char*)dstP + dstRB); } } -static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst) { +template<bool APPLY_PREBLEND> +static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst, SkMaskGamma::PreBlend* maskPreBlend) { SkASSERT(SkBitmap::kA8_Config == src.config()); SkASSERT(SkMask::kLCD32_Format == dst.fFormat); @@ -420,20 +375,29 @@ static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst) { const int height = dst.fBounds.height(); SkPMColor* dstP = (SkPMColor*)dst.fImage; size_t dstRB = dst.fRowBytes; + + const uint8_t* maskPreBlendR = NULL; + const uint8_t* maskPreBlendG = NULL; + const uint8_t* maskPreBlendB = NULL; + if (APPLY_PREBLEND) { + maskPreBlendR = maskPreBlend->fR; + maskPreBlendG = maskPreBlend->fG; + maskPreBlendB = maskPreBlend->fB; + } + for (int y = 0; y < height; ++y) { const uint8_t* srcP = src.getAddr8(0, y); for (int x = 0; x < width; ++x) { - unsigned r = *srcP++; - unsigned g = *srcP++; - unsigned b = *srcP++; - unsigned a = SkMax32(SkMax32(r, g), b); - dstP[x] = SkPackARGB32(a, r, g, b); + U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendR); + U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendG); + U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendB); + dstP[x] = SkPackARGB32(0xFF, r, g, b); } dstP = (SkPMColor*)((char*)dstP + dstRB); } } -static void generateMask(const SkMask& mask, const SkPath& path) { +static void generateMask(const SkMask& mask, const SkPath& path, SkMaskGamma::PreBlend* maskPreBlend) { SkBitmap::Config config; SkPaint paint; @@ -493,10 +457,18 @@ static void generateMask(const SkMask& mask, const SkPath& path) { if (0 == dstRB) { switch (mask.fFormat) { case SkMask::kLCD16_Format: - pack3xHToLCD16(bm, mask); + if (maskPreBlend) { + pack3xHToLCD16<true>(bm, mask, maskPreBlend); + } else { + pack3xHToLCD16<false>(bm, mask, maskPreBlend); + } break; case SkMask::kLCD32_Format: - pack3xHToLCD32(bm, mask); + if (maskPreBlend) { + pack3xHToLCD32<true>(bm, mask, maskPreBlend); + } else { + pack3xHToLCD32<false>(bm, mask, maskPreBlend); + } break; default: SkDEBUGFAIL("bad format for copyback"); @@ -504,10 +476,23 @@ static void generateMask(const SkMask& mask, const SkPath& path) { } } +static void applyLUTToA8Glyph(const SkGlyph& glyph, const uint8_t* lut) { + uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage; + unsigned rowBytes = glyph.rowBytes(); + + for (int y = glyph.fHeight - 1; y >= 0; --y) { + for (int x = glyph.fWidth - 1; x >= 0; --x) { + dst[x] = lut[dst[x]]; + } + dst += rowBytes; + } +} + void SkScalerContext::getImage(const SkGlyph& origGlyph) { const SkGlyph* glyph = &origGlyph; SkGlyph tmpGlyph; - + SkMaskGamma::PreBlend* maskPreBlend = &fMaskPreBlend; + if (fMaskFilter) { // restore the prefilter bounds tmpGlyph.init(origGlyph.fID); @@ -523,6 +508,7 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) { SkASSERT(tmpGlyph.fWidth <= origGlyph.fWidth); SkASSERT(tmpGlyph.fHeight <= origGlyph.fHeight); glyph = &tmpGlyph; + maskPreBlend = NULL; } if (fGenerateImageFromPath) { @@ -542,11 +528,19 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) { SkMask::kJustRenderImage_CreateMode)) { return; } + //apply maskPreBlend to a8 (if not NULL) + if (maskPreBlend) { + applyLUTToA8Glyph(*glyph, maskPreBlend->fG); + } } else { - generateMask(mask, devPath); + generateMask(mask, devPath, maskPreBlend); + //apply maskPreBlend to a8 (if not NULL) -- already applied to lcd. + if (maskPreBlend && mask.fFormat == SkMask::kA8_Format) { + applyLUTToA8Glyph(*glyph, maskPreBlend->fG); + } } } else { - this->getGlyphContext(*glyph)->generateImage(*glyph); + this->getGlyphContext(*glyph)->generateImage(*glyph, maskPreBlend); } if (fMaskFilter) { @@ -581,6 +575,11 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) { dst += dstRB; } SkMask::FreeImage(dstM.fImage); + + /* Pre-blend is not currently applied to filtered text. + The primary filter is blur, for which contrast makes no sense, + and for which the destination guess error is more visible. */ + //applyLUTToA8Glyph(origGlyph, fMaskPreBlend.fG); } } } @@ -749,7 +748,7 @@ protected: virtual void generateMetrics(SkGlyph* glyph) { glyph->zeroMetrics(); } - virtual void generateImage(const SkGlyph& glyph) {} + virtual void generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) {} virtual void generatePath(const SkGlyph& glyph, SkPath* path) {} virtual void generateFontMetrics(SkPaint::FontMetrics* mx, SkPaint::FontMetrics* my) { |