From 17b90c25caa0fe4179997b63594e30343bae9fee Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 9 Mar 2021 14:08:02 +0100 Subject: [PATCH] Improved font atlas debug images Produce debug images of font's atlas with highlighted bounding box, offset and advance. It creates a specific image for subpixel rendering that shows each subpixel separately. --- lib/font_renderer/font_renderer.cpp | 181 +++++++++++++++++++++++++++- lib/font_renderer/meson.build | 2 +- meson.build | 5 +- 3 files changed, 185 insertions(+), 3 deletions(-) diff --git a/lib/font_renderer/font_renderer.cpp b/lib/font_renderer/font_renderer.cpp index d060839f..4fb772da 100644 --- a/lib/font_renderer/font_renderer.cpp +++ b/lib/font_renderer/font_renderer.cpp @@ -1,3 +1,5 @@ +#include + #include "font_renderer.h" #include "agg_lcd_distribution_lut.h" @@ -6,6 +8,9 @@ #include "font_renderer_alpha.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + // Important: when a subpixel scale is used the width below will be the width in logical pixel. // As each logical pixel contains 3 subpixels it means that the 'pixels' pointer // will hold enough space for '3 * width' uint8_t values. @@ -14,6 +19,62 @@ struct FR_Bitmap { int width, height; }; +static FR_Bitmap *debug_bitmap_to_image_rgb(FR_Bitmap *alpha_bitmap, const int subpixel_scale) { + const int w = alpha_bitmap->width, h = alpha_bitmap->height; + const int rgb_comp = 3; + + fmt::print("W: {} H: {}\n", w, h); + + FR_Bitmap *rgb_image = (FR_Bitmap *) malloc(sizeof(FR_Bitmap) + w * h * rgb_comp); + if (!rgb_image) { return nullptr; } + rgb_image->pixels = (agg::int8u *) (rgb_image + 1); + rgb_image->width = w; + rgb_image->height = h; + + agg::int8u *dst_ptr = rgb_image->pixels, *src_ptr = alpha_bitmap->pixels; + for (int y = 0; y < alpha_bitmap->height; y++) { + for (int x = 0; x < alpha_bitmap->width; x++) { + if (subpixel_scale == 3) { + dst_ptr[0] = 0xff - src_ptr[0]; + dst_ptr[1] = 0xff - src_ptr[1]; + dst_ptr[2] = 0xff - src_ptr[2]; + } else { + dst_ptr[0] = 0xff - src_ptr[0]; + dst_ptr[1] = 0xff - src_ptr[0]; + dst_ptr[2] = 0xff - src_ptr[0]; + } + src_ptr += subpixel_scale; + dst_ptr += rgb_comp; + } + } + return rgb_image; +} + +static FR_Bitmap *debug_bitmap_to_image_rgb_subpixel(FR_Bitmap *alpha_bitmap, const int subpixel_scale) { + const int w = alpha_bitmap->width, h = alpha_bitmap->height; + const int rgb_comp = 3; + + FR_Bitmap *rgb_image = (FR_Bitmap *) malloc(sizeof(FR_Bitmap) + subpixel_scale * w * h * rgb_comp); + if (!rgb_image) { return nullptr; } + rgb_image->pixels = (agg::int8u *) (rgb_image + 1); + rgb_image->width = subpixel_scale * w; + rgb_image->height = h; + + agg::int8u *dst_ptr = rgb_image->pixels, *src_ptr = alpha_bitmap->pixels; + for (int y = 0; y < alpha_bitmap->height; y++) { + for (int x = 0; x < alpha_bitmap->width; x++) { + for (int sub = 0; sub < subpixel_scale; sub++) { + dst_ptr[0] = 0xff - src_ptr[sub]; + dst_ptr[1] = 0xff - src_ptr[sub]; + dst_ptr[2] = 0xff - src_ptr[sub]; + dst_ptr += rgb_comp; + } + src_ptr += subpixel_scale; + } + } + return rgb_image; +} + class FR_Renderer { public: // Conventional LUT values: (1./3., 2./9., 1./9.) @@ -29,6 +90,8 @@ public: agg::lcd_distribution_lut& lcd_distribution_lut() { return m_lcd_lut; } int subpixel_scale() const { return (m_subpixel ? 3 : 1); } + std::string debug_font_name; + private: font_renderer_alpha m_renderer; agg::lcd_distribution_lut m_lcd_lut; @@ -67,6 +130,12 @@ int FR_Subpixel_Scale(FR_Renderer *font_renderer) { int FR_Load_Font(FR_Renderer *font_renderer, const char *filename) { bool success = font_renderer->renderer_alpha().load_font(filename); + if (success) { + std::string fullname = filename; + size_t a = fullname.find_last_of("/"); + size_t b = fullname.find_last_of("."); + font_renderer->debug_font_name = fullname.substr(a + 1, b - a - 1); + } return (success ? 0 : 1); } @@ -176,6 +245,97 @@ static int div_neg(int n, int p) { return n >= 0 ? (n / p) : ((n - p + 1) / p); } +static void debug_image_write_glyphs(FR_Bitmap *rgb_image, + int subpixel_scale, int num_chars, + FR_Bitmap_Glyph_Metrics *glyphs, agg::int32u color) +{ + const int rgb_comp = 3; + for (int i = 0; i < num_chars; i++) { + FR_Bitmap_Glyph_Metrics& gi = glyphs[i]; + + int y = gi.y0; + agg::int8u *row = rgb_image->pixels + (rgb_image->width * y + gi.x0) * rgb_comp; + for (int x = gi.x0; x < gi.x1; x++) { + row[0] = (agg::int32u) row[0] * (((color >> 0 ) & 0xff) + 1) / 256; + row[1] = (agg::int32u) row[1] * (((color >> 8 ) & 0xff) + 1) / 256; + row[2] = (agg::int32u) row[2] * (((color >> 16) & 0xff) + 1) / 256; + row += rgb_comp; + } + + y = gi.y1; + row = rgb_image->pixels + (rgb_image->width * y + gi.x0) * rgb_comp; + for (int x = gi.x0; x < gi.x1; x++) { + row[0] = (agg::int32u) row[0] * (((color >> 0 ) & 0xff) + 1) / 256; + row[1] = (agg::int32u) row[1] * (((color >> 8 ) & 0xff) + 1) / 256; + row[2] = (agg::int32u) row[2] * (((color >> 16) & 0xff) + 1) / 256; + row += rgb_comp; + } + + int x = gi.x0 - gi.xoff; + agg::int32u color_off = 0x0000ff; + for (int y = gi.y0; y < gi.y1; y++) { + row = rgb_image->pixels + (rgb_image->width * y + x) * rgb_comp; + row[0] = (agg::int32u) row[0] * (((color_off >> 0 ) & 0xff) + 1) / 256; + row[1] = (agg::int32u) row[1] * (((color_off >> 8 ) & 0xff) + 1) / 256; + row[2] = (agg::int32u) row[2] * (((color_off >> 16) & 0xff) + 1) / 256; + } + } +} + +static void debug_image_write_glyphs_subpixel(FR_Bitmap *rgb_image, + int subpixel_scale, int num_chars, + FR_Bitmap_Glyph_Metrics *glyphs, agg::int32u color) +{ + const int rgb_comp = 3; + for (int i = 0; i < num_chars; i++) { + FR_Bitmap_Glyph_Metrics& gi = glyphs[i]; + + int y = gi.y0; + agg::int8u *row = rgb_image->pixels + (rgb_image->width * y + subpixel_scale * gi.x0) * rgb_comp; + for (int x = gi.x0; x < gi.x1; x++) { + for (int sub = 0; sub < subpixel_scale; sub++) { + row[0] = (agg::int32u) row[0] * (((color >> 0 ) & 0xff) + 1) / 256; + row[1] = (agg::int32u) row[1] * (((color >> 8 ) & 0xff) + 1) / 256; + row[2] = (agg::int32u) row[2] * (((color >> 16) & 0xff) + 1) / 256; + row += rgb_comp; + } + } + + y = gi.y1; + row = rgb_image->pixels + (rgb_image->width * y + subpixel_scale * gi.x0) * rgb_comp; + for (int x = gi.x0; x < gi.x1; x++) { + for (int sub = 0; sub < subpixel_scale; sub++) { + row[0] = (agg::int32u) row[0] * (((color >> 0 ) & 0xff) + 1) / 256; + row[1] = (agg::int32u) row[1] * (((color >> 8 ) & 0xff) + 1) / 256; + row[2] = (agg::int32u) row[2] * (((color >> 16) & 0xff) + 1) / 256; + row += rgb_comp; + } + } + + int x = gi.x0 - gi.xoff; + agg::int32u color_off = 0x0000ff; + for (int y = gi.y0; y < gi.y1; y++) { + row = rgb_image->pixels + (rgb_image->width * y + subpixel_scale * x) * rgb_comp; + for (int sub = 0; sub < subpixel_scale; sub++) { + row[0] = (agg::int32u) row[0] * (((color_off >> 0 ) & 0xff) + 1) / 256; + row[1] = (agg::int32u) row[1] * (((color_off >> 8 ) & 0xff) + 1) / 256; + row[2] = (agg::int32u) row[2] * (((color_off >> 16) & 0xff) + 1) / 256; + row += rgb_comp; + } + } + + x = gi.x0 - gi.xoff; + agg::int32u color_adv = 0xff0000; + for (int y = gi.y0; y < gi.y1; y++) { + int x_adv = lroundf(gi.xadvance); + row = rgb_image->pixels + (rgb_image->width * y + subpixel_scale * x + x_adv) * rgb_comp; + row[0] = (agg::int32u) row[0] * (((color_adv >> 0 ) & 0xff) + 1) / 256; + row[1] = (agg::int32u) row[1] * (((color_adv >> 8 ) & 0xff) + 1) / 256; + row[2] = (agg::int32u) row[2] * (((color_adv >> 16) & 0xff) + 1) / 256; + } + } +} + FR_Bitmap *FR_Bake_Font_Bitmap(FR_Renderer *font_renderer, int font_height, int first_char, int num_chars, FR_Bitmap_Glyph_Metrics *glyphs) { @@ -244,7 +404,8 @@ FR_Bitmap *FR_Bake_Font_Bitmap(FR_Renderer *font_renderer, int font_height, i = i + 1; } - const int pixels_width = glyph_count > 0 ? (x_size_sum / glyph_count) * 16 : 12; + const int glyph_avg_width = glyph_count > 0 ? x_size_sum / (glyph_count * subpixel_scale) : font_height; + const int pixels_width = glyph_avg_width * 20; // dry run simulating pixel position to estimate required image's height int x = x_start, y = 0, y_bottom = y; @@ -275,6 +436,7 @@ FR_Bitmap *FR_Bake_Font_Bitmap(FR_Renderer *font_renderer, int font_height, const int pixels_height = -y_bottom + 1; const int pixel_size = 1; + fmt::print("image size: {} {}\n", pixels_width, pixels_height); FR_Bitmap *image = FR_Bitmap_New(font_renderer, pixels_width, pixels_height); if (!image) { free(index); @@ -345,6 +507,23 @@ FR_Bitmap *FR_Bake_Font_Bitmap(FR_Renderer *font_renderer, int font_height, free(index); free(bounds); free(cover_swap_buffer); + + std::string image_filename = fmt::format("{}-{}-{}.png", font_renderer->debug_font_name, first_char, font_height); + fmt::print("{}\n", image_filename); + + FR_Bitmap *rgb_image = debug_bitmap_to_image_rgb(image, subpixel_scale); + debug_image_write_glyphs(rgb_image, subpixel_scale, num_chars, glyphs, 0x00ff00); + stbi_write_png(image_filename.c_str(), rgb_image->width, rgb_image->height, 3, rgb_image->pixels, rgb_image->width * 3); + FR_Bitmap_Free(rgb_image); + + std::string image_filename_subpixel = fmt::format("{}-{}-{}-subpixel.png", font_renderer->debug_font_name, first_char, font_height); + fmt::print("{}\n", image_filename_subpixel); + + FR_Bitmap *rgb_image_subpixel = debug_bitmap_to_image_rgb_subpixel(image, subpixel_scale); + debug_image_write_glyphs_subpixel(rgb_image_subpixel, subpixel_scale, num_chars, glyphs, 0x00ff00); + stbi_write_png(image_filename_subpixel.c_str(), rgb_image_subpixel->width, rgb_image_subpixel->height, 3, rgb_image_subpixel->pixels, rgb_image_subpixel->width * 3); + FR_Bitmap_Free(rgb_image_subpixel); + return image; } diff --git a/lib/font_renderer/meson.build b/lib/font_renderer/meson.build index 7724d584..16a18060 100644 --- a/lib/font_renderer/meson.build +++ b/lib/font_renderer/meson.build @@ -17,7 +17,7 @@ font_renderer_include = include_directories('.') libfontrenderer = static_library('fontrenderer', font_renderer_sources, - dependencies: [libagg_dep, freetype_dep], + dependencies: [libagg_dep, freetype_dep, stb_image_dep, fmt_dep], cpp_args: font_renderer_cdefs, ) diff --git a/meson.build b/meson.build index 4da88afa..8493bca7 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('lite', 'c', 'cpp', default_options : ['c_std=gnu11', 'cpp_std=c++03']) +project('lite', 'c', 'cpp', default_options : ['c_std=gnu11', 'cpp_std=c++11']) cc = meson.get_compiler('c') libm = cc.find_library('m', required : false) @@ -14,6 +14,9 @@ endif sdl_dep = dependency('sdl2', method: 'config-tool') +stb_image_dep = dependency('stb_image') +fmt_dep = dependency('fmt') + lite_cargs = [] if get_option('portable') lite_datadir = 'data'