subpixel rendering, part 1

This commit is contained in:
Vadim Lopatin 2015-01-23 15:48:29 +03:00
parent b417db74a0
commit 40c138e911
4 changed files with 181 additions and 48 deletions

View File

@ -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);

View File

@ -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;
}

View File

@ -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");
}

View File

@ -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;