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.
This commit is contained in:
Francesco Abbate 2021-03-09 14:08:02 +01:00
parent 5bf7abf23d
commit 17b90c25ca
3 changed files with 185 additions and 3 deletions

View File

@ -1,3 +1,5 @@
#include <fmt/core.h>
#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;
}

View File

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

View File

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