diff options
author | bungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2012-07-30 20:40:50 +0000 |
---|---|---|
committer | bungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2012-07-30 20:40:50 +0000 |
commit | 97efada074e4806479f1350ab1508939c2fdcb53 (patch) | |
tree | 374659467fad6bb854be612ec5bcb8398a44c97a /src/ports | |
parent | fa3d892f2e3beba39ccb9957be78aad529521740 (diff) |
Gamma correcting masks.
https://codereview.appspot.com/6244068/
git-svn-id: http://skia.googlecode.com/svn/trunk@4841 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'src/ports')
-rw-r--r-- | src/ports/SkFontHost_FreeType.cpp | 206 | ||||
-rw-r--r-- | src/ports/SkFontHost_gamma.cpp | 107 | ||||
-rw-r--r-- | src/ports/SkFontHost_gamma_none.cpp | 23 | ||||
-rw-r--r-- | src/ports/SkFontHost_mac_coretext.cpp | 555 | ||||
-rwxr-xr-x | src/ports/SkFontHost_win.cpp | 256 | ||||
-rw-r--r-- | src/ports/sk_predefined_gamma.h | 51 |
6 files changed, 394 insertions, 804 deletions
diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp index 0d72fa1dc5..497a259a5b 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<bool APPLY_PREBLEND> 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<APPLY_PREBLEND>(*srcR++, tableR), + sk_apply_lut_if<APPLY_PREBLEND>(*srcG++, tableG), + sk_apply_lut_if<APPLY_PREBLEND>(*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<APPLY_PREBLEND>(triple[2], tableR), + sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG), + sk_apply_lut_if<APPLY_PREBLEND>(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<APPLY_PREBLEND>(triple[0], tableR), + sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG), + sk_apply_lut_if<APPLY_PREBLEND>(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<true>(glyph, fFace->glyph->bitmap, doBGR, doVert, + tableR, tableG, tableB); + } else { + copyFT2LCD16<false>(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<true>(glyph, fFace->glyph->bitmap, doBGR, doVert, + tableR, tableG, tableB); + } else { + copyFT2LCD16<false>(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 0d154142fc..0000000000 --- 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 <math.h> - -// 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 18f113c888..0000000000 --- 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 cdd4a74d7e..c723c8848b 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<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>((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<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>(cgPixels[i], table8); } - src = (CGRGBPixel*)((char*)src + rowBytes); - dst = (CGRGBPixel*)((char*)dst + rowBytes); + cgPixels = (CGRGBPixel*)((char*)cgPixels + cgRowBytes); + dst += dstRB; + } +} + +template<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR); + U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG); + U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 0) & 0xFF, tableB); + return SkPack888ToRGB16(r, g, b); +} +template<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>(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<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR); + U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG); + U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((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<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>(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 <typename T> 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<true>(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB); + } else { + rgb_to_lcd32<false>(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB); + } + } break; + case SkMask::kLCD16_Format: { + if (maskPreBlend) { + rgb_to_lcd16<true>(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB); + } else { + rgb_to_lcd16<false>(cgPixels, cgRowBytes, glyph, tableR, tableG, tableB); + } + } break; + case SkMask::kA8_Format: { + if (maskPreBlend) { + rgb_to_a8<true>(cgPixels, cgRowBytes, glyph, tableG); + } else { + rgb_to_a8<false>(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 082e6e48aa..fbe4bb13e6 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<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>((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<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR); + U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG); + U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((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<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR); + U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG); + U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((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<bool APPLY_PREBLEND> 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<APPLY_PREBLEND>(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<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>(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<bool APPLY_PREBLEND> +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<APPLY_PREBLEND>(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<true>(src, srcRB, glyph, tableG); + } else { + rgb_to_a8<false>(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<true>(src, srcRB, glyph, tableR, tableG, tableB); + } else { + rgb_to_lcd16<false>(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<true>(src, srcRB, glyph, tableR, tableG, tableB); + } else { + rgb_to_lcd32<false>(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 d363594900..0000000000 --- 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 |