Change default LCD filter to be normalized and color-balanced. Update documentation. * src/base/ftlcdfil.c (FT_Library_SetLcdFilter): Update `default_filter'.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
diff --git a/ChangeLog b/ChangeLog
index e188de7..ee204c2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2015-11-28 Nikolaus Waxweiler <madigens@gmail.com>
+
+ 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 <wl@gnu.org>
[docmaker] Allow references to section names.
diff --git a/include/freetype/freetype.h b/include/freetype/freetype.h
index c239f1b..c0e5112 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. */
/* */
/* <Values> */
/* 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. */
+ /* */
/* <InOut> */
/* 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 ff72e5e..d0f6445 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*
- *
- * 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.
+ * *Experimental* *only,* *requires* *linear* *alpha* *blending* *and*
+ * *gamma* *correction*
+ *
+ * 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 3a62528..91facf9 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
- *
- * {
- * dst = alpha * src + (1 - alpha) * dst ,
- * }
- *
- * 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.
- *
- * 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
- *
- * {
- * [a - c, a + c, 2 * a, a + c, a - c] .
- * }
- *
- * 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, 0x50, 0x60, 0x50, 0x10] ,
- * }
- *
- * 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.
+ * A filter should have two properties:
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Examples:
+ *
+ * - [0x10 0x40 0x70 0x40 0x10] is beveled and neither balanced nor
+ * normalized.
+ *
+ * - [0x1A 0x33 0x4D 0x33 0x1A] is beveled and balanced but not
+ * normalized.
+ *
+ * - [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 b50383c..5ee7e0a 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 )