implement gamma correction for freetype text.

Need to opt-in to have it applied to kA8 text (which chrome can't for a while)
A8 text needs to use Slight hinting to look better, but that is not forced...



git-svn-id: http://skia.googlecode.com/svn/trunk@3277 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
reed@google.com 2012-02-28 17:06:02 +00:00
Родитель cef7e3fc4b
Коммит 1ac8350821
2 изменённых файлов: 114 добавлений и 44 удалений

Просмотреть файл

@ -78,6 +78,8 @@
'defines': [
'SK_SAMPLES_FOR_X',
'SK_BUILD_FOR_UNIX',
'SK_USE_COLOR_LUMINANCE',
'SK_GAMMA_APPLY_TO_A8',
],
'configurations': {
'Debug': {

Просмотреть файл

@ -54,6 +54,10 @@
//#define ENABLE_GLYPH_SPEW // for tracing calls
//#define DUMP_STRIKE_CREATION
//#define SK_GAMMA_APPLY_TO_A8
#define SK_GAMMA_CONTRAST 0x33
#define SK_GAMMA_EXPONENT 1.8
#ifdef SK_DEBUG
#define SkASSERT_CONTINUE(pred) \
do { \
@ -66,14 +70,6 @@
using namespace skia_advanced_typeface_metrics_utils;
// SK_FREETYPE_LCD_LERP should be 0...256
// 0 means no color reduction (e.g. just as returned from FreeType)
// 256 means 100% color reduction (e.g. gray)
//
#ifndef SK_FREETYPE_LCD_LERP
#define SK_FREETYPE_LCD_LERP 96
#endif
static bool isLCD(const SkScalerContext::Rec& rec) {
switch (rec.fMaskFormat) {
case SkMask::kLCD16_Format:
@ -113,7 +109,8 @@ InitFreetype() {
// Setup LCD filtering. This reduces colour fringes for LCD rendered
// glyphs.
#ifdef FT_LCD_FILTER_H
err = FT_Library_SetLcdFilter(gFTLibrary, FT_LCD_FILTER_DEFAULT);
// err = FT_Library_SetLcdFilter(gFTLibrary, FT_LCD_FILTER_DEFAULT);
err = FT_Library_SetLcdFilter(gFTLibrary, FT_LCD_FILTER_LIGHT);
gLCDSupport = err == 0;
#else
gLCDSupport = false;
@ -630,11 +627,13 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec) {
if (SkPaint::kFull_Hinting == h && !isLCD(*rec)) {
// collapse full->normal hinting if we're not doing LCD
h = SkPaint::kNormal_Hinting;
} else if ((rec->fFlags & SkScalerContext::kSubpixelPositioning_Flag) &&
SkPaint::kNo_Hinting != h) {
// to do subpixel, we must have at most slight hinting
h = SkPaint::kSlight_Hinting;
}
if ((rec->fFlags & SkScalerContext::kSubpixelPositioning_Flag) || isLCD(*rec)) {
if (SkPaint::kNo_Hinting != h) {
h = SkPaint::kSlight_Hinting;
}
}
#ifndef SK_IGNORE_ROTATED_FREETYPE_FIX
// rotated text looks bad with hinting, so we disable it as needed
if (!isAxisAligned(*rec)) {
@ -643,6 +642,7 @@ 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.
@ -662,6 +662,7 @@ void SkFontHost::FilterRec(SkScalerContext::Rec* rec) {
}
rec->setLuminanceBits(lum);
}
#endif
}
#ifdef SK_BUILD_FOR_ANDROID
@ -1038,19 +1039,71 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
#endif
}
static int lerp(int start, int end) {
SkASSERT((unsigned)SK_FREETYPE_LCD_LERP <= 256);
return start + ((end - start) * (SK_FREETYPE_LCD_LERP) >> 8);
///////////////////////////////////////////////////////////////////////////////
static int apply_contrast(int srca, int contrast) {
return srca + (((255 - srca) * contrast * srca) / (255*255));
}
static uint16_t packTriple(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) + b) >> 4;
r = lerp(r, ave);
g = lerp(g, ave);
b = lerp(b, ave);
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);
int xx = SkScalarRoundToInt(SkFloatToScalar(x * 255));
table[i] = SkToU8(xx);
}
}
static void build_gamma_table(uint8_t table[256], int src, int dst) {
static bool gInit;
static uint8_t powTable[256], invPowTable[256];
if (!gInit) {
const float g = SK_GAMMA_EXPONENT;
build_power_table(powTable, g);
build_power_table(invPowTable, 1/g);
gInit = true;
}
int linSrc = powTable[src];
int linDst = powTable[dst];
for (int i = 0; i < 256; ++i) {
int srca = i;
int dsta = 255 - srca;
//Calculate the output we want.
int linOut = (linSrc * srca + dsta * linDst) / 255;
SkASSERT((unsigned)linOut <= 255);
int out = invPowTable[linOut];
//Undo what the blit blend will do.
int result = ((255 * out) - (255 * dst)) / (src - dst);
SkASSERT((unsigned)result <= 255);
result = apply_contrast(result, SK_GAMMA_CONTRAST);
SkASSERT((unsigned)result <= 255);
table[srca] = 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, 0xFF);
build_gamma_table(gGammaTables[1], 0x55, 0xAA);
build_gamma_table(gGammaTables[2], 0xAA, 0x55);
build_gamma_table(gGammaTables[3], 0xFF, 0x00);
gInited = true;
}
SkASSERT(0 == (luminance >> 8));
return gGammaTables[luminance >> 6];
}
static uint16_t packTriple(unsigned r, unsigned g, unsigned b) {
return SkPackRGB16(r >> 3, g >> 2, b >> 3);
}
@ -1066,7 +1119,8 @@ static int bittst(const uint8_t data[], int bitOffset) {
}
static void copyFT2LCD16(const SkGlyph& glyph, const FT_Bitmap& bitmap,
int lcdIsBGR) {
int lcdIsBGR, const uint8_t* tableR,
const uint8_t* tableG, const uint8_t* tableB) {
SkASSERT(glyph.fHeight == bitmap.rows);
uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage);
const size_t dstRB = glyph.rowBytes();
@ -1099,12 +1153,16 @@ static void copyFT2LCD16(const SkGlyph& glyph, const FT_Bitmap& bitmap,
const uint8_t* triple = src;
if (lcdIsBGR) {
for (int x = 0; x < width; x++) {
dst[x] = packTriple(triple[2], triple[1], triple[0]);
dst[x] = packTriple(tableR[triple[2]],
tableG[triple[1]],
tableB[triple[0]]);
triple += 3;
}
} else {
for (int x = 0; x < width; x++) {
dst[x] = packTriple(triple[0], triple[1], triple[2]);
dst[x] = packTriple(tableR[triple[0]],
tableG[triple[1]],
tableB[triple[2]]);
triple += 3;
}
}
@ -1133,6 +1191,18 @@ 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 = getGammaTable(lum);
const uint8_t* tableG = getGammaTable(lum);
const uint8_t* tableB = getGammaTable(lum);
#endif
switch ( fFace->glyph->format ) {
case FT_GLYPH_FORMAT_OUTLINE: {
FT_Outline* outline = &fFace->glyph->outline;
@ -1165,7 +1235,8 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
FT_Render_Glyph(fFace->glyph, FT_RENDER_MODE_LCD);
copyFT2LCD16(glyph, fFace->glyph->bitmap,
fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag);
fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag,
tableR, tableG, tableB);
} else {
target.width = glyph.fWidth;
target.rows = glyph.fHeight;
@ -1231,7 +1302,8 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
}
} else if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
copyFT2LCD16(glyph, fFace->glyph->bitmap,
fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag);
fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag,
tableR, tableG, tableB);
} else {
SkDEBUGFAIL("unknown glyph bitmap transform needed");
}
@ -1242,25 +1314,21 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
goto ERROR;
}
if (gGammaTables[0] || gGammaTables[1]) {
bool isWhite = fRec.getLuminanceByte() >= WHITE_LUMINANCE_LIMIT;
bool isBlack = fRec.getLuminanceByte() <= BLACK_LUMINANCE_LIMIT;
if ((isWhite | isBlack) && SkMask::kA8_Format == glyph.fMaskFormat) {
int index = isBlack ? 0 : 1;
if (gGammaTables[index]) {
const uint8_t* SK_RESTRICT table = gGammaTables[index];
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 += rowBytes;
}
#ifdef SK_GAMMA_APPLY_TO_A8
if (SkMask::kA8_Format == glyph.fMaskFormat) {
SkASSERT(tableR == tableG && tableR == tableB);
const uint8_t* table = tableR;
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 += rowBytes;
}
}
#endif
}
///////////////////////////////////////////////////////////////////////////////