diff --git a/examples/example1/src/example1.d b/examples/example1/src/example1.d index a2b63d97..2f5cd79f 100644 --- a/examples/example1/src/example1.d +++ b/examples/example1/src/example1.d @@ -194,10 +194,12 @@ extern (C) int UIAppMain(string[] args) { Platform.instance.uiTheme = "theme_default"; // you can override default hinting mode here (Normal, AutoHint, Disabled) - FontManager.instance.hintingMode = HintingMode.Normal; + FontManager.hintingMode = HintingMode.Normal; // you can override antialiasing setting here (0 means antialiasing always on, some big value = always off) // fonts with size less than specified value will not be antialiased - FontManager.instance.minAnitialiasedFontSize = 0; // 0 means always antialiased + FontManager.minAnitialiasedFontSize = 0; // 0 means always antialiased + // you can turn on subpixel font rendering (ClearType) here + FontManager.subpixelRenderingMode = SubpixelRenderingMode.None;// SubpixelRenderingMode.BGR; // create window Window window = Platform.instance.createWindow("My Window", null); diff --git a/src/dlangui/core/types.d b/src/dlangui/core/types.d index 5c7622df..421bf017 100644 --- a/src/dlangui/core/types.d +++ b/src/dlangui/core/types.d @@ -197,6 +197,14 @@ int pixelsToPoints(int px) { return px * POINTS_PER_INCH / SCREEN_DPI; } +/// Subpixel rendering mode for fonts (aka ClearType) +enum SubpixelRenderingMode : ubyte { + /// no sub + None, + /// subpixel rendering on, subpixel order: B,G,R + BGR, +} + /** Character glyph. @@ -205,25 +213,27 @@ int pixelsToPoints(int px) { align(1) struct Glyph { + /// 0: width of glyph black box + ushort blackBoxX; + /// 2: height of glyph black box + ubyte blackBoxY; + /// 3: X origin for glyph + byte originX; + /// 4: Y origin for glyph + byte originY; + + /// 5: full width of glyph + ubyte width; + /// 6: subpixel rendering mode - if !=SubpixelRenderingMode.None, glyph data contains 3 bytes per pixel instead of 1 + SubpixelRenderingMode subpixelMode; + /// 7: usage flag, to handle cleanup of unused glyphs + ubyte lastUsage; version (USE_OPENGL) { - ///< 0: unique id of glyph (for drawing in hardware accelerated scenes) + /// 8: unique id of glyph (for drawing in hardware accelerated scenes) uint id; } - ///< 4: width of glyph black box - ubyte blackBoxX; - ///< 5: height of glyph black box - ubyte blackBoxY; - ///< 6: X origin for glyph - byte originX; - ///< 7: Y origin for glyph - byte originY; - - ///< 8: full width of glyph - ubyte width; - ///< 9: usage flag, to handle cleanup of unused glyphs - ubyte lastUsage; - ///< 12: glyph data, arbitrary size + ///< 12: glyph data, arbitrary size (blackBoxX * blackBoxY) ubyte[] glyph; } diff --git a/src/dlangui/graphics/fonts.d b/src/dlangui/graphics/fonts.d index a8c358bb..310f6226 100644 --- a/src/dlangui/graphics/fonts.d +++ b/src/dlangui/graphics/fonts.d @@ -527,6 +527,7 @@ class FontManager { protected static __gshared FontManager _instance; protected static __gshared int _minAnitialiasedFontSize = DEF_MIN_ANTIALIASED_FONT_SIZE; protected static __gshared HintingMode _hintingMode = HintingMode.Normal; + protected static __gshared SubpixelRenderingMode _subpixelRenderingMode = SubpixelRenderingMode.None; /// sets new font manager singleton instance static @property void instance(FontManager manager) { @@ -552,25 +553,35 @@ class FontManager { abstract void cleanup(); /// get min font size for antialiased fonts (0 means antialiasing always on, some big value = always off) - @property int minAnitialiasedFontSize() { + static @property int minAnitialiasedFontSize() { return _minAnitialiasedFontSize; } /// set new min font size for antialiased fonts - fonts with size >= specified value will be antialiased (0 means antialiasing always on, some big value = always off) - @property void minAnitialiasedFontSize(int size) { + static @property void minAnitialiasedFontSize(int size) { _minAnitialiasedFontSize = size; } /// get current hinting mode (Normal, AutoHint, Disabled) - @property HintingMode hintingMode() { + static @property HintingMode hintingMode() { return _hintingMode; } /// set hinting mode (Normal, AutoHint, Disabled) - @property void hintingMode(HintingMode mode) { + static @property void hintingMode(HintingMode mode) { _hintingMode = mode; } + /// get current subpixel rendering mode for fonts (aka ClearType) + static @property SubpixelRenderingMode subpixelRenderingMode() { + return _subpixelRenderingMode; + } + + /// set subpixel rendering mode for fonts (aka ClearType) + static @property void subpixelRenderingMode(SubpixelRenderingMode mode) { + _subpixelRenderingMode = mode; + } + ~this() { Log.d("Destroying font manager"); } diff --git a/src/dlangui/platforms/windows/win32fonts.d b/src/dlangui/platforms/windows/win32fonts.d index 7cc6e193..df3d2ee6 100644 --- a/src/dlangui/platforms/windows/win32fonts.d +++ b/src/dlangui/platforms/windows/win32fonts.d @@ -48,6 +48,100 @@ private struct FontDef { } } +// support of subpixel rendering +// from AntigrainGeometry https://rsdn.ru/forum/src/830679.1 +import std.math; +// Sub-pixel energy distribution lookup table. +// See description by Steve Gibson: http://grc.com/cttech.htm +// The class automatically normalizes the coefficients +// in such a way that primary + 2*secondary + 3*tertiary = 1.0 +// Also, the input values are in range of 0...NumLevels, output ones +// are 0...255 +//--------------------------------- +struct lcd_distribution_lut(int maxv = 65) +{ + this(double prim, double second, double tert) + { + double norm = (255.0 / (maxv - 1)) / (prim + second*2 + tert*2); + prim *= norm; + second *= norm; + tert *= norm; + for(int i = 0; i < maxv; i++) + { + m_primary[i] = cast(ubyte)floor(prim * i); + m_secondary[i] = cast(ubyte)floor(second * i); + m_tertiary[i] = cast(ubyte)floor(tert * i); + } + } + + uint primary(int v) const { + if (v >= maxv) { + Log.e("pixel value returned from font engine > 64: ", v); + v = maxv - 1; + } + return m_primary[v]; + } + uint secondary(int v) const { + if (v >= maxv) { + Log.e("pixel value returned from font engine > 64: ", v); + v = maxv - 1; + } + return m_secondary[v]; + } + uint tertiary(int v) const { + if (v >= maxv) { + Log.e("pixel value returned from font engine > 64: ", v); + v = maxv - 1; + } + return m_tertiary[v]; + } + +private: + ubyte m_primary[maxv]; + ubyte m_secondary[maxv]; + ubyte m_tertiary[maxv]; +}; + +private __gshared lcd_distribution_lut!65 lut; +__gshared static this() { + lut = lcd_distribution_lut!65(0.5, 0.25, 0.125); +} + +// This function prepares the alpha-channel information +// for the glyph averaging the values in accordance with +// the method suggested by Steve Gibson. The function +// extends the width by 4 extra pixels, 2 at the beginning +// and 2 at the end. Also, it doesn't align the new width +// to 4 bytes, that is, the output gm.gmBlackBoxX is the +// actual width of the array. +// returns dst glyph width +//--------------------------------- +ushort prepare_lcd_glyph(ubyte * gbuf1, + ref GLYPHMETRICS gm, + ref ubyte[] gbuf2) +{ + uint src_stride = (gm.gmBlackBoxX + 3) / 4 * 4; + uint dst_width = src_stride + 4; + gbuf2 = new ubyte[dst_width * gm.gmBlackBoxY]; + + for(uint y = 0; y < gm.gmBlackBoxY; ++y) + { + ubyte * src_ptr = gbuf1 + src_stride * y; + ubyte * dst_ptr = gbuf2.ptr + dst_width * y; + uint x; + for(x = 0; x < gm.gmBlackBoxX; ++x) + { + uint v = *src_ptr++; + dst_ptr[0] += lut.tertiary(v); + dst_ptr[1] += lut.secondary(v); + dst_ptr[2] += lut.primary(v); + dst_ptr[3] += lut.secondary(v); + dst_ptr[4] += lut.tertiary(v); + ++dst_ptr; + } + } + return cast(ushort) dst_width; +} /** * Font implementation based on Win32 API system fonts. @@ -133,14 +227,21 @@ class Win32Font : Font { return null; GLYPHMETRICS metrics; - MAT2 identity = { {0,1}, {0,0}, {0,0}, {0,1} }; + bool needSubpixelRendering = FontManager.subpixelRenderingMode && antialiased; + MAT2 scaleMatrix = { {0,1}, {0,0}, {0,0}, {0,1} }; + int xdivider = 1; + if (needSubpixelRendering) { + scaleMatrix.eM11.value = 3; // request glyph 3 times wider for subpixel antialiasing + xdivider = 3; + } + uint res; res = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch, GGO_METRICS, &metrics, 0, null, - &identity ); + &scaleMatrix ); if (res == GDI_ERROR) return null; int gs = 0; @@ -151,14 +252,14 @@ class Win32Font : Font { &metrics, 0, NULL, - &identity ); + &scaleMatrix ); } else { gs = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch, GGO_BITMAP, &metrics, 0, NULL, - &identity ); + &scaleMatrix ); } if (gs >= 0x10000 || gs < 0) @@ -168,15 +269,15 @@ class Win32Font : Font { version (USE_OPENGL) { g.id = nextGlyphId(); } - g.blackBoxX = cast(ubyte)metrics.gmBlackBoxX; + g.blackBoxX = cast(ushort)metrics.gmBlackBoxX; g.blackBoxY = cast(ubyte)metrics.gmBlackBoxY; - g.originX = cast(byte)metrics.gmptGlyphOrigin.x; + g.originX = cast(byte)(needSubpixelRendering ? metrics.gmptGlyphOrigin.x / 3: metrics.gmptGlyphOrigin.x); g.originY = cast(byte)metrics.gmptGlyphOrigin.y; - g.width = cast(ubyte)metrics.gmCellIncX; + g.width = cast(ubyte)(needSubpixelRendering ? metrics.gmCellIncX / 3 : metrics.gmCellIncX); + g.subpixelMode = needSubpixelRendering ? FontManager.subpixelRenderingMode : SubpixelRenderingMode.None; //g.glyphIndex = cast(ushort)glyphIndex; - if (g.blackBoxX>0 && g.blackBoxY>0) - { + if (g.blackBoxX > 0 && g.blackBoxY > 0) { g.glyph = new ubyte[g.blackBoxX * g.blackBoxY]; if (gs>0) { @@ -188,27 +289,36 @@ class Win32Font : Font { &metrics, gs, glyph.ptr, - &identity ); + &scaleMatrix); if (res==GDI_ERROR) { return null; } - int glyph_row_size = (g.blackBoxX + 3) / 4 * 4; - ubyte * src = glyph.ptr; - ubyte * dst = g.glyph.ptr; - for (int y = 0; y < g.blackBoxY; y++) - { - for (int x = 0; x < g.blackBoxX; x++) - { - ubyte b = src[x]; - if (b>=64) - b = 63; - b = (b<<2) & 0xFC; - dst[x] = b; - } - src += glyph_row_size; - dst += g.blackBoxX; - } + if (needSubpixelRendering) { + ubyte[] newglyph; + g.blackBoxX = prepare_lcd_glyph(glyph.ptr, + metrics, + newglyph); + g.glyph = newglyph; + //g.width = g.width / 3; + } else { + int glyph_row_size = (g.blackBoxX + 3) / 4 * 4; + ubyte * src = glyph.ptr; + ubyte * dst = g.glyph.ptr; + for (int y = 0; y < g.blackBoxY; y++) + { + for (int x = 0; x < g.blackBoxX; x++) + { + ubyte b = src[x]; + if (b>=64) + b = 63; + b = (b<<2) & 0xFC; + dst[x] = b; + } + src += glyph_row_size; + dst += g.blackBoxX; + } + } } else { // bitmap glyph ubyte[] glyph = new ubyte[gs]; @@ -217,7 +327,7 @@ class Win32Font : Font { &metrics, gs, glyph.ptr, - &identity ); + &scaleMatrix ); if (res==GDI_ERROR) { return null;