Implement subpixel LCD font rendering

This commit is contained in:
Francesco Abbate 2020-06-04 16:29:28 +02:00
parent f61ffc4710
commit a0e7d16167
6 changed files with 218 additions and 116 deletions

View File

@ -0,0 +1,78 @@
//----------------------------------------------------------------------------
// Anti-Grain Geometry (AGG) - Version 2.5
// A high quality rendering engine for C++
// Copyright (C) 2002-2006 Maxim Shemanarev
// Contact: mcseem@antigrain.com
// mcseemagg@yahoo.com
// http://antigrain.com
//
// AGG is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// AGG is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with AGG; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301, USA.
//----------------------------------------------------------------------------
#ifndef AGG_LCD_DISTRIBUTION_LUT_INCLUDED
#define AGG_LCD_DISTRIBUTION_LUT_INCLUDED
#include "agg_basics.h"
namespace agg
{
//=====================================================lcd_distribution_lut
class lcd_distribution_lut
{
public:
lcd_distribution_lut(double prim, double second, double tert)
{
double norm = 1.0 / (prim + second*2 + tert*2);
prim *= norm;
second *= norm;
tert *= norm;
for(unsigned i = 0; i < 256; i++)
{
unsigned b = (i << 8);
unsigned s = round(second * b);
unsigned t = round(tert * b);
unsigned p = b - (2*s + 2*t);
m_data[3*i + 1] = s; /* secondary */
m_data[3*i + 2] = t; /* tertiary */
m_data[3*i ] = p; /* primary */
}
}
unsigned convolution(const int8u* covers, int i0, int i_min, int i_max) const
{
unsigned sum = 0;
int k_min = (i0 >= i_min + 2 ? -2 : i_min - i0);
int k_max = (i0 <= i_max - 2 ? 2 : i_max - i0);
for (int k = k_min; k <= k_max; k++)
{
/* select the primary, secondary or tertiary channel */
int channel = abs(k) % 3;
int8u c = covers[i0 + k];
sum += m_data[3*c + channel];
}
return (sum + 128) >> 8;
}
private:
unsigned short m_data[256*3];
};
}
#endif

View File

