Advertisement

[Freetype] Cannot render font to a bitmap

Started by May 09, 2015 02:33 PM
2 comments, last by Zylann 9 years, 6 months ago

Hello,

I'm integrating Freetype into my project, but so far I've never been able to render any font into a bitmap.

Freetype functions never returns an error, the bitmap I get is just empty, width and height are zero, whatever character I pass.

The fon't I'm using is a basic outline TTF.

In my project, I split the code in 2 files: FontLoader.cpp and Font.cpp.

FontLoader is a class that holds the FT_Library, and Font holds the FT_Face.

Here is the part of my code that uses Freetype (unrelated code eluded for clarity):

FontLoader.hpp


//...
#include <core/asset/AssetLoader.hpp>

#include <ft2build.h>
#include FT_FREETYPE_H

namespace freetype
{

class FontLoader : public sn::AssetLoader
{
public:

    //...

    bool load(std::ifstream & ifs, sn::Asset & asset) const override;

    //...

private:
    FT_Library m_library;

};

} // freetype
//...

FontLoader.cpp


//...
#include "Font.hpp"
#include "FontLoader.hpp"

using namespace sn;

namespace freetype
{

FontLoader::FontLoader():
    m_library(nullptr)
{
    // Initialize Freetype
    if (FT_Init_FreeType(&m_library) != 0)
    {
        SN_ERROR("Failed to initialize FreeType library");
    }
}

FontLoader::~FontLoader()
{
    if (m_library != 0)
    {
        // Deinitialize Freetype
        FT_Done_FreeType(m_library);
    }
}

//...

bool FontLoader::load(std::ifstream & ifs, sn::Asset & asset) const
{
    freetype::Font * font = sn::checked_cast<freetype::Font*>(&asset);
    SN_ASSERT(font != nullptr, "Invalid asset type");

    // Read the whole stream
    ifs.seekg(0, ifs.end);
    u32 len = ifs.tellg();
    ifs.seekg(0, ifs.beg);
    char * data = new char[len];
    ifs.read(data, len);

    // Load the face
    FT_Face face;
    if (FT_New_Memory_Face(m_library, reinterpret_cast<const FT_Byte*>(data), len, 0, &face) != 0)
    {
        SN_ERROR("Failed to create Freetype font face from memory");
        delete[] data;
        return false;
    }
    delete[] data;

    // Select the unicode character map
    if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
    {
        SN_ERROR("Failed to select the Unicode character set (Freetype)");
        return false;
    }

    // Store the loaded font
    font->setFace(face);

    return true;
}

} // namespace freetype

Font.hpp


//...
#include <ft2build.h>
#include FT_FREETYPE_H

namespace freetype
{

class Font : public sn::Font, public sn::NonCopyable
{
    //...
private:
    bool generateGlyph(sn::Glyph & out_glyph, sn::u32 unicode, sn::FontFormat format) const;
    //...
    bool setCurrentSize(sn::u32 characterSize) const;

private:
    FT_Face                                 m_face;
    //...

};

} // namespace freetype
//...

Font.cpp


#include "Font.hpp"

