From 01ce1c6a9959b745c40707970c71fb971dfafa37 Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Sat, 28 Nov 2015 12:04:28 +0100 Subject: [PATCH] Change default LCD filter to be normalized and color-balanced. Update documentation. * src/base/ftlcdfil.c (FT_Library_SetLcdFilter): Update `default_filter'. --- ChangeLog | 7 +++ include/freetype/freetype.h | 92 +++++++++++++++++++++++++++++-- include/freetype/ftautoh.h | 37 +++++++------ include/freetype/ftlcdfil.h | 107 +++++++++++++++++++++++------------- src/base/ftlcdfil.c | 6 +- 5 files changed, 185 insertions(+), 64 deletions(-) diff --git a/ChangeLog b/ChangeLog index e188de7c2..ee204c25e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2015-11-28 Nikolaus Waxweiler + + Change default LCD filter to be normalized and color-balanced. + + * src/base/ftlcdfil.c (FT_Library_SetLcdFilter): Update + `default_filter'. + 2015-11-28 Werner Lemberg [docmaker] Allow references to section names. diff --git a/include/freetype/freetype.h b/include/freetype/freetype.h index c239f1b81..c0e5112ac 100644 --- a/include/freetype/freetype.h +++ b/include/freetype/freetype.h @@ -2828,9 +2828,14 @@ FT_BEGIN_HEADER * @FT_LOAD_TARGET_MONO instead. * * FT_LOAD_TARGET_LIGHT :: - * A lighter hinting algorithm for non-monochrome modes. Many - * generated glyphs are more fuzzy but better resemble its original - * shape. A bit like rendering on Mac OS~X. + * A lighter hinting algorithm for gray-level modes. Many generated + * glyphs are fuzzier but better resemble their original shape. This + * is achieved by snapping glyphs to the pixel grid only vertically + * (Y-axis), as is done by Microsoft's ClearType and Adobe's + * proprietary font renderer. This preserves inter-glyph spacing in + * horizontal text. The snapping is done either by the native font + * driver if the driver itself and the font support it or by the + * auto-hinter. * * FT_LOAD_TARGET_MONO :: * Strong hinting algorithm that should only be used for monochrome @@ -2937,7 +2942,10 @@ FT_BEGIN_HEADER /* field in the @FT_GlyphSlotRec structure gives the format of the */ /* returned bitmap. */ /* */ - /* All modes except @FT_RENDER_MODE_MONO use 256 levels of opacity. */ + /* All modes except @FT_RENDER_MODE_MONO use 256 levels of opacity, */ + /* indicating pixel coverage. Use linear alpha blending and gamma */ + /* correction to correctly render non-monochrome glyph bitmaps onto a */ + /* surface; see @FT_Render_Glyph. */ /* */ /* */ /* FT_RENDER_MODE_NORMAL :: */ @@ -3007,6 +3015,82 @@ FT_BEGIN_HEADER /* the glyph image format, finding the relevant renderer, and */ /* invoking it. */ /* */ + /* When FreeType outputs a bitmap of a glyph, it really outputs an */ + /* alpha coverage map. If a pixel is completely covered by a */ + /* filled-in outline, the bitmap contains 0xFF at that pixel, meaning */ + /* that 0xFF/0xFF fraction of that pixel is covered, meaning the */ + /* pixel is 100% black (or 0% bright). If a pixel is only 50% */ + /* covered (value 0x80), the pixel is made 50% black (50% bright or a */ + /* middle shade of grey). 0% covered means 0% black (100% bright or */ + /* white). */ + /* */ + /* On high-DPI screens like on smartphones and tablets, the pixels */ + /* are so small that their chance of being completely covered and */ + /* therefore completely black are fairly good. On the low-DPI */ + /* screens, however, the situation is different. The pixels are too */ + /* large for most of the details of a glyph and shades of gray are */ + /* the norm rather than the exception. */ + /* */ + /* This is relevant because all our screens have a second problem: */ + /* they are not linear. 1~+~1 is not~2. Twice the value does not */ + /* result in twice the brightness. When a pixel is only 50% covered, */ + /* the coverage map says 50% black, and this translates to a pixel */ + /* value of 128 when you use 8~bits per channel (0-255). However, */ + /* this does not translate to 50% brightness for that pixel on our */ + /* sRGB and gamma~2.2 screens. Due to their non-linearity, they */ + /* dwell longer in the darks and only a pixel value of about 186 */ + /* results in 50% brightness – 128 ends up too dark on both bright */ + /* and dark backgrounds. The net result is that dark text looks */ + /* burnt-out, pixely and blotchy on bright background, bright text */ + /* too frail on dark backgrounds, and colored text on colored */ + /* background (for example, red on green) seems to have dark halos or */ + /* `dirt' around it. The situation is especially ugly for diagonal */ + /* stems like in `w' glyph shapes where the quality of FreeType's */ + /* anti-aliasing depends on the correct display of grays. On */ + /* high-DPI screens where smaller, fully black pixels reign supreme, */ + /* this doesn't matter, but on our low-DPI screens with all the gray */ + /* shades, it does. 0% and 100% brightness are the same things in */ + /* linear and non-linear space, just all the shades in-between */ + /* aren't. */ + /* */ + /* The blending function for placing text over a background is */ + /* */ + /* { */ + /* dst = alpha * src + (1 - alpha) * dst , */ + /* } */ + /* */ + /* which is known as the OVER operator. */ + /* */ + /* To correctly composite an antialiased pixel of a glyph onto a */ + /* surface, */ + /* */ + /* 1. take the foreground and background colors (e.g., in sRGB space) */ + /* and apply gamma to get them in a linear space, */ + /* */ + /* 2. use OVER to blend the two linear colors using the glyph pixel */ + /* as the alpha value (remember, the glyph bitmap is a coverage */ + /* bitmap), and */ + /* */ + /* 3. apply inverse gamma to the blended pixel and write it back to */ + /* the image. */ + /* */ + /* Internal testing at Adobe found that a target inverse gamma of~1.8 */ + /* for step~3 gives good results across a wide range of displays with */ + /* an sRGB gamma curve or a similar one. */ + /* */ + /* This process can cost performance. There is an approximation that */ + /* does not need to know about the background color; see */ + /* https://bel.fi/alankila/lcd/ and */ + /* https://bel.fi/alankila/lcd/alpcor.html for details. */ + /* */ + /* *ATTENTION*: Linear blending is even more important when dealing */ + /* with subpixel-rendered glyphs to prevent color-fringing! A */ + /* subpixel-rendered glyph must first be filtered with a filter that */ + /* gives equal weight to the three color primaries and does not */ + /* exceed a sum of 0x100, see section @lcd_filtering. Then the */ + /* only difference to gray linear blending is that subpixel-rendered */ + /* linear blending is done 3~times per pixel. */ + /* */ /* */ /* slot :: A handle to the glyph slot containing the image to */ /* convert. */ diff --git a/include/freetype/ftautoh.h b/include/freetype/ftautoh.h index ff72e5ebb..d0f6445ed 100644 --- a/include/freetype/ftautoh.h +++ b/include/freetype/ftautoh.h @@ -445,13 +445,26 @@ FT_BEGIN_HEADER * no-stem-darkening[autofit] * * @description: - * *Experimental* *only* + * *Experimental* *only,* *requires* *linear* *alpha* *blending* *and* + * *gamma* *correction* * - * The main purpose of emboldening glyphs or `stem darkening' is to - * enhance readability at smaller sizes. The smaller the size, the more - * emboldening is applied to keep glyphs from `thinning out'. All - * glyphs that pass through the autohinter will be emboldened unless - * this property is set to TRUE. + * Stem darkening emboldens glyphs at smaller sizes to make them more + * readable on common low-DPI screens when using linear alpha blending + * and gamma correction, see @FT_Render_Glyph. When not using linear + * alpha blending and gamma correction, glyphs will appear heavy and + * fuzzy! + * + * Gamma correction essentially lightens fonts since shades of grey are + * shifted to higher pixel values (=~higher brightness) to match the + * original intention to the reality of our screens. The side-effect is + * that glyphs `thin out'. Mac OS~X and Adobe's proprietary font + * rendering library implement a counter-measure: stem darkening at + * smaller sizes where shades of gray dominate. By emboldening a glyph + * slightly in relation to its pixel size, individual pixels get higher + * coverage of filled-in outlines and are therefore `blacker'. This + * counteracts the `thinning out' of glyphs, making text remain readable + * at smaller sizes. All glyphs that pass through the auto-hinter will + * be emboldened unless this property is set to TRUE. * * See the description of the CFF driver for algorithmic details. Total * consistency with the CFF driver is currently not achieved because the @@ -460,18 +473,6 @@ FT_BEGIN_HEADER * The smaller the size (especially 9ppem and down), the higher the loss * of emboldening versus the CFF driver. * - * *ATTENTION*: This feature has been developed with linear alpha - * blending and gamma correction of glyphs in mind: A rendering library - * must apply linear alpha blending while compositing glyph bitmaps onto - * a surface and then apply gamma correction to the glyph pixels to get - * from linear space to display space (unless the display works in - * linear space). Internal testing at Adobe found that a gamma - * correction value of 1.8 gives good results across a wide range of - * displays with a sRGB gamma curve or a similar one. - * - * If this is not possible, it might be better to disable stem - * darkening. Currently, this can only be done globally. - * */ diff --git a/include/freetype/ftlcdfil.h b/include/freetype/ftlcdfil.h index 3a6252836..91facf92b 100644 --- a/include/freetype/ftlcdfil.h +++ b/include/freetype/ftlcdfil.h @@ -41,56 +41,76 @@ FT_BEGIN_HEADER * LCD Filtering * * @abstract: - * Reduce color fringes of LCD-optimized bitmaps. + * Reduce color fringes of subpixel-rendered bitmaps. * * @description: - * The @FT_Library_SetLcdFilter API can be used to specify a low-pass - * filter, which is then applied to LCD-optimized bitmaps generated - * through @FT_Render_Glyph. This is useful to reduce color fringes - * that would occur with unfiltered rendering. + * Subpixel rendering exploits the color-striped structure of LCD + * pixels, increasing the available resolution in the direction of the + * stripe (usually horizontal RGB) by a factor of~3. Since these + * subpixels are color pixels, using them unfiltered creates severe + * color fringes. Use the @FT_Library_SetLcdFilter API to specify a + * low-pass filter, which is then applied to subpixel-rendered bitmaps + * generated through @FT_Render_Glyph. * * Note that no filter is active by default, and that this function is * *not* implemented in default builds of the library. You need to * #define FT_CONFIG_OPTION_SUBPIXEL_RENDERING in your `ftoption.h' file * in order to activate it. * - * FreeType generates alpha coverage maps, which are linear by nature. - * For instance, the value 0x80 in bitmap representation means that - * (within numerical precision) 0x80/0xFF fraction of that pixel is - * covered by the glyph's outline. The blending function for placing - * text over a background is + * A filter should have two properties: * - * { - * dst = alpha * src + (1 - alpha) * dst , - * } + * 1) It should be normalized, meaning the sum of the 5~components + * should be 256 (0x100). It is possible to go above or under this + * target sum, however: going under means tossing out contrast, going + * over means invoking clamping and thereby non-linearities that + * increase contrast somewhat at the expense of greater distortion + * and color-fringing. Contrast is better enhanced through stem + * darkening. * - * which is known as OVER. However, when calculating the output of the - * OVER operator, the source colors should first be transformed to a - * linear color space, then alpha blended in that space, and transformed - * back to the output color space. + * 2) It should be color-balanced, meaning a filter `{~a, b, c, b, a~}' + * where a~+ b~=~c. It distributes the computed coverage for one + * subpixel to all subpixels equally, sacrificing some won resolution + * but drastically reducing color-fringing. Positioning improvements + * remain! Note that color-fringing can only really be minimized + * when using a color-balanced filter and alpha-blending the glyph + * onto a surface in linear space; see @FT_Render_Glyph. * - * When linear light blending is used, the default FIR5 filtering - * weights (as given by FT_LCD_FILTER_DEFAULT) are no longer optimal, as - * they have been designed for black on white rendering while lacking - * gamma correction. To preserve color neutrality, weights for a FIR5 - * filter should be chosen according to two free parameters `a' and `c', - * and the FIR weights should be + * Regarding the form, a filter can be a `boxy' filter or a `beveled' + * filter. Boxy filters are sharper but are less forgiving of non-ideal + * gamma curves of a screen (viewing angles!), beveled filters are + * fuzzier but more tolerant. * - * { - * [a - c, a + c, 2 * a, a + c, a - c] . - * } + * Examples: * - * This formula generates equal weights for all the color primaries - * across the filter kernel, which makes it colorless. One suggested - * set of weights is + * - [0x10 0x40 0x70 0x40 0x10] is beveled and neither balanced nor + * normalized. * - * { - * [0x10, 0x50, 0x60, 0x50, 0x10] , - * } + * - [0x1A 0x33 0x4D 0x33 0x1A] is beveled and balanced but not + * normalized. * - * where `a' has value 0x30 and `c' value 0x20. The weights in filter - * may have a sum larger than 0x100, which increases coloration slightly - * but also improves contrast. + * - [0x19 0x33 0x66 0x4c 0x19] is beveled and normalized but not + * balanced. + * + * - [0x00 0x4c 0x66 0x4c 0x00] is boxily beveled and normalized but not + * balanced. + * + * - [0x00 0x55 0x56 0x55 0x00] is boxy, normalized, and almost + * balanced. + * + * - [0x08 0x4D 0x56 0x4D 0x08] is beveled, normalized and, almost + * balanced. + * + * It is important to understand that linear alpha blending and gamma + * correction is critical for correctly rendering glyphs onto surfaces + * without artifacts and even more critical when subpixel rendering is + * involved. + * + * Each of the 3~alpha values (subpixels) is independently used to blend + * one color channel. That is, red alpha blends the red channel of the + * text color with the red channel of the background pixel. The + * distribution of density values by the color-balanced filter assumes + * alpha blending is done in linear space; only then color artifacts + * cancel out. */ @@ -111,10 +131,21 @@ FT_BEGIN_HEADER * The default filter reduces color fringes considerably, at the cost * of a slight blurriness in the output. * + * It is a beveled, normalized, and color-balanced five-tap filter + * that is more forgiving to screens with non-ideal gamma curves and + * viewing angles. Note that while color-fringing is reduced, it can + * only be minimized by using linear alpha blending and gamma + * correction to render glyphs onto surfaces. + * * FT_LCD_FILTER_LIGHT :: - * The light filter is a variant that produces less blurriness at the - * cost of slightly more color fringes than the default one. It might - * be better, depending on taste, your monitor, or your personal vision. + * The light filter is a variant that is sharper at the cost of + * slightly more color fringes than the default one. + * + * It is a boxy, normalized, and color-balanced three-tap filter that + * is less forgiving to screens with non-ideal gamma curves and + * viewing angles. This filter works best when the rendering system + * uses linear alpha blending and gamma correction to render glyphs + * onto surfaces. * * FT_LCD_FILTER_LEGACY :: * This filter corresponds to the original libXft color filter. It diff --git a/src/base/ftlcdfil.c b/src/base/ftlcdfil.c index b50383c18..5ee7e0aa2 100644 --- a/src/base/ftlcdfil.c +++ b/src/base/ftlcdfil.c @@ -305,12 +305,10 @@ FT_Library_SetLcdFilter( FT_Library library, FT_LcdFilter filter ) { + static const FT_Byte default_filter[5] = + { 0x08, 0x4d, 0x56, 0x4d, 0x08 }; static const FT_Byte light_filter[5] = { 0x00, 0x55, 0x56, 0x55, 0x00 }; - /* the values here sum up to a value larger than 256, */ - /* providing a cheap gamma correction */ - static const FT_Byte default_filter[5] = - { 0x10, 0x40, 0x70, 0x40, 0x10 }; if ( !library )