@ -1,5 +1,6 @@
#include "font_renderer.h"
#include "agg_lcd_distribution_lut.h"
#include "agg_pixfmt_rgb.h"
#include "agg_pixfmt_rgba.h"
#include "agg_gamma_lut.h"
@ -10,21 +11,28 @@ typedef agg::blender_rgb_gamma<agg::rgba8, agg::order_bgra, agg::gamma_lut<> > b
class FontRendererImpl {
public:
// Conventional LUT values: (1./3., 2./9., 1./9.)
// The values below are fine tuned as in the Elementary Plot library.
FontRendererImpl(bool hinting, bool kerning, float gamma_value) :
m_renderer(hinting, kerning),
m_gamma_lut(double(gamma_value)),
m_blender()
m_blender(),
m_lcd_lut(0.448, 0.184, 0.092)
{
m_blender.gamma(m_gamma_lut);
}
font_renderer_alpha& renderer_alpha() { return m_renderer; }
blender_gamma_type& blender() { return m_blender; }
agg::gamma_lut<>& gamma() { return m_gamma_lut; }
agg::lcd_distribution_lut& lcd_distribution_lut() { return m_lcd_lut; }
private:
font_renderer_alpha m_renderer;
agg::gamma_lut<> m_gamma_lut;
blender_gamma_type m_blender;
agg::lcd_distribution_lut m_lcd_lut;
};
FontRenderer *FontRendererNew(unsigned int flags, float gamma) {
@ -51,9 +59,10 @@ int FontRendererGetFontHeight(FontRenderer *font_renderer, float size) {
return int((ascender - descender) * face_height * scale + 0.5);
}
static void glyph_trim_rect(agg::rendering_buffer& ren_buf, GlyphBitmapInfo *gli) {
static void glyph_trim_rect(agg::rendering_buffer& ren_buf, GlyphBitmapInfo *gli, int subpixel_scale) {
const int height = ren_buf.height();
int x0 = gli->x0, y0 = gli->y0, x1 = gli->x1, y1 = gli->y1;
int x0 = gli->x0 * subpixel_scale, x1 = gli->x1 * subpixel_scale;
int y0 = gli->y0, y1 = gli->y1;
for (int y = gli->y0; y < gli->y1; y++) {
uint8_t *row = ren_buf.row_ptr(height - 1 - y);
unsigned int row_bitsum = 0;
@ -81,35 +90,48 @@ static void glyph_trim_rect(agg::rendering_buffer& ren_buf, GlyphBitmapInfo *gli
int xtriml = x0, xtrimr = x1;
for (int y = y0; y < y1; y++) {
uint8_t *row = ren_buf.row_ptr(height - 1 - y);
for (int x = x0; x < x1; x++) {
if (row[x]) {
for (int x = x0; x < x1; x += subpixel_scale) {
unsigned int xaccu = 0;
for (int i = 0; i < subpixel_scale; i++) {
xaccu |= row[x + i];
}
if (xaccu > 0) {
// FIXME: fix xs comparaison below.
if (x < xtriml) xtriml = x;
break;
}
}
for (int x = x1 - 1; x >= x0; x--) {
if (row[x]) {
for (int x = x1 - subpixel_scale; x >= x0; x -= subpixel_scale) {
unsigned int xaccu = 0;
for (int i = 0; i < subpixel_scale; i++) {
xaccu |= row[x + i];
}
if (xaccu > 0) {
if (x > xtrimr) xtrimr = x + 1;
break;
}
}
}
gli->xoff += (xtriml - x0);
gli->xoff += (xtriml - x0) / subpixel_scale;
gli->yoff += (y0 - gli->y0);
gli->x0 = xtriml;
gli->x0 = xtriml / subpixel_scale;
gli->y0 = y0;
gli->x1 = xtrimr;
gli->x1 = xtrimr / subpixel_scale;
gli->y1 = y1;
}
static int ceil_to_multiple(int n, int p) {
return p * ((n + p - 1) / p);
}
int FontRendererBakeFontBitmap(FontRenderer *font_renderer, int font_height,
void *pixels, int pixels_width, int pixels_height,
int first_char, int num_chars, GlyphBitmapInfo *glyphs)
int first_char, int num_chars, GlyphBitmapInfo *glyphs, int subpixel_scale)
{
font_renderer_alpha& renderer_alpha = font_renderer->renderer_alpha();
const int pixel_size = 1;
memset(pixels, 0x00, pixels_width * pixels_height * pixel_size);
memset(pixels, 0x00, pixels_width * pixels_height * subpixel_scale * pixel_size);
double ascender, descender;
renderer_alpha.get_font_vmetrics(ascender, descender);
@ -117,11 +139,12 @@ int FontRendererBakeFontBitmap(FontRenderer *font_renderer, int font_height,
const int ascender_px = int(ascender * font_height + 0.5);
const int descender_px = int(descender * font_height + 0.5);
const int pad_y = font_height / 10, pad_x = 1;
const int pad_y = font_height / 10;
const int y_step = font_height + 2 * pad_y;
agg::rendering_buffer ren_buf((agg::int8u *) pixels, pixels_width, pixels_height, -pixels_width * pixel_size);
int x = 0, y = pixels_height;
agg::rendering_buffer ren_buf((agg::int8u *) pixels, pixels_width * subpixel_scale, pixels_height, -pixels_width * subpixel_scale * pixel_size);
const int x_start = subpixel_scale;
int x = x_start, y = pixels_height;
int res = 0;
const agg::alpha8 text_color(0xff);
#ifdef FONT_RENDERER_HEIGHT_HACK
@ -131,8 +154,8 @@ int FontRendererBakeFontBitmap(FontRenderer *font_renderer, int font_height,
#endif
for (int i = 0; i < num_chars; i++) {
int codepoint = first_char + i;
if (x + font_height > pixels_width) {
x = 0;
if (x + font_height * subpixel_scale > pixels_width * subpixel_scale) {
x = x_start;
y -= y_step;
}
if (y - font_height - 2 * pad_y < 0) {
@ -142,22 +165,22 @@ int FontRendererBakeFontBitmap(FontRenderer *font_renderer, int font_height,
const int y_baseline = y - pad_y - font_height;
double x_next = x, y_next = y_baseline;
renderer_alpha.render_codepoint(ren_buf, font_height_reduced, text_color, x_next, y_next, codepoint);
int x_next_i = int(x_next + 1.0);
renderer_alpha.render_codepoint(ren_buf, font_height_reduced, text_color, x_next, y_next, codepoint, subpixel_scale);
int x_next_i = (subpixel_scale == 1 ? int(x_next + 1.0) : ceil_to_multiple(x_next + 0.5, subpixel_scale));
GlyphBitmapInfo& glyph_info = glyphs[i];
glyph_info.x0 = x;
glyph_info.x0 = x / subpixel_scale;
glyph_info.y0 = pixels_height - (y_baseline + ascender_px + pad_y);
glyph_info.x1 = x_next_i;
glyph_info.x1 = x_next_i / subpixel_scale;
glyph_info.y1 = pixels_height - (y_baseline + descender_px - pad_y);
glyph_info.xoff = 0;
glyph_info.yoff = -pad_y;
glyph_info.xadvance = x_next - x;
glyph_info.xadvance = (x_next - x) / subpixel_scale;
glyph_trim_rect(ren_buf, &glyph_info);
glyph_trim_rect(ren_buf, &glyph_info, subpixel_scale);
x = x_next_i + pad_x;
x = x_next_i;
#ifdef FONT_RENDERER_DEBUG
fprintf(stderr,
@ -171,6 +194,7 @@ int FontRendererBakeFontBitmap(FontRenderer *font_renderer, int font_height,
return res;
}
// FIXME: remove the Blender template argument.
template <typename Blender>
void blend_solid_hspan(agg::rendering_buffer& rbuf, Blender& blender,
int x, int y, unsigned len,
@ -205,6 +229,52 @@ void blend_solid_hspan(agg::rendering_buffer& rbuf, Blender& blender,
}
}
static int floor_div(int a, int b) {
int rem = a % b;
if (rem < 0) {
rem += b;
}
return (a - rem) / b;
}
void blend_solid_hspan_rgb_subpixel(agg::rendering_buffer& rbuf, agg::gamma_lut<>& gamma, agg::lcd_distribution_lut& lcd_lut,
int x_lcd, int y, unsigned len,
const agg::rgba8& c,
const agg::int8u* covers)
{
// const int subpixel_scale = 3;
// FIXME: rowlen à verifier
// unsigned rowlen = rbuf.width() * subpixel_scale;
// FIXME: no correct boundary limits for cx and cx_max
// int cx = (x_lcd - 2 >= 0 ? -2 : -x_lcd);
// int cx_max = (len + 2 <= rowlen ? len + 1 : rowlen - 1);
const int pixel_size = 4;
int cx = -2;
int cx_max = len + 1;
const int x_min = floor_div(x_lcd + cx, 3);
const int x_max = floor_div(x_lcd + cx_max, 3);
const agg::int8u rgb[3] = { c.r, c.g, c.b };
agg::int8u* p = rbuf.row_ptr(y) + x_min * pixel_size;
// Indexes to adress RGB colors in a BGRA32 format.
const int pixel_index[3] = {2, 1, 0};
for (int x = x_min; x <= x_max; x++)
{
for (int i = 0; i < 3; i++) {
int new_cx = x * 3 - x_lcd + i;
unsigned c_conv = lcd_lut.convolution(covers, new_cx, 0, len - 1);
unsigned alpha = (c_conv + 1) * (c.a + 1);
unsigned dst_col = gamma.dir(rgb[i]);
unsigned src_col = gamma.dir(*(p + pixel_index[i]));
*(p + pixel_index[i]) = gamma.inv((((dst_col - src_col) * alpha) + (src_col << 16)) >> 16);
}
//p[3] = 0xff;
p += 4;
}
}
// destination implicitly BGRA32. Source implictly single-byte renderer_alpha coverage.
void FontRendererBlendGamma(FontRenderer *font_renderer, uint8_t *dst, int dst_stride, uint8_t *src, int src_stride, int region_width, int region_height, FontRendererColor color) {
blender_gamma_type& blender = font_renderer->blender();
@ -215,3 +285,16 @@ void FontRendererBlendGamma(FontRenderer *font_renderer, uint8_t *dst, int dst_s
blend_solid_hspan<blender_gamma_type>(dst_ren_buf, blender, x, y, region_width, color_a, covers);
}
}
// destination implicitly BGRA32. Source implictly single-byte renderer_alpha coverage.
void FontRendererBlendGammaSubpixel(FontRenderer *font_renderer, uint8_t *dst, int dst_stride, uint8_t *src, int src_stride, int region_width, int region_height, FontRendererColor color) {
const int subpixel_scale = 3;
agg::gamma_lut<>& gamma = font_renderer->gamma();
agg::lcd_distribution_lut& lcd_lut = font_renderer->lcd_distribution_lut();
agg::rendering_buffer dst_ren_buf(dst, region_width, region_height, dst_stride);
const agg::rgba8 color_a(color.r, color.g, color.b);
for (int x = 0, y = 0; y < region_height; y++) {
agg::int8u *covers = src + y * src_stride;
blend_solid_hspan_rgb_subpixel(dst_ren_buf, gamma, lcd_lut, x, y, region_width * subpixel_scale, color_a, covers);
}
}

View File

@ -35,7 +35,8 @@ int FontRendererGetFontHeight(FontRenderer *, float size);
int FontRendererBakeFontBitmap(FontRenderer *, int font_height,
void *pixels, int pixels_width, int pixels_height,
int first_char, int num_chars, GlyphBitmapInfo *glyph_info);
int first_char, int num_chars, GlyphBitmapInfo *glyph_info,
int subpixel_scale);
void FontRendererBlendGamma(FontRenderer *,
uint8_t *dst, int dst_stride,
@ -43,6 +44,12 @@ void FontRendererBlendGamma(FontRenderer *,
int region_width, int region_height,
FontRendererColor color);
void FontRendererBlendGammaSubpixel(FontRenderer *,
uint8_t *dst, int dst_stride,
uint8_t *src, int src_stride,
int region_width, int region_height,
FontRendererColor color);
#ifdef __cplusplus
}
#endif

View File

@ -78,74 +78,15 @@ public:
return m_font_loaded;
}
template<class Rasterizer, class Scanline, class RenSolid>
void draw_text(Rasterizer& ras, Scanline& sl,
RenSolid& ren_solid, const color_type color,
const char* text,
double& x, double& y, double height)
{
const double scale_x = 100;
m_feng.height(height);
m_feng.width(height * scale_x);
m_feng.hinting(m_hinting);
const char* p = text;
// Represent the delta in x scaled by scale_x.
double x_delta = 0;
double start_x = x;
while(*p)
{
if(*p == '\n')
{
x_delta = 0;
y -= height * 1.25;
++p;
continue;
}
const agg::glyph_cache* glyph = m_fman.glyph(*p);
if(glyph)
{
if(m_kerning)
{
m_fman.add_kerning(&x_delta, &y);
}
m_fman.init_embedded_adaptors(glyph, 0, 0);
if(glyph->data_type == agg::glyph_data_outline)
{
double ty = m_hinting ? floor(y + 0.5) : y;
ras.reset();
m_mtx.reset();
m_mtx *= agg::trans_affine_scaling(1.0 / scale_x, 1);
m_mtx *= agg::trans_affine_translation(start_x + x_delta / scale_x, ty);
ras.add_path(m_trans);
ren_solid.color(color);
agg::render_scanlines(ras, sl, ren_solid);
}
// increment pen position
x_delta += glyph->advance_x;
y += glyph->advance_y;
}
++p;
}
// Update x value befor returning.
x += x_delta / scale_x;
}
template<class Rasterizer, class Scanline, class RenSolid>
void draw_codepoint(Rasterizer& ras, Scanline& sl,
RenSolid& ren_solid, const color_type color,
int codepoint, double& x, double& y, double height)
int codepoint, double& x, double& y, double height, int subpixel_scale)
{
const double scale_x = 100;
m_feng.height(height);
m_feng.width(height * scale_x);
m_feng.width(height * scale_x * subpixel_scale);
m_feng.hinting(m_hinting);
// Represent the delta in x scaled by scale_x.
@ -184,30 +125,11 @@ public:
ren_base.clear(color);
}
void render_text(agg::rendering_buffer& ren_buf,
const double text_size,
const color_type text_color,
double& x, double& y,
const char *text)
{
if (!m_font_loaded) {
return;
}
agg::scanline_u8 sl;
agg::rasterizer_scanline_aa<> ras;
ras.clip_box(0, 0, ren_buf.width(), ren_buf.height());
agg::pixfmt_alpha8 pf(ren_buf);
base_ren_type ren_base(pf);
renderer_solid ren_solid(ren_base);
draw_text(ras, sl, ren_solid, text_color, text, x, y, text_size);
}
void render_codepoint(agg::rendering_buffer& ren_buf,
const double text_size,
const color_type text_color,
double& x, double& y,
int codepoint)
int codepoint, int subpixel_scale)
{
if (!m_font_loaded) {
return;
@ -219,6 +141,6 @@ public:
agg::pixfmt_alpha8 pf(ren_buf);
base_ren_type ren_base(pf);
renderer_solid ren_solid(ren_base);
draw_codepoint(ras, sl, ren_solid, text_color, codepoint, x, y, text_size);
draw_codepoint(ras, sl, ren_solid, text_color, codepoint, x, y, text_size, subpixel_scale);
}
};

View File

@ -105,9 +105,9 @@ RenImage* ren_new_image(int width, int height) {
return image;
}
RenCoverageImage* ren_new_coverage(int width, int height) {
RenCoverageImage* ren_new_coverage(int width, int height, int subpixel_scale) {
assert(width > 0 && height > 0);
RenCoverageImage *image = malloc(sizeof(RenCoverageImage) + width * height * sizeof(uint8_t));
RenCoverageImage *image = malloc(sizeof(RenCoverageImage) + width * height * subpixel_scale * sizeof(uint8_t));
check_alloc(image);
image->pixels = (void*) (image + 1);
image->width = width;
@ -126,15 +126,17 @@ void ren_free_coverage(RenCoverageImage *coverage) {
static GlyphSet* load_glyphset(RenFont *font, int idx) {
GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet)));
const int subpixel_scale = 3;
/* init image */
int width = 128;
int height = 128;
retry:
set->coverage = ren_new_coverage(width, height);
set->coverage = ren_new_coverage(width, height, subpixel_scale);
int res = FontRendererBakeFontBitmap(font->renderer, font->height,
(void *) set->coverage->pixels, width, height,
idx << 8, 256, set->glyphs);
idx << 8, 256, set->glyphs, subpixel_scale);
/* retry with a larger image buffer if the buffer wasn't large enough */
if (res < 0) {
@ -169,7 +171,7 @@ RenFont* ren_load_font(const char *filename, float size) {
font = check_alloc(calloc(1, sizeof(RenFont)));
font->size = size;
const float gamma = 1.8;
const float gamma = 1.5;
font->renderer = FontRendererNew(FONT_RENDERER_HINTING, gamma);
if (FontRendererLoadFont(font->renderer, filename)) {
free(font);
@ -321,17 +323,26 @@ static void ren_draw_coverage_with_color(FontRenderer *renderer, RenCoverageImag
return;
}
const int subpixel_scale = 3;
#if 0
// FIXME: find a more robust solution.
const int sub_width_rem = sub->width % subpixel_scale;
if (sub_width_rem > 0) {
sub->width += (subpixel_scale - sub_width_rem);
}
#endif
/* draw */
SDL_Surface *surf = SDL_GetWindowSurface(window);
uint8_t *s = image->pixels;
RenColor *d = (RenColor*) surf->pixels;
s += sub->x + sub->y * image->width;
s += (sub->x + sub->y * image->width) * subpixel_scale;
d += x + y * surf->w;
const int surf_pixel_size = 4;
FontRendererBlendGamma(
FontRendererBlendGammaSubpixel(
renderer,
(uint8_t *) d, surf->w * surf_pixel_size,
s, image->width,
s, image->width * subpixel_scale,
sub->width, sub->height,
(FontRendererColor) { .r = color.r, .g = color.g, .b = color.b });
}

View File

@ -81,9 +81,10 @@ static GlyphSet* load_glyphset_agg(RenFont *font, int idx) {
retry:
set->image = ren_new_image(width, height);
const int subpixel_scale = 3;
int res = FontRendererBakeFontBitmap(font->renderer, font->height,
(void *) set->image->pixels, width, height,
idx << 8, 256, set->glyphs);
idx << 8, 256, set->glyphs, subpixel_scale);
/* retry with a larger image buffer if the buffer wasn't large enough */
if (res < 0) {