From: bungeman@google.com Date: Mon, 30 Jul 2012 20:40:50 +0000 (+0000) Subject: Gamma correcting masks. X-Git-Tag: accepted/tizen/5.0/unified/20181102.025319~15379 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=97efada074e4806479f1350ab1508939c2fdcb53;p=platform%2Fupstream%2FlibSkiaSharp.git Gamma correcting masks. https://codereview.appspot.com/6244068/ git-svn-id: http://skia.googlecode.com/svn/trunk@4841 2bbb7eff-a529-9590-31e7-b0007b416f81 --- diff --git a/gyp/common.gypi b/gyp/common.gypi index 06db4a8..4b558f8 100644 --- a/gyp/common.gypi +++ b/gyp/common.gypi @@ -11,6 +11,10 @@ ], 'target_defaults': { + 'defines': [ + 'SK_GAMMA_SRGB', + 'SK_GAMMA_APPLY_TO_A8', + ], # Validate the 'skia_os' setting against 'OS', because only certain # combinations work. You should only override 'skia_os' for certain diff --git a/gyp/common_conditions.gypi b/gyp/common_conditions.gypi index 2f58c1b..6dcfd81 100644 --- a/gyp/common_conditions.gypi +++ b/gyp/common_conditions.gypi @@ -80,15 +80,13 @@ 'defines': [ 'SK_SAMPLES_FOR_X', 'SK_BUILD_FOR_UNIX', - 'SK_USE_COLOR_LUMINANCE', - 'SK_GAMMA_APPLY_TO_A8', ], 'configurations': { 'Debug': { 'cflags': ['-g'] }, 'Release': { - 'cflags': ['-O3'], + 'cflags': ['-O3 -g'], 'defines': [ 'NDEBUG' ], }, }, diff --git a/gyp/core.gyp b/gyp/core.gyp index b4753b8..2ad0da8 100644 --- a/gyp/core.gyp +++ b/gyp/core.gyp @@ -85,6 +85,8 @@ '../src/core/SkMallocPixelRef.cpp', '../src/core/SkMask.cpp', '../src/core/SkMaskFilter.cpp', + '../src/core/SkMaskGamma.cpp', + '../src/core/SkMaskGamma.h', '../src/core/SkMath.cpp', '../src/core/SkMatrix.cpp', '../src/core/SkMetaData.cpp', diff --git a/gyp/ports.gyp b/gyp/ports.gyp index 9436970..b9c1412 100644 --- a/gyp/ports.gyp +++ b/gyp/ports.gyp @@ -33,14 +33,12 @@ '../src/ports/SkTime_Unix.cpp', '../src/ports/SkTime_win.cpp', '../src/ports/SkXMLParser_empty.cpp', - '../src/ports/sk_predefined_gamma.h', ], 'conditions': [ [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris"]', { 'sources': [ '../src/ports/SkThread_pthread.cpp', '../src/ports/SkFontHost_FreeType.cpp', - '../src/ports/SkFontHost_gamma_none.cpp', '../src/ports/SkFontHost_linux.cpp', ], }], @@ -54,7 +52,6 @@ '../src/utils/mac/SkStream_mac.cpp', # '../src/ports/SkFontHost_FreeType.cpp', # '../src/ports/SkFontHost_freetype_mac.cpp', -# '../src/ports/SkFontHost_gamma_none.cpp', '../src/ports/SkThread_pthread.cpp', ], 'sources!': [ @@ -94,7 +91,6 @@ '../src/ports/SkDebug_android.cpp', '../src/ports/SkThread_pthread.cpp', '../src/ports/SkFontHost_android.cpp', - '../src/ports/SkFontHost_gamma.cpp', '../src/ports/SkFontHost_FreeType.cpp', '../src/ports/FontHostConfiguration_android.cpp', #TODO: include the ports/SkImageRef_ashmem.cpp for non-NDK builds diff --git a/include/core/SkColorPriv.h b/include/core/SkColorPriv.h index a2a9c58..073a4f5 100644 --- a/include/core/SkColorPriv.h +++ b/include/core/SkColorPriv.h @@ -18,6 +18,20 @@ #include "SkColor.h" #include "SkMath.h" +///@{ +/** See ITU-R Recommendation BT.709 at http://www.itu.int/rec/R-REC-BT.709/ .*/ +#define SK_ITU_BT709_LUM_COEFF_R (0.2126f) +#define SK_ITU_BT709_LUM_COEFF_G (0.7152f) +#define SK_ITU_BT709_LUM_COEFF_B (0.0722f) +///@} + +///@{ +/** A float value which specifies this channel's contribution to luminance. */ +#define SK_LUM_COEFF_R SK_ITU_BT709_LUM_COEFF_R +#define SK_LUM_COEFF_G SK_ITU_BT709_LUM_COEFF_G +#define SK_LUM_COEFF_B SK_ITU_BT709_LUM_COEFF_B +///@} + /** Turn 0..255 into 0..256 by adding 1 at the half-way point. Used to turn a byte into a scale value, so that we can say scale * value >> 8 instead of alpha * value / 255. diff --git a/include/core/SkFontHost.h b/include/core/SkFontHost.h index 31f628f..7e89ee3 100644 --- a/include/core/SkFontHost.h +++ b/include/core/SkFontHost.h @@ -227,15 +227,6 @@ public: /////////////////////////////////////////////////////////////////////////// - /** DEPRECATED -- only called by SkFontHost_FreeType internally - - Return NULL or a pointer to 256 bytes for the black (table[0]) and - white (table[1]) gamma tables. - */ - static void GetGammaTables(const uint8_t* tables[2]); - - /////////////////////////////////////////////////////////////////////////// - /** LCDs either have their color elements arranged horizontally or vertically. When rendering subpixel glyphs we need to know which way round they are. diff --git a/include/core/SkScalerContext.h b/include/core/SkScalerContext.h index ccc4fa9..adbdf8a 100644 --- a/include/core/SkScalerContext.h +++ b/include/core/SkScalerContext.h @@ -9,22 +9,27 @@ #define SkScalerContext_DEFINED #include "SkMask.h" +#include "SkMaskGamma.h" #include "SkMatrix.h" #include "SkPaint.h" #ifdef SK_BUILD_FOR_ANDROID - //For SkFontID - #include "SkTypeface.h" + //For SkFontID + #include "SkTypeface.h" #endif -//#define SK_USE_COLOR_LUMINANCE - struct SkGlyph; class SkDescriptor; class SkMaskFilter; class SkPathEffect; class SkRasterizer; +//The following typedef hides from the rest of the implementation the number of +//most significant bits to consider when creating mask gamma tables. Two bits +//per channel was chosen as a balance between fidelity (more bits) and cache +//sizes (fewer bits). +typedef SkTMaskGamma<2, 2, 2> SkMaskGamma; + class SkScalerContext { public: enum Flags { @@ -49,24 +54,11 @@ public: // Generate A8 from LCD source (for GDI), only meaningful if fMaskFormat is kA8 // Perhaps we can store this (instead) in fMaskFormat, in hight bit? kGenA8FromLCD_Flag = 0x0800, - -#ifdef SK_USE_COLOR_LUMINANCE - kLuminance_Bits = 3 -#else - // luminance : 0 for black text, kLuminance_Max for white text - kLuminance_Shift = 13, // shift to land in the high 3-bits of Flags - kLuminance_Bits = 3 // ensure Flags doesn't exceed 16bits -#endif }; // computed values enum { kHinting_Mask = kHintingBit1_Flag | kHintingBit2_Flag, -#ifdef SK_USE_COLOR_LUMINANCE -#else - kLuminance_Max = (1 << kLuminance_Bits) - 1, - kLuminance_Mask = kLuminance_Max << kLuminance_Shift -#endif }; struct Rec { @@ -75,9 +67,49 @@ public: SkScalar fTextSize, fPreScaleX, fPreSkewX; SkScalar fPost2x2[2][2]; SkScalar fFrameWidth, fMiterLimit; -#ifdef SK_USE_COLOR_LUMINANCE + + //These describe the parameters to create (uniquely identify) the pre-blend. uint32_t fLumBits; -#endif + uint8_t fDeviceGamma; //2.6, (0.0, 4.0) gamma, 0.0 for sRGB + uint8_t fPaintGamma; //2.6, (0.0, 4.0) gamma, 0.0 for sRGB + uint8_t fContrast; //0.8+1, [0.0, 1.0] artificial contrast + uint8_t fReservedAlign; + + SkScalar getDeviceGamma() const { + return SkIntToScalar(fDeviceGamma) / (1 << 6); + } + void setDeviceGamma(SkScalar dg) { + SkASSERT(0 <= dg && dg < SkIntToScalar(4)); + fDeviceGamma = SkScalarFloorToInt(dg * (1 << 6)); + } + + SkScalar getPaintGamma() const { + return SkIntToScalar(fPaintGamma) / (1 << 6); + } + void setPaintGamma(SkScalar pg) { + SkASSERT(0 <= pg && pg < SkIntToScalar(4)); + fPaintGamma = SkScalarFloorToInt(pg * (1 << 6)); + } + + SkScalar getContrast() const { + return SkIntToScalar(fContrast) / ((1 << 8) - 1); + } + void setContrast(SkScalar c) { + SkASSERT(0 <= c && c <= SK_Scalar1); + fContrast = SkScalarRoundToInt(c * ((1 << 8) - 1)); + } + + /** + * Causes the luminance color and contrast to be ignored, and the + * paint and device gamma to be effectively 1.0. + */ + void ignorePreBlend() { + setLuminanceColor(0x00000000); + setPaintGamma(SK_Scalar1); + setDeviceGamma(SK_Scalar1); + setContrast(0); + } + uint8_t fMaskFormat; uint8_t fStrokeJoin; uint16_t fFlags; @@ -102,8 +134,7 @@ public: SkMask::Format getFormat() const { return static_cast(fMaskFormat); } - -#ifdef SK_USE_COLOR_LUMINANCE + SkColor getLuminanceColor() const { return fLumBits; } @@ -111,24 +142,6 @@ public: void setLuminanceColor(SkColor c) { fLumBits = c; } -#else - unsigned getLuminanceBits() const { - return (fFlags & kLuminance_Mask) >> kLuminance_Shift; - } - - void setLuminanceBits(unsigned lum) { - SkASSERT(lum <= kLuminance_Max); - fFlags = (fFlags & ~kLuminance_Mask) | (lum << kLuminance_Shift); - } - - U8CPU getLuminanceByte() const { - SkASSERT(3 == kLuminance_Bits); - unsigned lum = this->getLuminanceBits(); - lum |= (lum << kLuminance_Bits); - lum |= (lum << kLuminance_Bits*2); - return lum >> (4*kLuminance_Bits - 8); - } -#endif }; SkScalerContext(const SkDescriptor* desc); @@ -185,9 +198,10 @@ public: #endif static inline void MakeRec(const SkPaint&, const SkMatrix*, Rec* rec); - static inline void PostMakeRec(Rec*); + static inline void PostMakeRec(const SkPaint&, Rec*); static SkScalerContext* Create(const SkDescriptor*); + static SkMaskGamma::PreBlend GetMaskPreBlend(const Rec& rec); protected: Rec fRec; @@ -197,7 +211,7 @@ protected: virtual uint16_t generateCharToGlyph(SkUnichar) = 0; virtual void generateAdvance(SkGlyph*) = 0; virtual void generateMetrics(SkGlyph*) = 0; - virtual void generateImage(const SkGlyph&) = 0; + virtual void generateImage(const SkGlyph&, SkMaskGamma::PreBlend* maskPreBlend) = 0; virtual void generatePath(const SkGlyph&, SkPath*) = 0; virtual void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY) = 0; @@ -227,6 +241,9 @@ private: // link-list of context, to handle missing chars. null-terminated. SkScalerContext* fNextContext; + + // converts linear masks to gamma correcting masks. + SkMaskGamma::PreBlend fMaskPreBlend; }; #define kRec_SkDescriptorTag SkSetFourByteTag('s', 'r', 'e', 'c') diff --git a/src/core/SkMaskGamma.cpp b/src/core/SkMaskGamma.cpp new file mode 100644 index 0000000..47903fb --- /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 0000000..e8fa703 --- /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 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 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 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(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(SkColorGetR(color) >> (8 - kLuminanceBits_R)), + sk_t_scale255(SkColorGetG(color) >> (8 - kLuminanceBits_G)), + sk_t_scale255(SkColorGetB(color) >> (8 - kLuminanceBits_B))); + } + + /** The type of the mask pre-blend which will be returned from preBlend(SkColor). */ + typedef SkTMaskPreBlend 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 class SkTMaskPreBlend { +private: + SkTMaskPreBlend(SkTMaskGamma* parent, + const uint8_t* r, + const uint8_t* g, + const uint8_t* b) + : fParent(parent), fR(r), fG(g), fB(b) { + parent->ref(); + } + SkAutoTUnref > fParent; + friend class SkTMaskGamma; +public: + /** + * This copy contructor exists for correctness, but should never be called + * when return value optimization is enabled. + */ + SkTMaskPreBlend(const SkTMaskPreBlend& 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 +SkTMaskPreBlend +SkTMaskGamma::preBlend(SkColor color) { + return SkTMaskPreBlend( + 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 static inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t*) { + return component; +} +template<> /*static*/ inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t* lut) { + return lut[component]; +} +///@} + +#endif diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp index f240737..04a8f5a 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 7c26921..9d99713 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(desc->findEntry(kRec_SkDescriptorTag, NULL))) + , fBaseGlyphCount(0) + , fPathEffect(static_cast(load_flattenable(desc, kPathEffect_SkDescriptorTag))) + , fMaskFilter(static_cast(load_flattenable(desc, kMaskFilter_SkDescriptorTag))) + , fRasterizer(static_cast(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 +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(*srcP++, maskPreBlendR); + U8CPU g = sk_apply_lut_if(*srcP++, maskPreBlendG); + U8CPU b = sk_apply_lut_if(*srcP++, maskPreBlendB); + dstP[x] = SkPack888ToRGB16(r, g, b); } dstP = (uint16_t*)((char*)dstP + dstRB); } } -static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst) { +template +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(*srcP++, maskPreBlendR); + U8CPU g = sk_apply_lut_if(*srcP++, maskPreBlendG); + U8CPU b = sk_apply_lut_if(*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(bm, mask, maskPreBlend); + } else { + pack3xHToLCD16(bm, mask, maskPreBlend); + } break; case SkMask::kLCD32_Format: - pack3xHToLCD32(bm, mask); + if (maskPreBlend) { + pack3xHToLCD32(bm, mask, maskPreBlend); + } else { + pack3xHToLCD32(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) { diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp index 0d72fa1..497a259 100644 --- a/src/ports/SkFontHost_FreeType.cpp +++ b/src/ports/SkFontHost_FreeType.cpp @@ -16,6 +16,7 @@ #include "SkFontHost.h" #include "SkGlyph.h" #include "SkMask.h" +#include "SkMaskGamma.h" #include "SkAdvancedTypefaceMetrics.h" #include "SkScalerContext.h" #include "SkStream.h" @@ -57,14 +58,6 @@ //#define DUMP_STRIKE_CREATION //#define SK_GAMMA_APPLY_TO_A8 -//#define SK_GAMMA_SRGB - -#ifndef SK_GAMMA_CONTRAST - #define SK_GAMMA_CONTRAST 0x66 -#endif -#ifndef SK_GAMMA_EXPONENT - #define SK_GAMMA_EXPONENT 2.2 -#endif #ifdef SK_DEBUG #define SkASSERT_CONTINUE(pred) \ @@ -100,8 +93,6 @@ static bool gLCDSupportValid; // true iff |gLCDSupport| has been set. static bool gLCDSupport; // true iff LCD is supported by the runtime. static int gLCDExtra; // number of extra pixels for filtering. -static const uint8_t* gGammaTables[2]; - ///////////////////////////////////////////////////////////////////////// // See http://freetype.sourceforge.net/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden @@ -153,7 +144,7 @@ protected: virtual uint16_t generateCharToGlyph(SkUnichar uni); virtual void generateAdvance(SkGlyph* glyph); virtual void generateMetrics(SkGlyph* glyph); - 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); @@ -670,25 +661,9 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec) { #endif rec->setHinting(h); -#ifndef SK_USE_COLOR_LUMINANCE - // for compatibility at the moment, discretize luminance to 3 settings - // black, white, gray. This helps with fontcache utilization, since we - // won't create multiple entries that in the end map to the same results. - { - unsigned lum = rec->getLuminanceByte(); - if (gGammaTables[0] || gGammaTables[1]) { - if (lum <= BLACK_LUMINANCE_LIMIT) { - lum = 0; - } else if (lum >= WHITE_LUMINANCE_LIMIT) { - lum = SkScalerContext::kLuminance_Max; - } else { - lum = SkScalerContext::kLuminance_Max >> 1; - } - } else { - lum = 0; // no gamma correct, so use 0 since SkPaint uses that - // when measuring text w/o regard for luminance - } - rec->setLuminanceBits(lum); +#ifndef SK_GAMMA_APPLY_TO_A8 + if (!isLCD(*rec)) { + rec->ignorePreBlend(); } #endif } @@ -716,7 +691,6 @@ SkScalerContext_FreeType::SkScalerContext_FreeType(const SkDescriptor* desc) if (!InitFreetype()) { sk_throw(); } - SkFontHost::GetGammaTables(gGammaTables); } ++gFTCount; @@ -1169,103 +1143,6 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) { /////////////////////////////////////////////////////////////////////////////// -#ifdef SK_USE_COLOR_LUMINANCE - -static float apply_contrast(float srca, float contrast) { - return srca + ((1.0f - srca) * contrast * srca); -} - -#ifdef SK_GAMMA_SRGB -static float lin(float per) { - if (per <= 0.04045f) { - return per / 12.92f; - } - return powf((per + 0.055f) / 1.055, 2.4f); -} -static float per(float lin) { - if (lin <= 0.0031308f) { - return lin * 12.92f; - } - return 1.055f * powf(lin, 1.0f / 2.4f) - 0.055f; -} -#else //SK_GAMMA_SRGB -static float lin(float per) { - const float g = SK_GAMMA_EXPONENT; - return powf(per, g); -} -static float per(float lin) { - const float g = SK_GAMMA_EXPONENT; - return powf(lin, 1.0f / g); -} -#endif //SK_GAMMA_SRGB - -static void build_gamma_table(uint8_t table[256], int srcI) { - const float src = (float)srcI / 255.0f; - const float linSrc = lin(src); - const float linDst = 1.0f - linSrc; - const float dst = per(linDst); - - // have our contrast value taper off to 0 as the src luminance becomes white - const float contrast = SK_GAMMA_CONTRAST / 255.0f * linDst; - const float step = 1.0f / 256.0f; - - //Remove discontinuity and instability when src is close to dst. - if (fabs(src - dst) < 0.01f) { - float rawSrca = 0.0f; - for (int i = 0; i < 256; ++i, rawSrca += step) { - float srca = apply_contrast(rawSrca, contrast); - table[i] = 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, contrast); - SkASSERT(srca <= 1.0f); - float dsta = 1 - srca; - - //Calculate the output we want. - float linOut = (linSrc * srca + dsta * linDst); - SkASSERT(linOut <= 1.0f); - float out = per(linOut); - - //Undo what the blit blend will do. - float result = (out - dst) / (src - dst); - SkASSERT(sk_float_round2int(255.0f * result) <= 255); - - table[i] = sk_float_round2int(255.0f * result); - } - } -} - -static const uint8_t* getGammaTable(U8CPU luminance) { - static uint8_t gGammaTables[4][256]; - static bool gInited; - if (!gInited) { - build_gamma_table(gGammaTables[0], 0x00); - build_gamma_table(gGammaTables[1], 0x55); - build_gamma_table(gGammaTables[2], 0xAA); - build_gamma_table(gGammaTables[3], 0xFF); - - gInited = true; - } - SkASSERT(0 == (luminance >> 8)); - return gGammaTables[luminance >> 6]; -} - -#else //SK_USE_COLOR_LUMINANCE -static const uint8_t* getIdentityTable() { - static bool gOnce; - static uint8_t gIdentityTable[256]; - if (!gOnce) { - for (int i = 0; i < 256; ++i) { - gIdentityTable[i] = i; - } - gOnce = true; - } - return gIdentityTable; -} -#endif //SK_USE_COLOR_LUMINANCE - static uint16_t packTriple(unsigned r, unsigned g, unsigned b) { return SkPackRGB16(r >> 3, g >> 2, b >> 3); } @@ -1281,6 +1158,7 @@ static int bittst(const uint8_t data[], int bitOffset) { return lowBit & 1; } +template static void copyFT2LCD16(const SkGlyph& glyph, const FT_Bitmap& bitmap, int lcdIsBGR, bool lcdIsVert, const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) { @@ -1325,25 +1203,25 @@ static void copyFT2LCD16(const SkGlyph& glyph, const FT_Bitmap& bitmap, SkTSwap(srcR, srcB); } for (int x = 0; x < width; x++) { - dst[x] = packTriple(tableR[*srcR++], - tableG[*srcG++], - tableB[*srcB++]); + dst[x] = packTriple(sk_apply_lut_if(*srcR++, tableR), + sk_apply_lut_if(*srcG++, tableG), + sk_apply_lut_if(*srcB++, tableB)); } src += 3 * bitmap.pitch; } else { // horizontal stripes const uint8_t* triple = src; if (lcdIsBGR) { for (int x = 0; x < width; x++) { - dst[x] = packTriple(tableR[triple[2]], - tableG[triple[1]], - tableB[triple[0]]); + dst[x] = packTriple(sk_apply_lut_if(triple[2], tableR), + sk_apply_lut_if(triple[1], tableG), + sk_apply_lut_if(triple[0], tableB)); triple += 3; } } else { for (int x = 0; x < width; x++) { - dst[x] = packTriple(tableR[triple[0]], - tableG[triple[1]], - tableB[triple[2]]); + dst[x] = packTriple(sk_apply_lut_if(triple[0], tableR), + sk_apply_lut_if(triple[1], tableG), + sk_apply_lut_if(triple[2], tableB)); triple += 3; } } @@ -1355,7 +1233,7 @@ static void copyFT2LCD16(const SkGlyph& glyph, const FT_Bitmap& bitmap, } } -void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) { +void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) { SkAutoMutexAcquire ac(gFTMutex); FT_Error err; @@ -1373,25 +1251,15 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) { return; } -#ifdef SK_USE_COLOR_LUMINANCE - SkColor lumColor = fRec.getLuminanceColor(); - const uint8_t* tableR = getGammaTable(SkColorGetR(lumColor)); - const uint8_t* tableG = getGammaTable(SkColorGetG(lumColor)); - const uint8_t* tableB = getGammaTable(SkColorGetB(lumColor)); -#else - unsigned lum = fRec.getLuminanceByte(); - const uint8_t* tableR; - const uint8_t* tableG; - const uint8_t* tableB; - - bool isWhite = lum >= WHITE_LUMINANCE_LIMIT; - bool isBlack = lum <= BLACK_LUMINANCE_LIMIT; - if ((gGammaTables[0] || gGammaTables[1]) && (isBlack || isWhite)) { - tableR = tableG = tableB = gGammaTables[isBlack ? 0 : 1]; - } else { - tableR = tableG = tableB = getIdentityTable(); + //Must be careful not to use these if maskPreBlend == NULL + const uint8_t* tableR = NULL; + const uint8_t* tableG = NULL; + const uint8_t* tableB = NULL; + if (maskPreBlend) { + tableR = maskPreBlend->fR; + tableG = maskPreBlend->fG; + tableB = maskPreBlend->fB; } -#endif const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); const bool doVert = fLCDIsVert; @@ -1427,8 +1295,13 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) { if (SkMask::kLCD16_Format == glyph.fMaskFormat) { FT_Render_Glyph(fFace->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD); - copyFT2LCD16(glyph, fFace->glyph->bitmap, doBGR, doVert, - tableR, tableG, tableB); + if (maskPreBlend) { + copyFT2LCD16(glyph, fFace->glyph->bitmap, doBGR, doVert, + tableR, tableG, tableB); + } else { + copyFT2LCD16(glyph, fFace->glyph->bitmap, doBGR, doVert, + tableR, tableG, tableB); + } } else { target.width = glyph.fWidth; target.rows = glyph.fHeight; @@ -1493,8 +1366,13 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) { dst += glyph.rowBytes(); } } else if (SkMask::kLCD16_Format == glyph.fMaskFormat) { - copyFT2LCD16(glyph, fFace->glyph->bitmap, doBGR, doVert, - tableR, tableG, tableB); + if (maskPreBlend) { + copyFT2LCD16(glyph, fFace->glyph->bitmap, doBGR, doVert, + tableR, tableG, tableB); + } else { + copyFT2LCD16(glyph, fFace->glyph->bitmap, doBGR, doVert, + tableR, tableG, tableB); + } } else { SkDEBUGFAIL("unknown glyph bitmap transform needed"); } @@ -1507,16 +1385,14 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) { // We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum, // it is optional -#if defined(SK_GAMMA_APPLY_TO_A8) || !defined(SK_USE_COLOR_LUMINANCE) - if (SkMask::kA8_Format == glyph.fMaskFormat) { - SkASSERT(tableR == tableG && tableR == tableB); - const uint8_t* table = tableR; +#if defined(SK_GAMMA_APPLY_TO_A8) + if (SkMask::kA8_Format == glyph.fMaskFormat && maskPreBlend) { 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] = table[dst[x]]; + dst[x] = tableG[dst[x]]; } dst += rowBytes; } diff --git a/src/ports/SkFontHost_gamma.cpp b/src/ports/SkFontHost_gamma.cpp deleted file mode 100644 index 0d15414..0000000 --- a/src/ports/SkFontHost_gamma.cpp +++ /dev/null @@ -1,107 +0,0 @@ - -/* - * Copyright 2011 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ -#include "SkFontHost.h" -#include - -// define this to use pre-compiled tables for gamma. This is slightly faster, -// and doesn't create any RW global memory, but means we cannot change the -// gamma at runtime. -//#define USE_PREDEFINED_GAMMA_TABLES - -#ifndef USE_PREDEFINED_GAMMA_TABLES - // define this if you want to spew out the "C" code for the tables, given - // the current values for SK_BLACK_GAMMA and SK_WHITE_GAMMA. - #define DUMP_GAMMA_TABLESx -#endif - -/////////////////////////////////////////////////////////////////////////////// - -#include "SkGraphics.h" - -// declared here, so we can link against it elsewhere -void skia_set_text_gamma(float blackGamma, float whiteGamma); - -#ifdef USE_PREDEFINED_GAMMA_TABLES - -#include "sk_predefined_gamma.h" - -void skia_set_text_gamma(float blackGamma, float whiteGamma) {} - -#else // use writable globals for gamma tables - -static void build_power_table(uint8_t table[], float ee) { -// SkDebugf("------ build_power_table %g\n", ee); - for (int i = 0; i < 256; i++) { - float x = i / 255.f; - // printf(" %d %g", i, x); - x = powf(x, ee); - // printf(" %g", x); - int xx = SkScalarRound(SkFloatToScalar(x * 255)); - // printf(" %d\n", xx); - table[i] = SkToU8(xx); - } -} - -static bool gGammaIsBuilt; -static uint8_t gBlackGamma[256], gWhiteGamma[256]; - -static float gBlackGammaCoeff = 1.4f; -static float gWhiteGammaCoeff = 1/1.4f; - -void skia_set_text_gamma(float blackGamma, float whiteGamma) { - gBlackGammaCoeff = blackGamma; - gWhiteGammaCoeff = whiteGamma; - gGammaIsBuilt = false; - SkGraphics::PurgeFontCache(); - build_power_table(gBlackGamma, gBlackGammaCoeff); - build_power_table(gWhiteGamma, gWhiteGammaCoeff); -} - -#ifdef DUMP_GAMMA_TABLES - -#include "SkString.h" - -static void dump_a_table(const char name[], const uint8_t table[], - float gamma) { - SkDebugf("\n"); - SkDebugf("\/\/ Gamma table for %g\n", gamma); - SkDebugf("static const uint8_t %s[] = {\n", name); - for (int y = 0; y < 16; y++) { - SkString line, tmp; - for (int x = 0; x < 16; x++) { - tmp.printf("0x%02X, ", *table++); - line.append(tmp); - } - SkDebugf(" %s\n", line.c_str()); - } - SkDebugf("};\n"); -} - -#endif - -#endif - -/////////////////////////////////////////////////////////////////////////////// - -void SkFontHost::GetGammaTables(const uint8_t* tables[2]) { -#ifndef USE_PREDEFINED_GAMMA_TABLES - if (!gGammaIsBuilt) { - build_power_table(gBlackGamma, gBlackGammaCoeff); - build_power_table(gWhiteGamma, gWhiteGammaCoeff); - gGammaIsBuilt = true; - -#ifdef DUMP_GAMMA_TABLES - dump_a_table("gBlackGamma", gBlackGamma, gBlackGammaCoeff); - dump_a_table("gWhiteGamma", gWhiteGamma, gWhiteGammaCoeff); -#endif - } -#endif - tables[0] = gBlackGamma; - tables[1] = gWhiteGamma; -} - diff --git a/src/ports/SkFontHost_gamma_none.cpp b/src/ports/SkFontHost_gamma_none.cpp deleted file mode 100644 index 18f113c..0000000 --- a/src/ports/SkFontHost_gamma_none.cpp +++ /dev/null @@ -1,23 +0,0 @@ - -/* - * Copyright 2008 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - - -// ----------------------------------------------------------------------------- -// This is a noop gamma implementation for systems where gamma is already -// corrected, or dealt with in a system wide fashion. For example, on X windows -// one uses the xgamma utility to set the server-wide gamma correction value. -// ----------------------------------------------------------------------------- - -#include "SkFontHost.h" - -void SkFontHost::GetGammaTables(const uint8_t* tables[2]) -{ - tables[0] = NULL; - tables[1] = NULL; -} - diff --git a/src/ports/SkFontHost_mac_coretext.cpp b/src/ports/SkFontHost_mac_coretext.cpp index cdd4a74..c723c88 100644 --- a/src/ports/SkFontHost_mac_coretext.cpp +++ b/src/ports/SkFontHost_mac_coretext.cpp @@ -24,6 +24,7 @@ #include "SkFontDescriptor.h" #include "SkFloatingPoint.h" #include "SkGlyph.h" +#include "SkMaskGamma.h" #include "SkPaint.h" #include "SkString.h" #include "SkStream.h" @@ -265,13 +266,42 @@ static SkScalar getFontScale(CGFontRef cgFont) { #define BITMAP_INFO_RGB (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host) #define BITMAP_INFO_GRAY (kCGImageAlphaNone) +/** + * There does not appear to be a publicly accessable API for determining if lcd + * font smoothing will be applied if we request it. The main issue is that if + * smoothing is applied a gamma of 2.0 will be used, if not a gamma of 1.0. + */ +static bool supports_LCD() { + static int gSupportsLCD = -1; + if (gSupportsLCD >= 0) { + return (bool) gSupportsLCD; + } + int rgb = 0; + CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); + CGContextRef cgContext = CGBitmapContextCreate(&rgb, 1, 1, 8, 4, colorspace, + BITMAP_INFO_RGB); + CGContextSelectFont(cgContext, "Helvetica", 16, kCGEncodingMacRoman); + CGContextSetShouldSmoothFonts(cgContext, true); + CGContextSetShouldAntialias(cgContext, true); + CGContextSetTextDrawingMode(cgContext, kCGTextFill); + CGContextSetGrayFillColor( cgContext, 1, 1); + CGContextShowTextAtPoint(cgContext, -1, 0, "|", 1); + CFSafeRelease(colorspace); + CFSafeRelease(cgContext); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = (rgb >> 0) & 0xFF; + gSupportsLCD = r != g || r != b; + return (bool) gSupportsLCD; +} + class Offscreen { public: Offscreen(); ~Offscreen(); CGRGBPixel* getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph, - bool fgColorIsWhite, CGGlyph glyphID, size_t* rowBytesPtr); + CGGlyph glyphID, size_t* rowBytesPtr); private: enum { @@ -283,7 +313,6 @@ private: // cached state CGContextRef fCG; SkISize fSize; - bool fFgColorIsWhite; bool fDoAA; bool fDoLCD; @@ -576,13 +605,13 @@ public: protected: - unsigned generateGlyphCount(void); - uint16_t generateCharToGlyph(SkUnichar uni); - void generateAdvance(SkGlyph* glyph); - void generateMetrics(SkGlyph* glyph); - void generateImage(const SkGlyph& glyph); - void generatePath( const SkGlyph& glyph, SkPath* path); - void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY); + unsigned generateGlyphCount(void) SK_OVERRIDE; + uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE; + void generateAdvance(SkGlyph* glyph) SK_OVERRIDE; + void generateMetrics(SkGlyph* glyph) SK_OVERRIDE; + void generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) SK_OVERRIDE; + void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE; + void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY) SK_OVERRIDE; private: @@ -597,12 +626,7 @@ private: SkMatrix fVerticalMatrix; // unit rotated SkMatrix fMatrix; // with font size SkMatrix fAdjustBadMatrix; // lion-specific fix -#ifdef SK_USE_COLOR_LUMINANCE - Offscreen fBlackScreen; - Offscreen fWhiteScreen; -#else Offscreen fOffscreen; -#endif CTFontRef fCTFont; CTFontRef fCTVerticalFont; // for vertical advance CGFontRef fCGFont; @@ -698,8 +722,11 @@ SkScalerContext_Mac::~SkScalerContext_Mac() { } CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph, - bool fgColorIsWhite, CGGlyph glyphID, size_t* rowBytesPtr) { + CGGlyph glyphID, size_t* rowBytesPtr) { if (!fRGBSpace) { + //It doesn't appear to matter what color space is specified. + //Regular blends and antialiased text are always (s*a + d*(1-a)) + //and smoothed text is always g=2.0. fRGBSpace = CGColorSpaceCreateDeviceRGB(); } @@ -710,13 +737,14 @@ CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& switch (glyph.fMaskFormat) { case SkMask::kLCD16_Format: case SkMask::kLCD32_Format: + case SkMask::kA8_Format: //Draw A8 as LCD, then downsample doLCD = true; doAA = true; break; - case SkMask::kA8_Format: - doLCD = false; - doAA = true; - break; + //case SkMask::kA8_Format: + // doLCD = false; + // doAA = true; + // break; default: break; } @@ -749,10 +777,13 @@ CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& CGContextSetAllowsFontSubpixelPositioning(fCG, context.fDoSubPosition); CGContextSetShouldSubpixelPositionFonts(fCG, context.fDoSubPosition); + // Draw white on black to create mask. + // TODO: Draw black on white and invert, CG has a special case codepath. + CGContextSetGrayFillColor(fCG, 1.0f, 1.0f); + // force our checks below to happen fDoAA = !doAA; fDoLCD = !doLCD; - fFgColorIsWhite = !fgColorIsWhite; } if (fDoAA != doAA) { @@ -763,23 +794,13 @@ CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& CGContextSetShouldSmoothFonts(fCG, doLCD); fDoLCD = doLCD; } - if (fFgColorIsWhite != fgColorIsWhite) { - CGContextSetGrayFillColor(fCG, fgColorIsWhite ? 1 : 0, 1); - fFgColorIsWhite = fgColorIsWhite; - } CGRGBPixel* image = (CGRGBPixel*)fImageStorage.get(); // skip rows based on the glyph's height image += (fSize.fHeight - glyph.fHeight) * fSize.fWidth; - // erase with the "opposite" of the fgColor - uint32_t erase = fgColorIsWhite ? 0 : ~0; -#if 0 - sk_memset_rect(image, erase, glyph.fWidth * sizeof(CGRGBPixel), - glyph.fHeight, rowBytes); -#else - sk_memset_rect32(image, erase, glyph.fWidth, glyph.fHeight, rowBytes); -#endif + // erase to black + sk_memset_rect32(image, 0, glyph.fWidth, glyph.fHeight, rowBytes); float subX = 0; float subY = 0; @@ -1060,62 +1081,27 @@ void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) { static void build_power_table(uint8_t table[], float ee) { for (int i = 0; i < 256; i++) { float x = i / 255.f; - x = powf(x, ee); + x = sk_float_pow(x, ee); int xx = SkScalarRoundToInt(SkFloatToScalar(x * 255)); table[i] = SkToU8(xx); } } -static const uint8_t* getInverseTable(bool isWhite) { - static uint8_t gWhiteTable[256]; - static uint8_t gTable[256]; - static bool gInited; - if (!gInited) { - build_power_table(gWhiteTable, 1.5f); - build_power_table(gTable, 2.2f); - gInited = true; - } - return isWhite ? gWhiteTable : gTable; -} - -#ifdef SK_USE_COLOR_LUMINANCE -static const uint8_t* getGammaTable(U8CPU luminance) { - static uint8_t gGammaTables[4][256]; +/** + * This will invert the gamma applied by CoreGraphics, so we can get linear + * values. + * + * CoreGraphics obscurely defaults to 2.0 as the smoothing gamma value. + * The color space used does not appear to affect this choice. + */ +static const uint8_t* getInverseGammaTableCoreGraphicSmoothing() { static bool gInited; + static uint8_t gTableCoreGraphicsSmoothing[256]; if (!gInited) { -#if 1 - float start = 1.1f; - float stop = 2.1f; - for (int i = 0; i < 4; ++i) { - float g = start + (stop - start) * i / 3; - build_power_table(gGammaTables[i], 1/g); - } -#else - build_power_table(gGammaTables[0], 1); - build_power_table(gGammaTables[1], 1); - build_power_table(gGammaTables[2], 1); - build_power_table(gGammaTables[3], 1); -#endif + build_power_table(gTableCoreGraphicsSmoothing, 2.0f); gInited = true; } - SkASSERT(0 == (luminance >> 8)); - return gGammaTables[luminance >> 6]; -} -#endif - -static void invertGammaMask(bool isWhite, CGRGBPixel rgb[], int width, - int height, size_t rb) { - const uint8_t* table = getInverseTable(isWhite); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - uint32_t c = rgb[x]; - int r = (c >> 16) & 0xFF; - int g = (c >> 8) & 0xFF; - int b = (c >> 0) & 0xFF; - rgb[x] = (table[r] << 16) | (table[g] << 8) | table[b]; - } - rgb = (CGRGBPixel*)((char*)rgb + rb); - } + return gTableCoreGraphicsSmoothing; } static void cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) { @@ -1131,230 +1117,164 @@ static void cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) { } } -#ifdef SK_USE_COLOR_LUMINANCE -static int lerpScale(int dst, int src, int scale) { - return dst + (scale * (src - dst) >> 23); +template +static inline uint8_t rgb_to_a8(CGRGBPixel rgb, const uint8_t* table8) { + U8CPU r = (rgb >> 16) & 0xFF; + U8CPU g = (rgb >> 8) & 0xFF; + U8CPU b = (rgb >> 0) & 0xFF; + return sk_apply_lut_if((r + g + b) / 3, table8); } - -static CGRGBPixel lerpPixel(CGRGBPixel dst, CGRGBPixel src, - int scaleR, int scaleG, int scaleB) { - int sr = (src >> 16) & 0xFF; - int sg = (src >> 8) & 0xFF; - int sb = (src >> 0) & 0xFF; - int dr = (dst >> 16) & 0xFF; - int dg = (dst >> 8) & 0xFF; - int db = (dst >> 0) & 0xFF; - - int rr = lerpScale(dr, sr, scaleR); - int rg = lerpScale(dg, sg, scaleG); - int rb = lerpScale(db, sb, scaleB); - return (rr << 16) | (rg << 8) | rb; -} - -static void lerpPixels(CGRGBPixel dst[], const CGRGBPixel src[], int width, - int height, int rowBytes, int lumBits) { - int scaleR = (1 << 23) * SkColorGetR(lumBits) / 0xFF; - int scaleG = (1 << 23) * SkColorGetG(lumBits) / 0xFF; - int scaleB = (1 << 23) * SkColorGetB(lumBits) / 0xFF; - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - // bit-not the src, since it was drawn from black, so we need the - // compliment of those bits - dst[x] = lerpPixel(dst[x], ~src[x], scaleR, scaleG, scaleB); +template +static void rgb_to_a8(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, + const SkGlyph& glyph, const uint8_t* table8) { + const int width = glyph.fWidth; + size_t dstRB = glyph.rowBytes(); + uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage; + + for (int y = 0; y < glyph.fHeight; y++) { + for (int i = 0; i < width; ++i) { + dst[i] = rgb_to_a8(cgPixels[i], table8); } - src = (CGRGBPixel*)((char*)src + rowBytes); - dst = (CGRGBPixel*)((char*)dst + rowBytes); + cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); + dst += dstRB; + } +} + +template +static inline uint16_t rgb_to_lcd16(CGRGBPixel rgb, const uint8_t* tableR, + const uint8_t* tableG, + const uint8_t* tableB) { + U8CPU r = sk_apply_lut_if((rgb >> 16) & 0xFF, tableR); + U8CPU g = sk_apply_lut_if((rgb >> 8) & 0xFF, tableG); + U8CPU b = sk_apply_lut_if((rgb >> 0) & 0xFF, tableB); + return SkPack888ToRGB16(r, g, b); +} +template +static void rgb_to_lcd16(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, const SkGlyph& glyph, + const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) { + const int width = glyph.fWidth; + size_t dstRB = glyph.rowBytes(); + uint16_t* SK_RESTRICT dst = (uint16_t*)glyph.fImage; + + for (int y = 0; y < glyph.fHeight; y++) { + for (int i = 0; i < width; i++) { + dst[i] = rgb_to_lcd16(cgPixels[i], tableR, tableG, tableB); + } + cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); + dst = (uint16_t*)((char*)dst + dstRB); } } -#endif -#if 1 -static inline int r32_to_16(int x) { return SkR32ToR16(x); } -static inline int g32_to_16(int x) { return SkG32ToG16(x); } -static inline int b32_to_16(int x) { return SkB32ToB16(x); } -#else -static inline int round8to5(int x) { - return (x + 3 - (x >> 5) + (x >> 7)) >> 3; -} -static inline int round8to6(int x) { - int xx = (x + 1 - (x >> 6) + (x >> 7)) >> 2; - SkASSERT((unsigned)xx <= 63); - - int ix = x >> 2; - SkASSERT(SkAbs32(xx - ix) <= 1); - return xx; +template +static inline uint32_t rgb_to_lcd32(CGRGBPixel rgb, const uint8_t* tableR, + const uint8_t* tableG, + const uint8_t* tableB) { + U8CPU r = sk_apply_lut_if((rgb >> 16) & 0xFF, tableR); + U8CPU g = sk_apply_lut_if((rgb >> 8) & 0xFF, tableG); + U8CPU b = sk_apply_lut_if((rgb >> 0) & 0xFF, tableB); + return SkPackARGB32(0xFF, r, g, b); } - -static inline int r32_to_16(int x) { return round8to5(x); } -static inline int g32_to_16(int x) { return round8to6(x); } -static inline int b32_to_16(int x) { return round8to5(x); } -#endif - -static inline uint16_t rgb_to_lcd16(CGRGBPixel rgb) { - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = (rgb >> 0) & 0xFF; - - return SkPackRGB16(r32_to_16(r), g32_to_16(g), b32_to_16(b)); +template +static void rgb_to_lcd32(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes, const SkGlyph& glyph, + const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) { + const int width = glyph.fWidth; + size_t dstRB = glyph.rowBytes(); + uint32_t* SK_RESTRICT dst = (uint32_t*)glyph.fImage; + for (int y = 0; y < glyph.fHeight; y++) { + for (int i = 0; i < width; i++) { + dst[i] = rgb_to_lcd32(cgPixels[i], tableR, tableG, tableB); + } + cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); + dst = (uint32_t*)((char*)dst + dstRB); + } } -static inline uint32_t rgb_to_lcd32(CGRGBPixel rgb) { - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = (rgb >> 0) & 0xFF; - - return SkPackARGB32(0xFF, r, g, b); +template T* SkTAddByteOffset(T* ptr, size_t byteOffset) { + return (T*)((char*)ptr + byteOffset); } -#define BLACK_LUMINANCE_LIMIT 0x40 -#define WHITE_LUMINANCE_LIMIT 0xA0 - -void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) { +void SkScalerContext_Mac::generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) { CGGlyph cgGlyph = (CGGlyph) glyph.getGlyphID(fBaseGlyphCount); - const bool isLCD = isLCDFormat(glyph.fMaskFormat); -#ifdef SK_USE_COLOR_LUMINANCE - const bool isBW = SkMask::kBW_Format == glyph.fMaskFormat; - const bool isA8 = !isLCD && !isBW; - - unsigned lumBits = fRec.getLuminanceColor(); - uint32_t xorMask = 0; - - if (isA8) { - // for A8, we just want a component (they're all the same) - lumBits = SkColorGetR(lumBits); - } -#else - bool fgColorIsWhite = true; - bool isWhite = fRec.getLuminanceByte() >= WHITE_LUMINANCE_LIMIT; - bool isBlack = fRec.getLuminanceByte() <= BLACK_LUMINANCE_LIMIT; - uint32_t xorMask; - bool invertGamma = false; - - /* For LCD16, we first create a temp offscreen cg-context in 32bit, - * erase to white, and then draw a black glyph into it. Then we can - * extract the r,g,b values, invert-them, and now we have the original - * src mask components, which we pack into our 16bit mask. - */ - if (isLCD) { - if (isBlack) { - xorMask = ~0U; - fgColorIsWhite = false; - } else { /* white or neutral */ - xorMask = 0; - invertGamma = true; - } - } -#endif - + // Draw the glyph size_t cgRowBytes; -#ifdef SK_USE_COLOR_LUMINANCE - CGRGBPixel* cgPixels; - const uint8_t* gammaTable = NULL; + CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes); + if (cgPixels == NULL) { + return; + } - if (isLCD) { - CGRGBPixel* wtPixels = NULL; - CGRGBPixel* bkPixels = NULL; - bool needBlack = true; - bool needWhite = true; - - if (SK_ColorWHITE == lumBits) { - needBlack = false; - } else if (SK_ColorBLACK == lumBits) { - needWhite = false; - } - - if (needBlack) { - bkPixels = fBlackScreen.getCG(*this, glyph, false, cgGlyph, &cgRowBytes); - cgPixels = bkPixels; - xorMask = ~0; - } - if (needWhite) { - wtPixels = fWhiteScreen.getCG(*this, glyph, true, cgGlyph, &cgRowBytes); - cgPixels = wtPixels; - xorMask = 0; - } + //TODO: see if drawing black on white and inverting is faster (at least in + //lcd case) as core graphics appears to have special case code for drawing + //black text. - if (wtPixels && bkPixels) { - lerpPixels(wtPixels, bkPixels, glyph.fWidth, glyph.fHeight, cgRowBytes, - ~lumBits); - } - } else { // isA8 or isBW - cgPixels = fWhiteScreen.getCG(*this, glyph, true, cgGlyph, &cgRowBytes); - if (isA8) { - gammaTable = getGammaTable(lumBits); + // Fix the glyph + const bool isLCD = isLCDFormat(glyph.fMaskFormat); + if (isLCD || (glyph.fMaskFormat == SkMask::kA8_Format && supports_LCD())) { + const uint8_t* table = getInverseGammaTableCoreGraphicSmoothing(); + + //Note that the following cannot really be integrated into the + //pre-blend, since we may not be applying the pre-blend; when we aren't + //applying the pre-blend it means that a filter wants linear anyway. + //Other code may also be applying the pre-blend, so we'd need another + //one with this and one without. + CGRGBPixel* addr = cgPixels; + for (int y = 0; y < glyph.fHeight; ++y) { + for (int x = 0; x < glyph.fWidth; ++x) { + int r = (addr[x] >> 16) & 0xFF; + int g = (addr[x] >> 8) & 0xFF; + int b = (addr[x] >> 0) & 0xFF; + addr[x] = (table[r] << 16) | (table[g] << 8) | table[b]; + } + addr = SkTAddByteOffset(addr, cgRowBytes); } } -#else - CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, fgColorIsWhite, cgGlyph, - &cgRowBytes); -#endif - - // Draw the glyph - if (cgPixels != NULL) { - -#ifdef SK_USE_COLOR_LUMINANCE -#else - if (invertGamma) { - invertGammaMask(isWhite, (uint32_t*)cgPixels, - glyph.fWidth, glyph.fHeight, cgRowBytes); - } -#endif - - int width = glyph.fWidth; - switch (glyph.fMaskFormat) { - case SkMask::kLCD32_Format: { - uint32_t* dst = (uint32_t*)glyph.fImage; - size_t dstRB = glyph.rowBytes(); - for (int y = 0; y < glyph.fHeight; y++) { - for (int i = 0; i < width; i++) { - dst[i] = rgb_to_lcd32(cgPixels[i] ^ xorMask); - } - cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); - dst = (uint32_t*)((char*)dst + dstRB); - } - } break; - case SkMask::kLCD16_Format: { - // downsample from rgba to rgb565 - uint16_t* dst = (uint16_t*)glyph.fImage; - size_t dstRB = glyph.rowBytes(); - for (int y = 0; y < glyph.fHeight; y++) { - for (int i = 0; i < width; i++) { - dst[i] = rgb_to_lcd16(cgPixels[i] ^ xorMask); - } - cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); - dst = (uint16_t*)((char*)dst + dstRB); - } - } break; - case SkMask::kA8_Format: { - uint8_t* dst = (uint8_t*)glyph.fImage; - size_t dstRB = glyph.rowBytes(); - for (int y = 0; y < glyph.fHeight; y++) { - for (int i = 0; i < width; ++i) { - unsigned alpha8 = CGRGBPixel_getAlpha(cgPixels[i]); -#ifdef SK_USE_COLOR_LUMINANCE - alpha8 = gammaTable[alpha8]; -#endif - dst[i] = alpha8; - } - cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); - dst += dstRB; - } - } break; - case SkMask::kBW_Format: { - uint8_t* dst = (uint8_t*)glyph.fImage; - size_t dstRB = glyph.rowBytes(); - for (int y = 0; y < glyph.fHeight; y++) { - cgpixels_to_bits(dst, cgPixels, width); - cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); - dst += dstRB; - } - } break; - default: - SkDEBUGFAIL("unexpected mask format"); - break; - } + + // Must be careful not to use these if maskPreBlend == NULL + const uint8_t* tableR = NULL; + const uint8_t* tableG = NULL; + const uint8_t* tableB = NULL; + if (maskPreBlend) { + tableR = maskPreBlend->fR; + tableG = maskPreBlend->fG; + tableB = maskPreBlend->fB; + } + + // Convert glyph to mask + switch (glyph.fMaskFormat) { + case SkMask::kLCD32_Format: { + if (maskPreBlend) { + rgb_to_lcd32(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB); + } else { + rgb_to_lcd32(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB); + } + } break; + case SkMask::kLCD16_Format: { + if (maskPreBlend) { + rgb_to_lcd16(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB); + } else { + rgb_to_lcd16(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB); + } + } break; + case SkMask::kA8_Format: { + if (maskPreBlend) { + rgb_to_a8(cgPixels, cgRowBytes, glyph, tableG); + } else { + rgb_to_a8(cgPixels, cgRowBytes, glyph, tableG); + } + } break; + case SkMask::kBW_Format: { + const int width = glyph.fWidth; + size_t dstRB = glyph.rowBytes(); + uint8_t* dst = (uint8_t*)glyph.fImage; + for (int y = 0; y < glyph.fHeight; y++) { + cgpixels_to_bits(dst, cgPixels, width); + cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); + dst += dstRB; + } + } break; + default: + SkDEBUGFAIL("unexpected mask format"); + break; } } @@ -1880,30 +1800,6 @@ SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) { return nextFontID; } -static bool supports_LCD() { - static int gSupportsLCD = -1; - if (gSupportsLCD >= 0) { - return (bool) gSupportsLCD; - } - int rgb = 0; - CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); - CGContextRef cgContext = CGBitmapContextCreate(&rgb, 1, 1, 8, 4, colorspace, - BITMAP_INFO_RGB); - CGContextSelectFont(cgContext, "Helvetica", 16, kCGEncodingMacRoman); - CGContextSetShouldSmoothFonts(cgContext, true); - CGContextSetShouldAntialias(cgContext, true); - CGContextSetTextDrawingMode(cgContext, kCGTextFill); - CGContextSetGrayFillColor( cgContext, 1, 1); - CGContextShowTextAtPoint(cgContext, -1, 0, "|", 1); - CFSafeRelease(colorspace); - CFSafeRelease(cgContext); - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = (rgb >> 0) & 0xFF; - gSupportsLCD = r != g || r != b; - return (bool) gSupportsLCD; -} - void SkFontHost::FilterRec(SkScalerContext::Rec* rec) { unsigned flagsWeDontSupport = SkScalerContext::kDevKernText_Flag | SkScalerContext::kAutohinting_Flag; @@ -1918,37 +1814,24 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec) { h = SkPaint::kNormal_Hinting; } rec->setHinting(h); - -#ifdef SK_USE_COLOR_LUMINANCE + + bool lcdSupport = supports_LCD(); if (isLCDFormat(rec->fMaskFormat)) { - SkColor c = rec->getLuminanceColor(); - // apply our chosen scaling between Black and White cg output - int r = SkColorGetR(c)*2/3; - int g = SkColorGetG(c)*2/3; - int b = SkColorGetB(c)*2/3; - rec->setLuminanceColor(SkColorSetRGB(r, g, b)); - } -#else - { - unsigned lum = rec->getLuminanceByte(); - if (lum <= BLACK_LUMINANCE_LIMIT) { - lum = 0; - } else if (lum >= WHITE_LUMINANCE_LIMIT) { - lum = SkScalerContext::kLuminance_Max; + if (lcdSupport) { + //CoreGraphics creates 555 masks for smoothed text anyway. + rec->fMaskFormat = SkMask::kLCD16_Format; } else { - lum = SkScalerContext::kLuminance_Max >> 1; + rec->fMaskFormat = SkMask::kA8_Format; } - rec->setLuminanceBits(lum); } + + if (lcdSupport) { + //CoreGraphics dialates smoothed text as needed. + rec->setContrast(0); + } else { +#ifndef SK_GAMMA_APPLY_TO_A8 + rec->ignorePreBlend(); #endif - - if (SkMask::kLCD16_Format == rec->fMaskFormat - || SkMask::kLCD32_Format == rec->fMaskFormat) { - if (supports_LCD()) { - rec->fMaskFormat = SkMask::kLCD32_Format; - } else { - rec->fMaskFormat = SkMask::kA8_Format; - } } } diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp index 082e6e4..fbe4bb1 100755 --- a/src/ports/SkFontHost_win.cpp +++ b/src/ports/SkFontHost_win.cpp @@ -13,6 +13,7 @@ #include "SkFontDescriptor.h" #include "SkFontHost.h" #include "SkGlyph.h" +#include "SkMaskGamma.h" #include "SkOTUtils.h" #include "SkStream.h" #include "SkString.h" @@ -397,7 +398,6 @@ public: fBits = NULL; fWidth = fHeight = 0; fIsBW = false; - fColor = kInvalid_Color; } ~HDCOffscreen() { @@ -414,8 +414,7 @@ public: fXform = xform; } - const void* draw(const SkGlyph&, bool isBW, SkGdiRGB fgColor, - size_t* srcRBPtr); + const void* draw(const SkGlyph&, bool isBW, size_t* srcRBPtr); private: HDC fDC; @@ -423,7 +422,6 @@ private: HFONT fFont; XFORM fXform; void* fBits; // points into fBM - COLORREF fColor; int fWidth; int fHeight; bool fIsBW; @@ -436,7 +434,7 @@ private: }; const void* HDCOffscreen::draw(const SkGlyph& glyph, bool isBW, - SkGdiRGB fgColor, size_t* srcRBPtr) { + size_t* srcRBPtr) { if (0 == fDC) { fDC = CreateCompatibleDC(0); if (0 == fDC) { @@ -446,7 +444,10 @@ const void* HDCOffscreen::draw(const SkGlyph& glyph, bool isBW, SetBkMode(fDC, TRANSPARENT); SetTextAlign(fDC, TA_LEFT | TA_BASELINE); SelectObject(fDC, fFont); - fColor = kInvalid_Color; + + COLORREF color = 0x00FFFFFF; + COLORREF prev = SetTextColor(fDC, color); + SkASSERT(prev != CLR_INVALID); } if (fBM && (fIsBW != isBW || fWidth < glyph.fWidth || fHeight < glyph.fHeight)) { @@ -455,16 +456,6 @@ const void* HDCOffscreen::draw(const SkGlyph& glyph, bool isBW, } fIsBW = isBW; - COLORREF color = fgColor; - if (fIsBW) { - color = 0xFFFFFF; - } - if (fColor != color) { - fColor = color; - COLORREF prev = SetTextColor(fDC, color); - SkASSERT(prev != CLR_INVALID); - } - fWidth = SkMax32(fWidth, glyph.fWidth); fHeight = SkMax32(fHeight, glyph.fHeight); @@ -498,8 +489,7 @@ const void* HDCOffscreen::draw(const SkGlyph& glyph, bool isBW, // erase size_t srcRB = isBW ? (biWidth >> 3) : (fWidth << 2); size_t size = fHeight * srcRB; - unsigned bg = (0 == color) ? 0xFF : 0; - memset(fBits, bg, size); + memset(fBits, 0, size); XFORM xform = fXform; xform.eDx = (float)-glyph.fLeft; @@ -525,14 +515,13 @@ public: virtual ~SkScalerContext_Windows(); protected: - virtual unsigned generateGlyphCount(); - virtual uint16_t generateCharToGlyph(SkUnichar uni); - virtual void generateAdvance(SkGlyph* glyph); - virtual void generateMetrics(SkGlyph* glyph); - virtual void generateImage(const SkGlyph& glyph); - virtual void generatePath(const SkGlyph& glyph, SkPath* path); - virtual void generateFontMetrics(SkPaint::FontMetrics* mX, - SkPaint::FontMetrics* mY); + virtual unsigned generateGlyphCount() SK_OVERRIDE; + virtual uint16_t generateCharToGlyph(SkUnichar uni) SK_OVERRIDE; + virtual void generateAdvance(SkGlyph* glyph) SK_OVERRIDE; + virtual void generateMetrics(SkGlyph* glyph) SK_OVERRIDE; + virtual void generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) SK_OVERRIDE; + virtual void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE; + virtual void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY) SK_OVERRIDE; private: HDCOffscreen fOffscreen; @@ -914,59 +903,82 @@ void SkScalerContext_Windows::generateFontMetrics(SkPaint::FontMetrics* mx, SkPa static void build_power_table(uint8_t table[], float ee) { for (int i = 0; i < 256; i++) { float x = i / 255.f; - x = powf(x, ee); + x = sk_float_pow(x, ee); int xx = SkScalarRound(SkFloatToScalar(x * 255)); table[i] = SkToU8(xx); } } -// This will invert the gamma applied by GDI, so we can sort-of get linear values. -// Needed when we draw non-black, non-white text, and don't know how to bias it. -static const uint8_t* getInverseGammaTable() { +/** + * This will invert the gamma applied by GDI (gray-scale antialiased), so we + * can get linear values. + * + * GDI grayscale appears to use a hard-coded gamma of 2.3. + * + * GDI grayscale appears to draw using the black and white rasterizer at four + * times the size and then downsamples to compute the coverage mask. As a + * result there are only seventeen total grays. This lack of fidelity means + * that shifting into other color spaces is imprecise. + */ +static const uint8_t* getInverseGammaTableGDI() { + static bool gInited; + static uint8_t gTableGdi[256]; + if (!gInited) { + build_power_table(gTableGdi, 2.3f); + gInited = true; + } + return gTableGdi; +} + +/** + * This will invert the gamma applied by GDI ClearType, so we can get linear + * values. + * + * GDI ClearType uses SPI_GETFONTSMOOTHINGCONTRAST / 1000 as the gamma value. + * If this value is not specified, the default is a gamma of 1.4. + */ +static const uint8_t* getInverseGammaTableClearType() { static bool gInited; - static uint8_t gTable[256]; + static uint8_t gTableClearType[256]; if (!gInited) { UINT level = 0; if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &level, 0) || !level) { // can't get the data, so use a default level = 1400; } - build_power_table(gTable, level / 1000.0f); + build_power_table(gTableClearType, level / 1000.0f); gInited = true; } - return gTable; + return gTableClearType; } #include "SkColorPriv.h" -// gdi's bitmap is upside-down, so we reverse dst walking in Y -// whenever we copy it into skia's buffer - -static int compute_luminance(int r, int g, int b) { -// return (r * 2 + g * 5 + b) >> 3; - return (r * 27 + g * 92 + b * 9) >> 7; -} - -static inline uint8_t rgb_to_a8(SkGdiRGB rgb) { - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = (rgb >> 0) & 0xFF; - return compute_luminance(r, g, b); +template +static inline uint8_t rgb_to_a8(SkGdiRGB rgb, const uint8_t* table8) { + SkASSERT( ((rgb >> 16) & 0xFF) == ((rgb >> 8) & 0xFF) && + ((rgb >> 16) & 0xFF) == ((rgb >> 0) & 0xFF) ); + return sk_apply_lut_if((rgb >> 16) & 0xFF, table8); } -static inline uint16_t rgb_to_lcd16(SkGdiRGB rgb) { - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = (rgb >> 0) & 0xFF; - return SkPackRGB16(SkR32ToR16(r), SkG32ToG16(g), SkB32ToB16(b)); +template +static inline uint16_t rgb_to_lcd16(SkGdiRGB rgb, const uint8_t* tableR, + const uint8_t* tableG, + const uint8_t* tableB) { + U8CPU r = sk_apply_lut_if((rgb >> 16) & 0xFF, tableR); + U8CPU g = sk_apply_lut_if((rgb >> 8) & 0xFF, tableG); + U8CPU b = sk_apply_lut_if((rgb >> 0) & 0xFF, tableB); + return SkPack888ToRGB16(r, g, b); } -static inline SkPMColor rgb_to_lcd32(SkGdiRGB rgb) { - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = (rgb >> 0) & 0xFF; - int a = SkMax32(r, SkMax32(g, b)); - return SkPackARGB32(a, r, g, b); +template +static inline SkPMColor rgb_to_lcd32(SkGdiRGB rgb, const uint8_t* tableR, + const uint8_t* tableG, + const uint8_t* tableB) { + U8CPU r = sk_apply_lut_if((rgb >> 16) & 0xFF, tableR); + U8CPU g = sk_apply_lut_if((rgb >> 8) & 0xFF, tableG); + U8CPU b = sk_apply_lut_if((rgb >> 0) & 0xFF, tableB); + return SkPackARGB32(0xFF, r, g, b); } // Is this GDI color neither black nor white? If so, we have to keep this @@ -993,8 +1005,10 @@ static bool is_rgb_really_bw(const SkGdiRGB* src, int width, int height, int src return true; } +// gdi's bitmap is upside-down, so we reverse dst walking in Y +// whenever we copy it into skia's buffer static void rgb_to_bw(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, - const SkGlyph& glyph, int32_t xorMask) { + const SkGlyph& glyph) { const int width = glyph.fWidth; const size_t dstRB = (width + 7) >> 3; uint8_t* SK_RESTRICT dst = (uint8_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB); @@ -1010,14 +1024,14 @@ static void rgb_to_bw(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, if (byteCount > 0) { for (int i = 0; i < byteCount; ++i) { unsigned byte = 0; - byte |= (src[0] ^ xorMask) & (1 << 7); - byte |= (src[1] ^ xorMask) & (1 << 6); - byte |= (src[2] ^ xorMask) & (1 << 5); - byte |= (src[3] ^ xorMask) & (1 << 4); - byte |= (src[4] ^ xorMask) & (1 << 3); - byte |= (src[5] ^ xorMask) & (1 << 2); - byte |= (src[6] ^ xorMask) & (1 << 1); - byte |= (src[7] ^ xorMask) & (1 << 0); + byte |= src[0] & (1 << 7); + byte |= src[1] & (1 << 6); + byte |= src[2] & (1 << 5); + byte |= src[3] & (1 << 4); + byte |= src[4] & (1 << 3); + byte |= src[5] & (1 << 2); + byte |= src[6] & (1 << 1); + byte |= src[7] & (1 << 0); dst[i] = byte; src += 8; } @@ -1026,7 +1040,7 @@ static void rgb_to_bw(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, unsigned byte = 0; unsigned mask = 0x80; for (int i = 0; i < bitCount; i++) { - byte |= (src[i] ^ xorMask) & mask; + byte |= src[i] & mask; mask >>= 1; } dst[byteCount] = byte; @@ -1036,48 +1050,51 @@ static void rgb_to_bw(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, } } +template static void rgb_to_a8(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, - const SkGlyph& glyph, int32_t xorMask) { + const SkGlyph& glyph, const uint8_t* table8) { const size_t dstRB = glyph.rowBytes(); const int width = glyph.fWidth; uint8_t* SK_RESTRICT dst = (uint8_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB); for (int y = 0; y < glyph.fHeight; y++) { for (int i = 0; i < width; i++) { - dst[i] = rgb_to_a8(src[i] ^ xorMask); + dst[i] = rgb_to_a8(src[i], table8); } src = SkTAddByteOffset(src, srcRB); dst -= dstRB; } } -static void rgb_to_lcd16(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, - const SkGlyph& glyph, int32_t xorMask) { +template +static void rgb_to_lcd16(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, const SkGlyph& glyph, + const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) { const size_t dstRB = glyph.rowBytes(); const int width = glyph.fWidth; uint16_t* SK_RESTRICT dst = (uint16_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB); for (int y = 0; y < glyph.fHeight; y++) { for (int i = 0; i < width; i++) { - dst[i] = rgb_to_lcd16(src[i] ^ xorMask); + dst[i] = rgb_to_lcd16(src[i], tableR, tableG, tableB); } src = SkTAddByteOffset(src, srcRB); dst = (uint16_t*)((char*)dst - dstRB); } } -static void rgb_to_lcd32(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, - const SkGlyph& glyph, int32_t xorMask) { +template +static void rgb_to_lcd32(const SkGdiRGB* SK_RESTRICT src, size_t srcRB, const SkGlyph& glyph, + const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) { const size_t dstRB = glyph.rowBytes(); const int width = glyph.fWidth; - SkPMColor* SK_RESTRICT dst = (SkPMColor*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB); + uint32_t* SK_RESTRICT dst = (uint32_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB); for (int y = 0; y < glyph.fHeight; y++) { for (int i = 0; i < width; i++) { - dst[i] = rgb_to_lcd32(src[i] ^ xorMask); + dst[i] = rgb_to_lcd32(src[i], tableR, tableG, tableB); } src = SkTAddByteOffset(src, srcRB); - dst = (SkPMColor*)((char*)dst - dstRB); + dst = (uint32_t*)((char*)dst - dstRB); } } @@ -1086,46 +1103,44 @@ static inline unsigned clamp255(unsigned x) { return x - (x >> 8); } -#define WHITE_LUMINANCE_LIMIT 0xA0 -#define BLACK_LUMINANCE_LIMIT 0x40 - -void SkScalerContext_Windows::generateImage(const SkGlyph& glyph) { - SkAutoMutexAcquire ac(gFTMutex); - +void SkScalerContext_Windows::generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) { + SkAutoMutexAcquire ac(gFTMutex); SkASSERT(fDDC); + //Must be careful not to use these if maskPreBlend == NULL + const uint8_t* tableR = NULL; + const uint8_t* tableG = NULL; + const uint8_t* tableB = NULL; + if (maskPreBlend) { + tableR = maskPreBlend->fR; + tableG = maskPreBlend->fG; + tableB = maskPreBlend->fB; + } + const bool isBW = SkMask::kBW_Format == fRec.fMaskFormat; const bool isAA = !isLCD(fRec); - bool isWhite = fRec.getLuminanceByte() >= WHITE_LUMINANCE_LIMIT; - bool isBlack = fRec.getLuminanceByte() <= BLACK_LUMINANCE_LIMIT; - - SkGdiRGB fgColor; - uint32_t rgbXOR; - const uint8_t* table = NULL; - if (isBW || isWhite) { - fgColor = 0x00FFFFFF; - rgbXOR = 0; - } else if (isBlack) { - fgColor = 0; - rgbXOR = ~0; - } else { - table = getInverseGammaTable(); - fgColor = 0x00FFFFFF; - rgbXOR = 0; - } size_t srcRB; - const void* bits = fOffscreen.draw(glyph, isBW, fgColor, &srcRB); + const void* bits = fOffscreen.draw(glyph, isBW, &srcRB); if (NULL == bits) { ensure_typeface_accessible(fRec.fFontID); - bits = fOffscreen.draw(glyph, isBW, fgColor, &srcRB); + bits = fOffscreen.draw(glyph, isBW, &srcRB); if (NULL == bits) { sk_bzero(glyph.fImage, glyph.computeImageSize()); return; } } - if (table) { + if (!isBW) { + const uint8_t* table = getInverseGammaTableClearType(); + if (isAA) { + table = getInverseGammaTableGDI(); + } + //Note that the following cannot really be integrated into the + //pre-blend, since we may not be applying the pre-blend; when we aren't + //applying the pre-blend it means that a filter wants linear anyway. + //Other code may also be applying the pre-blend, so we'd need another + //one with this and one without. SkGdiRGB* addr = (SkGdiRGB*)bits; for (int y = 0; y < glyph.fHeight; ++y) { for (int x = 0; x < glyph.fWidth; ++x) { @@ -1152,18 +1167,30 @@ void SkScalerContext_Windows::generateImage(const SkGlyph& glyph) { // since the caller may require A8 for maskfilters, we can't check for BW // ... until we have the caller tell us that explicitly const SkGdiRGB* src = (const SkGdiRGB*)bits; - rgb_to_a8(src, srcRB, glyph, rgbXOR); + if (maskPreBlend) { + rgb_to_a8(src, srcRB, glyph, tableG); + } else { + rgb_to_a8(src, srcRB, glyph, tableG); + } } else { // LCD16 const SkGdiRGB* src = (const SkGdiRGB*)bits; if (is_rgb_really_bw(src, width, glyph.fHeight, srcRB)) { - rgb_to_bw(src, srcRB, glyph, rgbXOR); + rgb_to_bw(src, srcRB, glyph); ((SkGlyph*)&glyph)->fMaskFormat = SkMask::kBW_Format; } else { if (SkMask::kLCD16_Format == glyph.fMaskFormat) { - rgb_to_lcd16(src, srcRB, glyph, rgbXOR); + if (maskPreBlend) { + rgb_to_lcd16(src, srcRB, glyph, tableR, tableG, tableB); + } else { + rgb_to_lcd16(src, srcRB, glyph, tableR, tableG, tableB); + } } else { SkASSERT(SkMask::kLCD32_Format == glyph.fMaskFormat); - rgb_to_lcd32(src, srcRB, glyph, rgbXOR); + if (maskPreBlend) { + rgb_to_lcd32(src, srcRB, glyph, tableR, tableG, tableB); + } else { + rgb_to_lcd32(src, srcRB, glyph, tableR, tableG, tableB); + } } } } @@ -1665,21 +1692,6 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec) { #endif rec->setHinting(h); - // for compatibility at the moment, discretize luminance to 3 settings - // black, white, gray. This helps with fontcache utilization, since we - // won't create multiple entries that in the end map to the same results. - { - unsigned lum = rec->getLuminanceByte(); - if (lum <= BLACK_LUMINANCE_LIMIT) { - lum = 0; - } else if (lum >= WHITE_LUMINANCE_LIMIT) { - lum = SkScalerContext::kLuminance_Max; - } else { - lum = SkScalerContext::kLuminance_Max >> 1; - } - rec->setLuminanceBits(lum); - } - // turn this off since GDI might turn A8 into BW! Need a bigger fix. #if 0 // Disable LCD when rotated, since GDI's output is ugly diff --git a/src/ports/sk_predefined_gamma.h b/src/ports/sk_predefined_gamma.h deleted file mode 100644 index d363594..0000000 --- a/src/ports/sk_predefined_gamma.h +++ /dev/null @@ -1,51 +0,0 @@ - -/* - * Copyright 2011 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ -#ifndef SK_PREDEFINED_GAMMA_H -#define SK_PREDEFINED_GAMMA_H - -// Gamma table for 1.4 -static const uint8_t gBlackGamma[] = { - 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, - 0x05, 0x06, 0x06, 0x07, 0x07, 0x08, 0x08, 0x09, 0x09, 0x0A, 0x0A, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D, - 0x0E, 0x0F, 0x0F, 0x10, 0x10, 0x11, 0x12, 0x12, 0x13, 0x14, 0x14, 0x15, 0x16, 0x16, 0x17, 0x18, - 0x19, 0x19, 0x1A, 0x1B, 0x1C, 0x1C, 0x1D, 0x1E, 0x1F, 0x1F, 0x20, 0x21, 0x22, 0x22, 0x23, 0x24, - 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x31, - 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, - 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, - 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, - 0x61, 0x62, 0x63, 0x64, 0x65, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, - 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x84, - 0x85, 0x86, 0x87, 0x88, 0x89, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x91, 0x92, 0x93, 0x94, 0x95, 0x97, - 0x98, 0x99, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA3, 0xA4, 0xA5, 0xA6, 0xA8, 0xA9, 0xAA, - 0xAB, 0xAD, 0xAE, 0xAF, 0xB0, 0xB2, 0xB3, 0xB4, 0xB5, 0xB7, 0xB8, 0xB9, 0xBB, 0xBC, 0xBD, 0xBE, - 0xC0, 0xC1, 0xC2, 0xC4, 0xC5, 0xC6, 0xC8, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF, 0xD1, 0xD2, 0xD3, - 0xD5, 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDD, 0xDE, 0xDF, 0xE1, 0xE2, 0xE3, 0xE5, 0xE6, 0xE8, 0xE9, - 0xEA, 0xEC, 0xED, 0xEE, 0xF0, 0xF1, 0xF2, 0xF4, 0xF5, 0xF7, 0xF8, 0xF9, 0xFB, 0xFC, 0xFE, 0xFF, -}; - -// Gamma table for 0.714286 -static const uint8_t gWhiteGamma[] = { - 0x00, 0x05, 0x08, 0x0B, 0x0D, 0x0F, 0x12, 0x14, 0x16, 0x17, 0x19, 0x1B, 0x1D, 0x1E, 0x20, 0x22, - 0x23, 0x25, 0x26, 0x28, 0x29, 0x2B, 0x2C, 0x2E, 0x2F, 0x31, 0x32, 0x33, 0x35, 0x36, 0x37, 0x39, - 0x3A, 0x3B, 0x3C, 0x3E, 0x3F, 0x40, 0x41, 0x43, 0x44, 0x45, 0x46, 0x48, 0x49, 0x4A, 0x4B, 0x4C, - 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, - 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, - 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, - 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, - 0x8E, 0x8F, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x97, 0x98, 0x99, 0x9A, 0x9B, - 0x9C, 0x9D, 0x9E, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, - 0xAA, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB4, 0xB5, 0xB6, - 0xB7, 0xB8, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC0, 0xC1, 0xC2, 0xC3, - 0xC4, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCC, 0xCD, 0xCE, 0xCF, 0xCF, - 0xD0, 0xD1, 0xD2, 0xD3, 0xD3, 0xD4, 0xD5, 0xD6, 0xD6, 0xD7, 0xD8, 0xD9, 0xD9, 0xDA, 0xDB, 0xDC, - 0xDC, 0xDD, 0xDE, 0xDF, 0xDF, 0xE0, 0xE1, 0xE2, 0xE2, 0xE3, 0xE4, 0xE5, 0xE5, 0xE6, 0xE7, 0xE8, - 0xE8, 0xE9, 0xEA, 0xEB, 0xEB, 0xEC, 0xED, 0xEE, 0xEE, 0xEF, 0xF0, 0xF1, 0xF1, 0xF2, 0xF3, 0xF3, - 0xF4, 0xF5, 0xF6, 0xF6, 0xF7, 0xF8, 0xF9, 0xF9, 0xFA, 0xFB, 0xFB, 0xFC, 0xFD, 0xFE, 0xFE, 0xFF, -}; - -#endif