我使用 FreeType 库做了一些简单的文本渲染。使用字体“SourceHanSansCN-Medium”,字号为12像素。渲染结果字母之间的距离很奇怪。例如,如果我渲染“Wet”,字母
W
和 e
彼此远离,但 e
和 t
紧贴:
我尝试在 Inkscape 中使用相同的字体文件,正确的字母距离看起来好多了:
我对 FreeType 不是很熟悉,因为它是一个相当大的系统。是否有任何选项可以控制字形距离的行为?
是不是subpixel hinting造成的?如果是这样,是否可以仅在水平线上启用提示并在垂直线上禁用提示,从而保持水平距离?
下面附上文字渲染代码,和FreeType文档中的示例代码很像:
void MyPainter::paintOneGlyphImage( juce::Image& target, juce::Point< int > bitmap_offset, const FT_Bitmap& bitmap )
{
if ( bitmap.pitch == 0 ) return;
const int px_bits = (bitmap.pixel_mode == FT_PIXEL_MODE_LCD || bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) ? 3 : 1;
int row_src_begin = std::max( -bitmap_offset.y, 0 );
int row_src_end = std::min( target.getHeight() - bitmap_offset.y, bitmap.rows );
int col_src_begin = std::max( -bitmap_offset.x, 0 );
int col_src_end = std::min( target.getWidth() - bitmap_offset.x, bitmap.width / px_bits );
for ( int row_src = row_src_begin; row_src < row_src_end; ++row_src )
{
int row_tgt = row_src + bitmap_offset.y;
jassert( 0 <= row_tgt && row_tgt < target.getHeight() );
const unsigned char* row_data = bitmap.buffer + row_src * bitmap.pitch;
for ( int col_src = col_src_begin; col_src < col_src_end; col_src += 1 )
{
int col_tgt = col_src + bitmap_offset.x;
jassert( 0 <= col_tgt && col_tgt < target.getWidth() );
const unsigned char* px_data = row_data + col_src * px_bits;
juce::Colour px_color;
switch ( bitmap.pixel_mode )
{
case FT_PIXEL_MODE_MONO:
if ( _glyph_bit_( bitmap, col_src, row_src ) )
{
px_color = guts->mColor;
guts->mPixelNum++;
}
break;
case FT_PIXEL_MODE_GRAY:
px_color = guts->mColor.withAlpha( *px_data ); // withGamma( mColor.withAlpha( *px_data ), mGamma );
if ( *px_data > 127 ) guts->mPixelNum++;
break;
case FT_PIXEL_MODE_LCD:
case FT_PIXEL_MODE_LCD_V:
px_color = getColorFromSubPixel( guts->mColor, px_data[0], px_data[1], px_data[2] );
if ( (px_data[0] + px_data[1] + px_data[2]) / 3 > 127 ) guts->mPixelNum++;
break;
default:
jassertfalse;
px_color = guts->mColor;
}
target.setPixelAt( col_tgt, row_tgt, px_color );
}
}
}
void MyPainter::generateFontImage()
{
// set font size
// create target image object by font size and letter amount
FT_Set_Char_Size( guts->mTypeface, 0, guts->m_font_size << 6, unsigned( 72 * guts->mDpiScale ), unsigned( 72 * guts->mDpiScale ) );
auto scaled_metric = guts->mTypeface->size->metrics;
juce::Image tmpImage( juce::Image::ARGB, int( scaled_metric.max_advance >> 6 ) * guts->mContent.length(), int( scaled_metric.height >> 6 ), true );
auto chars = guts->mContent.toWideCharPointer();
// step one by one for all letters
FT_Vector pen = { 0, 0 };
for ( int k = 0; k < guts->mContent.length(); ++k )
{
FT_Set_Transform( guts->mTypeface, nullptr, &pen );
int re = FT_Load_Char( guts->mTypeface, FT_ULong( chars[k] ), FT_LOAD_RENDER | ((!guts->mHinting) ? FT_LOAD_NO_HINTING : 0) | int( guts->mRenderMode ) );
if ( re != FT_Err_Ok ) // FT_LOAD_NO_HINTING
{
std::cout << "ERROR::FREETYTPE: Failed to load Glyph: " << re << std::endl;
jassertfalse;
}
auto* glyph = guts->mTypeface->glyph;
int paint_x = glyph->bitmap_left;
int paint_y = int( (scaled_metric.ascender >> 6) - glyph->bitmap_top );
paintOneGlyphImage( tmpImage, { paint_x, paint_y }, glyph->bitmap );
pen.x += glyph->advance.x;
pen.y += glyph->advance.y;
}
}