1
General discussions / Better font rendering
« on: March 29, 2019, 03:08:18 am »
I'm always dissatisfied with SFML font rendering quality. I know it has been improved for the last several years, but it is still hard to say good enough. So I've been trying to improve it myself last few days and I found FT_LOAD_FORCE_AUTOHINT flag in sf::Font makes more bad result mostly.
Check this. The first one is default SFML text and the second one is SFML text without FT_LOAD_FORCE_AUTOHINT flag, the last one is my implementation of SFML + freetype + harfbuzz. Interestingly they all have different letter spacing even though they all have the same character size. I don't know which one is correct, but the result of my implementation is exactly the same as the original code (https://github.com/lxnt/ex-sdl-freetype-harfbuzz)
You easily find ugly rendered Korean text(the red box, only few characters here but generally I could found a lot). Maybe you can't find significant differences between English texts here, but I'm sure it's not just a problem of non-English text. Usually in small texts, SFML font rendering is not so nice.
Any ideas?
I also attach my short example code.
Sorry for my bad English
Edit: fixed typo
Check this. The first one is default SFML text and the second one is SFML text without FT_LOAD_FORCE_AUTOHINT flag, the last one is my implementation of SFML + freetype + harfbuzz. Interestingly they all have different letter spacing even though they all have the same character size. I don't know which one is correct, but the result of my implementation is exactly the same as the original code (https://github.com/lxnt/ex-sdl-freetype-harfbuzz)
You easily find ugly rendered Korean text(the red box, only few characters here but generally I could found a lot). Maybe you can't find significant differences between English texts here, but I'm sure it's not just a problem of non-English text. Usually in small texts, SFML font rendering is not so nice.
Any ideas?
I also attach my short example code.
Sorry for my bad English
#include <SFML/Graphics.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <harfbuzz/hb.h>
#include <harfbuzz/hb-ft.h>
int main()
{
const std::string filename = "fonts/KoPub Dotum Bold.ttf";
const sf::String string = L"The quick brown fox jumps over the lazy dog. 다람쥐 헌 쳇바퀴에 타고파. 글굴귤";
constexpr int font_size = 20;
constexpr int width = 800;
constexpr int height = 600;
sf::RenderWindow window(sf::VideoMode(width, height), "SFML Text");
sf::Font font;
font.loadFromFile(filename);
sf::Text text(string, font, font_size);
text.setPosition(50.f, 50.f);
FT_Library ft_library;
FT_Face ft_face;
if (FT_Init_FreeType(&ft_library) ||
FT_New_Face(ft_library, filename.c_str(), 0, &ft_face) ||
FT_Set_Char_Size(ft_face, font_size * 64, font_size * 64, 0, 0))
return 1;
hb_font_t* hb_font = hb_ft_font_create(ft_face, NULL);
hb_buffer_t* hb_buffer = hb_buffer_create();
hb_buffer_add_utf32(hb_buffer, string.getData(), -1, 0, -1);
hb_buffer_guess_segment_properties(hb_buffer);
hb_shape(hb_font, hb_buffer, NULL, 0);
unsigned int glyph_count = hb_buffer_get_length(hb_buffer);
hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(hb_buffer, NULL);
hb_glyph_position_t* glyph_pos = hb_buffer_get_glyph_positions(hb_buffer, NULL);
std::vector<sf::Uint8> pixelbuffer;
pixelbuffer.resize(width * height * 4, 0);
{
sf::Uint8* current = pixelbuffer.data();
sf::Uint8* end = current + width * height * 4;
while (current != end)
{
(*current++) = 255;
(*current++) = 255;
(*current++) = 255;
(*current++) = 0;
}
}
int current_x = 50;
int current_y = 100;
// render
for (unsigned int i = 0; i < glyph_count; ++i)
{
if (FT_Load_Glyph(ft_face, glyph_info[i].codepoint, 0))
return 1;
current_x += glyph_pos[i].x_offset / 64;
current_y += glyph_pos[i].y_offset / 64;
FT_GlyphSlot ft_slot = ft_face->glyph;
FT_Render_Glyph(ft_slot, FT_RENDER_MODE_NORMAL);
FT_Bitmap ft_bitmap = ft_slot->bitmap;
int w = ft_bitmap.width;
int h = ft_bitmap.rows;
int left = ft_slot->bitmap_left;
int top = ft_slot->bitmap_top;
const sf::Uint8* buffer = ft_bitmap.buffer;
if (ft_bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
return 1;
constexpr int padding = 1;
w += 2 * padding;
h += 2 * padding;
for (int y = padding; y < h - padding; ++y)
{
for (int x = padding; x < w - padding; ++x)
{
if (buffer[x - padding] > 0)
{
std::size_t index = (current_x + x + left) + (current_y + y - top) * width - padding;
pixelbuffer[index * 4 + 3] = buffer[x - padding];
}
}
buffer += ft_bitmap.pitch;
}
current_x += glyph_pos[i].x_advance / 64;
current_y += glyph_pos[i].y_advance / 64;
}
//
hb_buffer_clear_contents(hb_buffer);
sf::Image image;
image.create(width, height, pixelbuffer.data());
sf::Texture texture;
texture.loadFromImage(image);
sf::Sprite sprite(texture);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(text);
window.draw(sprite);
window.display();
}
hb_buffer_destroy(hb_buffer);
hb_font_destroy(hb_font);
FT_Done_Face(ft_face);
FT_Done_FreeType(ft_library);
return 0;
}
#include <ft2build.h>
#include FT_FREETYPE_H
#include <harfbuzz/hb.h>
#include <harfbuzz/hb-ft.h>
int main()
{
const std::string filename = "fonts/KoPub Dotum Bold.ttf";
const sf::String string = L"The quick brown fox jumps over the lazy dog. 다람쥐 헌 쳇바퀴에 타고파. 글굴귤";
constexpr int font_size = 20;
constexpr int width = 800;
constexpr int height = 600;
sf::RenderWindow window(sf::VideoMode(width, height), "SFML Text");
sf::Font font;
font.loadFromFile(filename);
sf::Text text(string, font, font_size);
text.setPosition(50.f, 50.f);
FT_Library ft_library;
FT_Face ft_face;
if (FT_Init_FreeType(&ft_library) ||
FT_New_Face(ft_library, filename.c_str(), 0, &ft_face) ||
FT_Set_Char_Size(ft_face, font_size * 64, font_size * 64, 0, 0))
return 1;
hb_font_t* hb_font = hb_ft_font_create(ft_face, NULL);
hb_buffer_t* hb_buffer = hb_buffer_create();
hb_buffer_add_utf32(hb_buffer, string.getData(), -1, 0, -1);
hb_buffer_guess_segment_properties(hb_buffer);
hb_shape(hb_font, hb_buffer, NULL, 0);
unsigned int glyph_count = hb_buffer_get_length(hb_buffer);
hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(hb_buffer, NULL);
hb_glyph_position_t* glyph_pos = hb_buffer_get_glyph_positions(hb_buffer, NULL);
std::vector<sf::Uint8> pixelbuffer;
pixelbuffer.resize(width * height * 4, 0);
{
sf::Uint8* current = pixelbuffer.data();
sf::Uint8* end = current + width * height * 4;
while (current != end)
{
(*current++) = 255;
(*current++) = 255;
(*current++) = 255;
(*current++) = 0;
}
}
int current_x = 50;
int current_y = 100;
// render
for (unsigned int i = 0; i < glyph_count; ++i)
{
if (FT_Load_Glyph(ft_face, glyph_info[i].codepoint, 0))
return 1;
current_x += glyph_pos[i].x_offset / 64;
current_y += glyph_pos[i].y_offset / 64;
FT_GlyphSlot ft_slot = ft_face->glyph;
FT_Render_Glyph(ft_slot, FT_RENDER_MODE_NORMAL);
FT_Bitmap ft_bitmap = ft_slot->bitmap;
int w = ft_bitmap.width;
int h = ft_bitmap.rows;
int left = ft_slot->bitmap_left;
int top = ft_slot->bitmap_top;
const sf::Uint8* buffer = ft_bitmap.buffer;
if (ft_bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
return 1;
constexpr int padding = 1;
w += 2 * padding;
h += 2 * padding;
for (int y = padding; y < h - padding; ++y)
{
for (int x = padding; x < w - padding; ++x)
{
if (buffer[x - padding] > 0)
{
std::size_t index = (current_x + x + left) + (current_y + y - top) * width - padding;
pixelbuffer[index * 4 + 3] = buffer[x - padding];
}
}
buffer += ft_bitmap.pitch;
}
current_x += glyph_pos[i].x_advance / 64;
current_y += glyph_pos[i].y_advance / 64;
}
//
hb_buffer_clear_contents(hb_buffer);
sf::Image image;
image.create(width, height, pixelbuffer.data());
sf::Texture texture;
texture.loadFromImage(image);
sf::Sprite sprite(texture);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(text);
window.draw(sprite);
window.display();
}
hb_buffer_destroy(hb_buffer);
hb_font_destroy(hb_font);
FT_Done_Face(ft_face);
FT_Done_FreeType(ft_library);
return 0;
}
Edit: fixed typo