ref: 01ce1c6a9959b745c40707970c71fb971dfafa37
parent: 98afe3f5c8490837adc2fc60ae483ad57a76f76e
author: Nikolaus Waxweiler <[email protected]>
date: Sat Nov 28 07:04:28 EST 2015
Change default LCD filter to be normalized and color-balanced. Update documentation. * src/base/ftlcdfil.c (FT_Library_SetLcdFilter): Update `default_filter'.
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2015-11-28 Nikolaus Waxweiler <[email protected]>
+
+ 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 <[email protected]>
[docmaker] Allow references to section names.
--- a/include/freetype/freetype.h
+++ b/include/freetype/freetype.h
@@ -2828,9 +2828,14 @@
* @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 @@
/* 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. */
/* */
/* <Values> */
/* FT_RENDER_MODE_NORMAL :: */
@@ -3006,6 +3014,82 @@
/* Convert a given glyph image to a bitmap. It does so by inspecting */
/* 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. */
/* */
/* <InOut> */
/* slot :: A handle to the glyph slot containing the image to */
--- a/include/freetype/ftautoh.h
+++ b/include/freetype/ftautoh.h
@@ -445,14 +445,27 @@
* 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
* emboldening method differs and glyphs must be scaled down on the
@@ -459,18 +472,6 @@
* Y-axis to keep outline points inside their precomputed blue zones.
* 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.
*
*/
--- a/include/freetype/ftlcdfil.h
+++ b/include/freetype/ftlcdfil.h
@@ -41,13 +41,16 @@
* 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
@@ -54,43 +57,60 @@
* #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 @@
* 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
--- 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 )