subpixel rendering

This commit is contained in:
Vadim Lopatin 2015-01-23 17:50:25 +03:00
parent 40c138e911
commit ae75f40323
8 changed files with 121 additions and 27 deletions

View File

@ -201,8 +201,10 @@ int pixelsToPoints(int px) {
enum SubpixelRenderingMode : ubyte {
/// no sub
None,
/// subpixel rendering on, subpixel order: B,G,R
/// subpixel rendering on, subpixel order on device: B,G,R
BGR,
/// subpixel rendering on, subpixel order on device: R,G,B
RGB,
}
/**
@ -215,6 +217,9 @@ struct Glyph
{
/// 0: width of glyph black box
ushort blackBoxX;
@property ushort correctedBlackBoxX() { return subpixelMode ? blackBoxX / 3 : blackBoxX; }
/// 2: height of glyph black box
ubyte blackBoxY;
/// 3: X origin for glyph

View File

@ -18,6 +18,8 @@ Authors: Vadim Lopatin, coolreader.org@gmail.com
*/
module dlangui.graphics.colors;
import dlangui.core.types;
private import std.string : strip;
/// special color constant to identify value as not a color (to use default/parent value instead)
@ -51,6 +53,37 @@ uint blendARGB(uint dst, uint src, uint alpha) {
return (r << 16) | (g << 8) | b;
}
//immutable int COMPONENT_OFFSET_BGR[3] = [2, 1, 0];
immutable int COMPONENT_OFFSET_BGR[3] = [2, 1, 0];
//immutable int COMPONENT_OFFSET_BGR[3] = [1, 2, 0];
immutable int COMPONENT_OFFSET_RGB[3] = [0, 1, 2];
immutable int COMPONENT_OFFSET_ALPHA = 3;
int subpixelComponentIndex(int x0, SubpixelRenderingMode mode) {
switch (mode) {
case SubpixelRenderingMode.RGB:
return COMPONENT_OFFSET_BGR[x0];
case SubpixelRenderingMode.BGR:
default:
return COMPONENT_OFFSET_BGR[x0];
}
}
/// blend subpixel using alpha
void blendSubpixel(ubyte * dst, ubyte * src, uint alpha, int x0, SubpixelRenderingMode mode) {
uint dstalpha = dst[COMPONENT_OFFSET_ALPHA];
int offset = subpixelComponentIndex(x0, mode);
uint srcr = src[offset];
dst[COMPONENT_OFFSET_ALPHA] = 0;
if (dstalpha > 0x80) {
dst[offset] = cast(ubyte)srcr;
return;
}
uint dstr = dst[offset];
uint ialpha = 256 - alpha;
uint r = ((srcr * ialpha + dstr * alpha) >> 8) & 0xFF;
dst[offset] = cast(ubyte)r;
}
/// blend two alpha values 0..255 (255 is fully transparent, 0 is opaque)
uint blendAlpha(uint a1, uint a2) {
if (!a1)

View File

@ -562,6 +562,7 @@ class ColorDrawBufBase : DrawBuf {
int srcdy = glyph.blackBoxY;
bool clipping = true; //!_clipRect.empty();
color = applyAlpha(color);
bool subpixel = glyph.subpixelMode != SubpixelRenderingMode.None;
for (int yy = 0; yy < srcdy; yy++) {
int liney = y + yy;
if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom))
@ -571,21 +572,30 @@ class ColorDrawBufBase : DrawBuf {
uint * row = scanLine(liney);
ubyte * srcrow = src.ptr + yy * srcdx;
for (int xx = 0; xx < srcdx; xx++) {
int colx = xx + x;
int colx = x + (subpixel ? xx / 3 : xx);
if (clipping && (colx < _clipRect.left || colx >= _clipRect.right))
continue;
if (colx < 0 || colx >= _dx)
continue;
uint alpha1 = srcrow[xx] ^ 255;
uint alpha2 = (color >> 24);
uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255;
uint pixel = row[colx];
if (!alpha)
row[colx] = pixel;
else if (alpha < 255) {
// apply blending
row[colx] = blendARGB(pixel, color, alpha);
}
uint alpha1 = srcrow[xx] ^ 255;
uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255;
if (subpixel) {
int x0 = xx % 3;
ubyte * dst = cast(ubyte*)(row + colx);
ubyte * pcolor = cast(ubyte*)(&color);
blendSubpixel(dst, pcolor, alpha, x0, glyph.subpixelMode);
} else {
uint pixel = row[colx];
if (alpha < 255) {
if (!alpha)
row[colx] = pixel;
else {
// apply blending
row[colx] = blendARGB(pixel, color, alpha);
}
}
}
}
}
}

View File

@ -243,7 +243,7 @@ class Font : RefCountedObject {
continue;
}
int w = x + glyph.width; // using advance
int w2 = x + glyph.originX + glyph.blackBoxX; // using black box
int w2 = x + glyph.originX + glyph.correctedBlackBoxX; // using black box
if (w < w2) // choose bigger value
w = w2;
pwidths[i] = w;
@ -334,7 +334,7 @@ class Font : RefCountedObject {
continue;
if ( glyph.blackBoxX && glyph.blackBoxY ) {
int gx = x + xx + glyph.originX;
if (gx + glyph.blackBoxX < clip.left)
if (gx + glyph.correctedBlackBoxX < clip.left)
continue;
buf.drawGlyph( gx,
y + _baseline - glyph.originY,
@ -405,7 +405,7 @@ class Font : RefCountedObject {
continue;
if ( glyph.blackBoxX && glyph.blackBoxY ) {
int gx = x + xx + glyph.originX;
if (gx + glyph.blackBoxX < clip.left)
if (gx + glyph.correctedBlackBoxX < clip.left)
continue;
buf.drawGlyph( gx,
y + _baseline - glyph.originY,

View File

@ -266,6 +266,7 @@ private class FreeTypeFontFile {
glyph.originX = cast(byte)(_slot.metrics.horiBearingX >> 6);
glyph.originY = cast(byte)(_slot.metrics.horiBearingY >> 6);
glyph.width = cast(ubyte)(myabs(cast(int)(_slot.metrics.horiAdvance)) >> 6);
glyph.subpixelMode = SubpixelRenderingMode.None;
//glyph.glyphIndex = cast(ushort)code;
if (withImage) {
FT_Bitmap* bitmap = &_slot.bitmap;

View File

@ -92,8 +92,8 @@ class GLDrawBuf : DrawBuf {
/// draw 8bit alpha image - usually font glyph using specified color (clipping is applied)
override void drawGlyph(int x, int y, Glyph * glyph, uint color) {
assert(_scene !is null);
Rect dstrect = Rect(x,y, x + glyph.blackBoxX, y + glyph.blackBoxY);
Rect srcrect = Rect(0, 0, glyph.blackBoxX, glyph.blackBoxY);
Rect dstrect = Rect(x,y, x + glyph.correctedBlackBoxX, y + glyph.blackBoxY);
Rect srcrect = Rect(0, 0, glyph.correctedBlackBoxX, glyph.blackBoxY);
//Log.v("GLDrawBuf.drawGlyph dst=", dstrect, " src=", srcrect, " color=", color);
color = applyAlpha(color);
if (!isFullyTransparentColor(color) && applyClipping(dstrect, srcrect)) {
@ -659,7 +659,7 @@ private class GLGlyphCache {
return _itemCount;
}
GLGlyphCacheItem addItem(Glyph * glyph) {
GLGlyphCacheItem cacheItem = reserveSpace(glyph.id, glyph.blackBoxX, glyph.blackBoxY);
GLGlyphCacheItem cacheItem = reserveSpace(glyph.id, glyph.correctedBlackBoxX, glyph.blackBoxY);
if (cacheItem is null)
return null;
_drawbuf.drawGlyph(cacheItem._rc.left, cacheItem._rc.top, glyph, 0xFFFFFF);

View File

@ -107,6 +107,22 @@ __gshared static this() {
lut = lcd_distribution_lut!65(0.5, 0.25, 0.125);
}
private int myabs(int n) {
return n < 0 ? -n : n;
}
private int colorStat(ubyte * p) {
int avg = (cast(int)p[0] + cast(int)p[1] + cast(int)p[2]) / 3;
return myabs(avg - cast(int)p[0]) + myabs(avg - cast(int)p[1]) + myabs(avg - cast(int)p[2]);
}
private int minIndex(int n0, int n1, int n2) {
if (n0 <= n1 && n0 <= n2)
return 0;
if (n1 <= n0 && n1 <= n2)
return 1;
return n2;
}
// This function prepares the alpha-channel information
// for the glyph averaging the values in accordance with
// the method suggested by Steve Gibson. The function
@ -118,8 +134,10 @@ __gshared static this() {
//---------------------------------
ushort prepare_lcd_glyph(ubyte * gbuf1,
ref GLYPHMETRICS gm,
ref ubyte[] gbuf2)
ref ubyte[] gbuf2,
ref int shiftedBy)
{
shiftedBy = 0;
uint src_stride = (gm.gmBlackBoxX + 3) / 4 * 4;
uint dst_width = src_stride + 4;
gbuf2 = new ubyte[dst_width * gm.gmBlackBoxY];
@ -128,8 +146,7 @@ ushort prepare_lcd_glyph(ubyte * gbuf1,
{
ubyte * src_ptr = gbuf1 + src_stride * y;
ubyte * dst_ptr = gbuf2.ptr + dst_width * y;
uint x;
for(x = 0; x < gm.gmBlackBoxX; ++x)
for(uint x = 0; x < gm.gmBlackBoxX; ++x)
{
uint v = *src_ptr++;
dst_ptr[0] += lut.tertiary(v);
@ -140,6 +157,31 @@ ushort prepare_lcd_glyph(ubyte * gbuf1,
++dst_ptr;
}
}
/*
int dx = (dst_width - 2) / 3;
int stats[3] = [0, 0, 0];
for (uint y = 0; y < gm.gmBlackBoxY; y++) {
for(uint x = 0; x < dx; ++x)
{
for (uint x0 = 0; x0 < 3; x0++) {
stats[x0] += colorStat(gbuf2.ptr + dst_width * y + x0);
}
}
}
shiftedBy = 0; //minIndex(stats[0], stats[1], stats[2]);
if (shiftedBy) {
for (uint y = 0; y < gm.gmBlackBoxY; y++) {
ubyte * dst_ptr = gbuf2.ptr + dst_width * y;
for(uint x = 0; x < gm.gmBlackBoxX; ++x)
{
if (x + shiftedBy < gm.gmBlackBoxX)
dst_ptr[x] = dst_ptr[x + shiftedBy];
else
dst_ptr[x] = 0;
}
}
}
*/
return cast(ushort) dst_width;
}
@ -271,9 +313,9 @@ class Win32Font : Font {
}
g.blackBoxX = cast(ushort)metrics.gmBlackBoxX;
g.blackBoxY = cast(ubyte)metrics.gmBlackBoxY;
g.originX = cast(byte)(needSubpixelRendering ? metrics.gmptGlyphOrigin.x / 3: metrics.gmptGlyphOrigin.x);
g.originX = cast(byte)(needSubpixelRendering ? ((metrics.gmptGlyphOrigin.x + 0) / 3) : (metrics.gmptGlyphOrigin.x));
g.originY = cast(byte)metrics.gmptGlyphOrigin.y;
g.width = cast(ubyte)(needSubpixelRendering ? metrics.gmCellIncX / 3 : metrics.gmCellIncX);
g.width = cast(ubyte)(needSubpixelRendering ? (metrics.gmCellIncX + 0) / 3 : metrics.gmCellIncX);
g.subpixelMode = needSubpixelRendering ? FontManager.subpixelRenderingMode : SubpixelRenderingMode.None;
//g.glyphIndex = cast(ushort)glyphIndex;
@ -296,11 +338,14 @@ class Win32Font : Font {
}
if (needSubpixelRendering) {
ubyte[] newglyph;
g.blackBoxX = prepare_lcd_glyph(glyph.ptr,
int shiftedBy = 0;
g.blackBoxX = prepare_lcd_glyph(glyph.ptr,
metrics,
newglyph);
newglyph,
shiftedBy);
g.glyph = newglyph;
//g.width = g.width / 3;
//g.originX = cast(ubyte)((metrics.gmptGlyphOrigin.x + 2 - shiftedBy) / 3);
//g.width = cast(ubyte)((metrics.gmCellIncX + 2 - shiftedBy) / 3);
} else {
int glyph_row_size = (g.blackBoxX + 3) / 4 * 4;
ubyte * src = glyph.ptr;

View File

@ -315,7 +315,7 @@
layoutHeight="WRAP_CONTENT"
fontFace="Arial"
fontFamily="SansSerif"
fontSize="12">
fontSize="16">
</style>
<style id="TREE_ITEM_EXPAND_ICON"
margins="2,0,2,0"
@ -338,7 +338,7 @@
layoutHeight="WRAP_CONTENT"
align="Left|VCenter"
textFlags="Parent"
fontSize="12"
fontSize="16"
/>
<style id="RESIZER_VERTICAL"
layoutWidth="FILL_PARENT"