#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" #include "font_renderer_alpha.h" typedef agg::blender_rgb_gamma > blender_gamma_type; 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_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) { bool hinting = ((flags & FONT_RENDERER_HINTING) != 0); bool kerning = ((flags & FONT_RENDERER_KERNING) != 0); return new FontRendererImpl(hinting, kerning, gamma); } void FontRendererFree(FontRenderer *font_renderer) { delete font_renderer; } int FontRendererLoadFont(FontRenderer *font_renderer, const char *filename) { bool success = font_renderer->renderer_alpha().load_font(filename); return (success ? 0 : 1); } int FontRendererGetFontHeight(FontRenderer *font_renderer, float size) { font_renderer_alpha& renderer_alpha = font_renderer->renderer_alpha(); double ascender, descender; renderer_alpha.get_font_vmetrics(ascender, descender); int face_height = renderer_alpha.get_face_height(); float scale = renderer_alpha.scale_for_em_to_pixels(size); return int((ascender - descender) * face_height * scale + 0.5); } 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 * 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; for (int x = x0; x < x1; x++) { row_bitsum |= row[x]; } if (row_bitsum == 0) { y0++; } else { break; } } for (int y = gli.y1 - 1; y >= y0; y--) { uint8_t *row = ren_buf.row_ptr(height - 1 - y); unsigned int row_bitsum = 0; for (int x = x0; x < x1; x++) { row_bitsum |= row[x]; } if (row_bitsum == 0) { y1--; } else { break; } } for (int x = gli.x0 * subpixel_scale; x < gli.x1 * subpixel_scale; x += subpixel_scale) { unsigned int xaccu = 0; for (int y = y0; y < y1; y++) { uint8_t *row = ren_buf.row_ptr(height - 1 - y); for (int i = 0; i < subpixel_scale; i++) { xaccu |= row[x + i]; } } if (xaccu == 0) { x0 += subpixel_scale; } else { break; } } for (int x = (gli.x1 - 1) * subpixel_scale; x >= x0; x -= subpixel_scale) { unsigned int xaccu = 0; for (int y = y0; y < y1; y++) { uint8_t *row = ren_buf.row_ptr(height - 1 - y); for (int i = 0; i < subpixel_scale; i++) { xaccu |= row[x + i]; } } if (xaccu == 0) { x1 -= subpixel_scale; } else { break; } } gli.xoff += (x0 / subpixel_scale) - gli.x0; gli.yoff += (y0 - gli.y0); gli.x0 = x0 / subpixel_scale; gli.y0 = y0; gli.x1 = x1 / subpixel_scale; gli.y1 = y1; } static void debug_print_covers(const char *msg, agg::int8u *b, int n_pre, int n, int n_post) { fprintf(stderr, "%s", msg); for (int i = 0; i < n_pre; i++) { if (i % 3 == 0) fprintf(stderr, "|"); fprintf(stderr, " "); } for (int i = 0; i < n; i++) { if (i % 3 == 0) fprintf(stderr, "|"); fprintf(stderr, " %02x", b[i]); } for (int i = 0; i < n_post; i++) { if (i % 3 == 0) fprintf(stderr, "|"); fprintf(stderr, " "); } fprintf(stderr, "\n"); } static void glyph_lut_convolution(agg::rendering_buffer ren_buf, agg::lcd_distribution_lut& lcd_lut, agg::int8u *covers_buf, GlyphBitmapInfo& gli) { const int subpixel = 3; const int x0 = gli.x0, y0 = gli.y0, x1 = gli.x1, y1 = gli.y1; const int len = (x1 - x0) * subpixel; const int height = ren_buf.height(); for (int y = y0; y < y1; y++) { // FIXME: clarify why we do not use height - 1 below. agg::int8u *covers = ren_buf.row_ptr(height - 1 - y) + x0 * subpixel; memcpy(covers_buf, covers, len); #if 0 if (len >= 24) { debug_print_covers("BUF ", covers_buf, 0, len + 2 * subpixel, 0); debug_print_covers("BEFORE", covers - subpixel, 0, len + 2 * subpixel, 0); } #endif for (int x = x0 - 1; x < x1 + 1; x++) { for (int i = 0; i < subpixel; i++) { const int cx = (x - x0) * subpixel + i; covers[cx] = lcd_lut.convolution(covers, cx, 0, len - 1); } } #if 0 if (len >= 24) { debug_print_covers("AFTER ", covers - subpixel, 0, len + 2 * subpixel, 0); char c; scanf("%c", &c); } #endif } gli.x0 -= 1; gli.x1 += 1; gli.xoff -= 1; } 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 subpixel_scale) { font_renderer_alpha& renderer_alpha = font_renderer->renderer_alpha(); const int pixel_size = 1; memset(pixels, 0x00, pixels_width * pixels_height * subpixel_scale * pixel_size); double ascender, descender; renderer_alpha.get_font_vmetrics(ascender, descender); // FIXME: depending how we approximate ascender - descender could differ from font_height const int ascender_px = int( ascender * font_height + 1.0); const int descender_px = -int(-descender * font_height + 1.0); const int font_height_ext = ascender_px - descender_px; const int pad_y = font_height / 10; const int y_step = font_height_ext + 2 * pad_y; agg::lcd_distribution_lut& lcd_lut = font_renderer->lcd_distribution_lut(); agg::rendering_buffer ren_buf((agg::int8u *) pixels, pixels_width * subpixel_scale, pixels_height, -pixels_width * subpixel_scale * pixel_size); // When using subpixel font rendering it is needed to leave a padding pixel on the left and on the right. // Since each pixel is composed by n subpixel we set below x_start to subpixel_scale instead than zero. const int x_start = subpixel_scale; int x = x_start, y = pixels_height - 1; // - 20; // -20 is for debug int res = 0; const agg::alpha8 text_color(0xff); #ifdef FONT_RENDERER_HEIGHT_HACK const int font_height_reduced = (font_height * 86) / 100; #else const int font_height_reduced = font_height; #endif fprintf(stderr, "FONT HEIGHT %d, ASCENDER: %d DESCENDER: %d\n", font_height, ascender_px, descender_px); for (int i = 0; i < num_chars; i++) { int codepoint = first_char + i; if (x + font_height * subpixel_scale > pixels_width * subpixel_scale) { x = x_start; y -= y_step; } if (y - y_step < 0) { res = -1; break; } const int y_baseline = y - pad_y - ascender_px; double x_next = x, y_next = y_baseline; fprintf(stderr, "GLYPH (%d, %d)\n", x, y_baseline); 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)); // Below x and x_next_i will always be integer multiples of subpixel_scale. GlyphBitmapInfo& glyph_info = glyphs[i]; glyph_info.x0 = x / subpixel_scale; glyph_info.y0 = pixels_height - 1 - (y_baseline + ascender_px + pad_y); // FIXME: add -1 ? glyph_info.x1 = x_next_i / subpixel_scale; glyph_info.y1 = pixels_height - 1 - (y_baseline + descender_px - pad_y); // FIXME: add -1 ? glyph_info.xoff = 0; glyph_info.yoff = -pad_y; glyph_info.xadvance = (x_next - x) / subpixel_scale; if (glyph_info.x1 > glyph_info.x0) { agg::int8u *covers_buf = ren_buf.row_ptr(0); glyph_lut_convolution(ren_buf, lcd_lut, covers_buf, glyph_info); } // glyph_trim_rect(ren_buf, glyph_info, subpixel_scale); // When subpixel is activated we need at least two more subpixels on the right. x = x_next_i + 2 * subpixel_scale; } return res; } void blend_solid_hspan(agg::rendering_buffer& rbuf, blender_gamma_type& blender, int x, int y, unsigned len, const agg::rgba8& c, const agg::int8u* covers) { typedef typename blender_gamma_type::color_type color_type; typedef typename blender_gamma_type::order_type order_type; typedef typename color_type::value_type value_type; typedef typename color_type::calc_type calc_type; if (c.a) { value_type* p = (value_type*)rbuf.row_ptr(x, y, len) + (x << 2); do { calc_type alpha = (calc_type(c.a) * (calc_type(*covers) + 1)) >> 8; if(alpha == color_type::base_mask) { p[order_type::R] = c.r; p[order_type::G] = c.g; p[order_type::B] = c.b; } else { blender.blend_pix(p, c.r, c.g, c.b, alpha, *covers); } p += 4; ++covers; } while(--len); } } void blend_solid_hspan_rgb_subpixel(agg::rendering_buffer& rbuf, agg::gamma_lut<>& gamma, agg::lcd_distribution_lut& lcd_lut, int x, int y, unsigned len, const agg::rgba8& c, const agg::int8u* covers) { const int x_min = x; const int x_max = x + len / 3; const int pixel_size = 4; 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 xp = x_min; xp <= x_max; xp++) { for (int i = 0; i < 3; i++) { int cx = xp * 3 - x + i; unsigned cover_value = covers[cx]; unsigned alpha = (cover_value + 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); } // Leave p[3], the alpha channel value unmodified. 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(); 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(dst_ren_buf, blender, x, y, region_width, color_a, covers); } } // destination implicitly BGRA32. Source implictly single-byte renderer_alpha coverage with subpixel scale = 3. 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); } }