#include FT_GLYPH_H
#include FT_OUTLINE_H
#include FT_BITMAP_H
//...
bool Font::generateGlyph(Glyph & out_glyph, sn::u32 unicode, sn::FontFormat format) const
{
    Glyph glyph;

    if (!setCurrentSize(format.size))
        return false;

    // Load the glyph corresponding the unicode
    if (FT_Load_Char(m_face, unicode, FT_LOAD_TARGET_NORMAL) != 0)
        return false;

    // Retrieve the glyph
    FT_Glyph glyphDesc;
    if (FT_Get_Glyph(m_face->glyph, &glyphDesc) != 0)
        return false;

    // Apply bold
    FT_Pos weight = 1 << 6;
    bool outline = (glyphDesc->format == FT_GLYPH_FORMAT_OUTLINE);
    if (format.isBold() && outline)
    {
        FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc;
        FT_Outline_Embolden(&outlineGlyph->outline, weight);
    }

    // Convert the glyph to a bitmap (i.e. rasterize it)
    if (glyphDesc->format != FT_GLYPH_FORMAT_BITMAP)
    {
        if (FT_Glyph_To_Bitmap(&glyphDesc, FT_RENDER_MODE_NORMAL, 0, 1) != 0)
        {
            SN_ERROR("Failed to convert glyph to bitmap");
        }
    }
    FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph)glyphDesc;
    FT_Bitmap& bitmap = bitmapGlyph->bitmap;

    // Compute the glyph's advance offset
    glyph.advance = glyphDesc->advance.x >> 16;
    if (format.isBold())
        glyph.advance += weight >> 6;

    u32 width  = bitmap.width;
    u32 height = bitmap.rows;
    if (width > 0 && height > 0)
    {
        // NEVER ENTERS HERE

        // Funny conversion stuff
        //...
    }
    else
    {
        SN_DLOG("Character " << unicode << " (ascii: " << (char)unicode << ") has an empty bitmap");
    }

    // Delete the FT glyph
    FT_Done_Glyph(glyphDesc);

    out_glyph = glyph;

    return true;
}

//...
bool Font::setCurrentSize(sn::u32 characterSize) const
{
    SN_ASSERT(m_face != nullptr, "Invalid state: Freetype font face is null");

    FT_UShort currentSize = m_face->size->metrics.x_ppem;

    if (currentSize != characterSize)
    {
        return FT_Set_Pixel_Sizes(m_face, 0, characterSize) == 0;
    }
    else
    {
        return true;
    }
}
//...

EDIT: by stepping into FT_Glyph_To_Bitmap and further, I discovered this:

ftobjs.c


          error = renderer->render( renderer, slot, render_mode, NULL );
          if ( !error                                   ||
               FT_ERR_NEQ( error, Cannot_Render_Glyph ) ) // This line is reached
            break;

ftrend1.c


renderer->render being a function pointer, I steeped in it too:
    /* check rendering mode */
#ifndef FT_CONFIG_OPTION_PIC
    if ( mode != FT_RENDER_MODE_MONO )
    {
      /* raster1 is only capable of producing monochrome bitmaps */
      if ( render->clazz == &ft_raster1_renderer_class )
        return FT_THROW( Cannot_Render_Glyph ); // THIS LINE IS REACHED
    }
    else
    {
      /* raster5 is only capable of producing 5-gray-levels bitmaps */
      if ( render->clazz == &ft_raster5_renderer_class )
        return FT_THROW( Cannot_Render_Glyph );
    }
#else /* FT_CONFIG_OPTION_PIC */

I want to render with the FT_RENDER_NORMAL mode, but for some reason it seems Freetype can't sad.png

I'm not 100% sure, but I think you ought to be using FT_Render_Glyph rather than FT_Glyph_To_Bitmap.

My understanding is that FT_Glyph_To_Bitmap is converting your glyph into a different representation, while FT_Render_Glyph is the function that actually renders a glyph into the bitmapGlyph->bitmap structure.

Edit: Section 7 of the tutorial has an example of using FT_Render_Glyph : http://www.freetype.org/freetype2/docs/tutorial/step1.html

Advertisement

I really like freetype-gl as a sample implementation for how to go about doing this and various other freetype tasks.

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.

I finally found what was going on:


    // Load the face
    FT_Face face;
    if (FT_New_Memory_Face(m_library, reinterpret_cast<const FT_Byte*>(data), len, 0, &face) != 0)
    {
        SN_ERROR("Failed to create Freetype font face from memory");
        delete[] data;
        return false;
    }
    delete[] data; // DO NOT DELETE THIS...

When FT_New_Memory_Face is used, it doesn't takes ownership or copies the data, so I must keep it alive somewhere until I call FT_Done_Face.
I added a field in my Font class to hold this data and delete it in the destructor.

Also, it seems I had to include a few more headers so when I initialize the library, Freetype loads appropriate modules to rasterize smoothly.

Now everything works fine smile.png

This topic is closed to new replies.

Advertisement