ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
59.55k stars 10.14k forks source link

How to load txt-based .fnt and .ttf files #102

Closed Flix01 closed 9 years ago

Flix01 commented 9 years ago

In the weekend I made this imgui "extension", mainly to allow imgui users to load .fnt files in "text" format (actually it should autodetect if the .fnt is "text" or "binary" during the loading phase). Using the "text" .fnt format can be a useful way to improve code portability by solving endianess related problems (please see https://github.com/ocornut/imgui/issues/81).

As an extra I've added an optional preprocessor definition that can be used, together with stb_truetype.h, to load .ttf files (it's experimental, it was not my main concern, but I found out that I could reuse a lot of the code I had just written and then I made it).

Here is the gist with code (with usage instructions in the header file): https://gist.github.com/Flix01/13374fabd73214f82362

ocornut commented 9 years ago

Thanks, I will look at it. I may want to take the stb_truetype/TTF code and rework it to integrate in the main code.

Converting to a common format optimal for runtime makes sense (this common format may or not be the current bitmap font format, let's start with that since this is what we have now but it could possibly change), eventually the font loader could just convert whatever input in the format ImGui uses internally, like you did.

I had noticed that something about the font baseline/display offset isn't handled properly in the current code. Need to have a look at it.

ocornut commented 9 years ago

Looking at it now. I'll be using the new stb_rect_pack.h API and subsamping.

What is the purpose of bool allowNonSquaredBitmap=false in your code? Is it ever an advantage or requirement to have square textures?

ocornut commented 9 years ago

Testing Arial Unicode rendered with stb_truetype.h (without kerning / subpixel rendering yet)

truetype

proggy_clean.ttf also works well now. Identical output as the bitmap one. I'd be tempted to drop the bitmap version but the .ttf size would affect the size of imgui.cpp very noticeably. Might look into finding if embedding a small compressor would be a good middle ground.

Still lots of work to do. Fixing alignment / scaling issues. Testing with different renderers. Using the better packing. Handling errors and corner cases (there's various bugs in your version nothing major). Cleaning up. Adding the API or finding the best way for the user to specify character ranges (or make it automatic). Consider real world scenario or using different fonts and varying scale (possibly provide cache/helper for sizes). Figuring out how this may or not affect the initialisation step and the setup (avoid breakage, but there will be "some", e.g. the UV previously used with bitmap font required disabling bilinear filtering, so users will need to re-enable that in their codebase, etc.).

The important side-effect improvement is that if ImGui has control over the texture before it is passed into GPU then I can easily poke things manually.

Also I can derive of using structures that are binary matches for AngelFont and uses something that more optimal for runtime (even if it means converted angel fonts during init).

Lots of improvements ahead but I will probably have to shelve my work and resume later.

Flix01 commented 9 years ago

What is the purpose of bool allowNonSquaredBitmap=false in your code? Is it ever an advantage or requirement to have square textures?

The output bitmap width and height can be bigger than the input width and height: if that happens allowNonSquaredBitmap can be used to double the bitmap width only and doing an additional try before doubling the bitmap height too (note that the name is a bit misleading: it just deals with doubling the input parameters in a separate way). That may result in smaller textures (= less memory). However I've never tried it...

I'd be tempted to drop the bitmap version...

Then we can say goodbye to outline font support...

The important side-effect improvement is that if ImGui has control over the texture before it is passed into GPU then I can easily poke things manually.

Yes, but usually we can retrieve the bitmap data for .fnt files too (for example by using stb_image to manually load the texture, before feeding it to opengl/direct3d, or by finding a way to download pixel data from the graphic card): this way we can handle the texture ourselves when using .fnt files too.

P.S. As I've written, I did not need true type font support, because I can easily embed .fnt files in .txt format for portability (and this way I can use outline fonts too). That's why I hope you won't drop .fnt files...

P.S.2. One advanced topic for making the font look good at different scaling values would be to add a manual mipmap generator. Libraries like fontstash solve this problem continuously adding glyphs at different sizes to a set of textures: that's the best possible solution if we can waste texture space (although I don't like this approach). Automatic generation of mipmaps on font texture often produces artifacts/texture bleeding. Manually generating mipmaps by scaling each glyphs separately seems to be a possible way to avoid these artifacts. Here is a link about a similiar approach: http://www.google.it/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0CCEQFjAA&url=http%3A%2F%2F0fps.net%2F2013%2F07%2F09%2Ftexture-atlases-wrapping-and-mip-mapping%2F&ei=qAOtVJXAGoHdasfVgcgH&usg=AFQjCNFk12Jnl3yBAvYQ9YDGNnGEZkoBzg&bvm=bv.83134100,d.d2s However this is an advanced topic (this approach is often used only when the font is displayed in a 3D environment).

ocornut commented 9 years ago

Re. allowNonSquaredBitmap - so my question stands, what is the point in enforcing square texture when one can save space with non-square textures ?

We could also use distance-field textures for scalable fonts (thought it my experience they aren't that good with very small sizes).

Either way I think it is a bit over-reaching to allow freely scalable font and try to optimally solve every case. ImGui is still meant to be for building debug tools and doesn't need to be QT or HTML. It should be fairly easy for the users to generate a few sizes if they need them.

How are you using outline fonts, do you have a screen-shot ? I'm just curious, it feels a bit old-school (and that's coming from me, I usually like old-school).

Flix01 commented 9 years ago

Re. allowNonSquaredBitmap - so my question stand, what is the point in enforcing square texture when one can save space with non-square textures ?

Sorry. I thought some openGL implementations might not support non-squared textures: but I have to check if this is true...

We could also use distance-field textures for scalable fonts (thought it my experience they aren't that good with very small sizes).

Yes, that's the number-one option, but it's probably too much for imgui... my proposal was intended only as an advanced option, if we allow a single size font.

Either way I think it is a bit over-reaching to allow freely scalable font and try to optimally solve every case. ImGui is still meant to be for building debug tools and doesn't need to be QT or HTML. It should be fairly easy for the users to generate a few sizes if they need them.

Fully agreed.

How are you using outline fonts, do you have a screen-shot ? I'm just curious, it feels a bit old-school (and that's coming from me, I usually like old-school).

Default imgui font: selection_037

DejaVu Serif Condensed Outline: selection_036

Adding the API or finding the best way for the user to specify character ranges (or make it automatic).

The best way for the user would be to simply pass a UTF8 character string. I don't know the imgui internals, so I don't know if there are methods for extracting utf8 codepoints from a string (a char*): to do so I use is the piece of code that fontstash library includes: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/

I like it. Bjoern Hoehrmann's code is very concise and it just works. Obviously I did not include it in my gist to keep the code short enough. Also note that some old compilers might not accept UTF8 encoded source files and thus it should be better to just add some method to convert a utf8 string to a vector of codepoints, and a loadFromTTF(...) overload that calls the existent code.

ocornut commented 9 years ago

Passing a string wouldn't work, I'm talking about huge range here for CJK (I think the recommended range for Chinese has 21 thousands characters).

I'm adding function like that in ImFont my work version

// Retrieve list of ranges for common usage (2 value per range, values are inclusive, zero-terminate list)
static IMGUI_API const ImWchar*   GetGlyphRangesDefault();    // Basic Latin, Extended Latin + a few more
static IMGUI_API const ImWchar*   GetGlyphRangesCJK();   // Same as above + Hiragana, Katakana, Half-Width, CJK Unified Ideographs (0x4e00..0x9FCC) // FIXME: missing some

Probably a separate function for just Japanese/Korean. The LoadTTF function defaults to GetGlyphRangesDefault().

Thanks for the link, interesting stuff (I wanted to optimize the UTF-8 decoding possibly).

Flix01 commented 9 years ago

Passing a string wouldn't work, I'm talking about huge range here for CJK (I think the recommended range for Chinese has 21 thousands characters).

Oh, that's OK then. However my version only supports a single texture, so adding too many characters to it would generate a single huge bitmap (and I doubt that such a bitmap could be loaded into an OpenGL/Direct3D texture). Hope people from China can choose a wisely reduced character subset :).

ocornut commented 9 years ago

A 2048x2048 texture works well, you can fit e.g. a baked 15x15 full character set. It's just nice to make those usage possible. I'll see if I can finish the change today.

ocornut commented 9 years ago

I am committing to a branch https://github.com/ocornut/imgui/tree/2015-01-truetype

Would be nice if someone tested the TTF branch in their real app and posted feedback on it. Namely, I have broken the initialization procedure:

I an unhappy about the breakages and still figuring out if / how I can alleviate them. Additionally some of those setup api may change before I merge back in.

Also .bmf font loading was dropped, it was just much more simple this way. It is possible to resurrect it by converting the .bmf data to the new runtime structure and ensuing that extra space is added to the texture. I don't think it is worth it but it is possible.

Refer to branch commits https://github.com/ocornut/imgui/commit/b3a208901a089b0930ff1d265a024da8832e7df0

ocornut commented 9 years ago

Will also merge in https://github.com/ocornut/imgui/issues/73

Which will add a reference back to the texture in the ImDrawCmd (the identifier probably in the form of a user-set void*). Allowing for multiple fonts to be used along with images.

Need to consider the side-effect of using a 1 channel texture for the font. If the user wants to mix font and images (unlikely to be 1 channel) then their texture identifier has to become more of a "material / shader" identifier which be more annoying to setup on some codebase? A way to simplify would be to turn the font texture into RGBA at slight GPU bandwidth cost (probably minor), will probably do that. Or even a way to request the texture pixels data from ImGui and it can come in both Alpha-only or RGBA formats.

For multiple fonts later on should provide helpers to pack them into a single texture.

ocornut commented 9 years ago

Also note that the stb_truetype.h embedded in the new branch is a modification of the original. The main modification allows to determinate the size of the atlas without performing any rendering, to avoid incrementally trying/failing/resizing (unacceptable for big fonts).

memononen commented 9 years ago

Have you thought about adding glyphs dynamically to the atlas as you go? I do that in fontstash [1], and it simplifies multi font case a lot.

[1] https://github.com/memononen/fontstash

ocornut commented 9 years ago

Might do but also trying to keep it simple so I'll skip on that right now. I still expect people to use ASCII-extended set for ImGui so dynamic atlas would mainly make sense for dynamic font resizing. I'm very content right now with what the change brings (TTF, imgui's own texture space, image display soon) so there's a fair bit to work on from there.

ocornut commented 9 years ago

I am pretty much done with this branch and improved things a fair bit since the last post.

The only breakage is that: ImGui::GetDefaultFontData()

Became io.Font->GetTextureData**() (variants for RGBA32 and Alpha8 types).

I am keeping the breakage because earlier had different parameters and returned PNG data whereas new API returns uncompressed pixels. Might add a dummy GetDefaultFontData() that asserts and inform programmer with the fix.

Also added a new field in ImDrawCmd for additional textures but user only needs to handle it if they use images or multiple fonts (which are both new features) so it won't be perceived as breakage.

New API ImGui::Image() to display textures. Will add the other API from #73.

Here's how it looks with Arial Unicode, size 20, no oversampling. Overkill baking and rect-packing 21472 chinese glyphs for the test ( cc: @nothings if he wants to see a success story ) The texture size is computed from the packing before rendering. I have pretty much rewrote all the code since flix01's gist but the Gist helped to guide me through stb_truetype.

arial unicode 20

nothings commented 9 years ago

Regarding outline fonts, I have a separate fork of stb_truetype at work that generates outlines (or rather just dilates characters so that it looks like an outline when the regular character is drawn on top of it), and I'll be integrating that back into the public stb_truetype at some point.

Flix01 commented 9 years ago

nothings wrote: Regarding outline fonts, I have a separate fork of stb_truetype at work...

Hope that will be integrated into ImGui: that will help me regaining happiness after the announced drop of .bmf files...

P.S. I might be wrong, but I think that the outline of .bmf fonts is not made by simply pasting a smaller glyph on the top of a bigger one: maybe there's some way of scaling the bezier curves directly (outward for the outer contours and inward for the inner ones, or viceversa depending on the approach we take...). However I'm not too sure about it. In any case thank you for the branch!

nothings commented 9 years ago

Yes, "dilating" means expanding it in all directions, rather than just using a larger version of the character (in which case you wouldn't get outlines on the holes and such). Doing it with the beziers might be conceptually simpler, but there's problems when they self-overlap, so it's easier to just use a bitmap operation (in this case, it's essentially a gaussian blur and then a threshhold operation).

Flix01 commented 9 years ago

Thank you for your explanation! That means that the outline will look good!

ocornut commented 9 years ago

Merged the TTF branch into trunk.

I spent a considerable amount of time getting this right

The basic use case is:

ImGuiIO& io = ImGui::GetIO();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsAlpha8(&pixels, &width, &height);

But you can do:

ImGuiIO& io = ImGui::GetIO();
ImFont* my_font1 = io.Fonts->AddFontDefault();
ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese());
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsAlpha8(&pixels, &width, &height);

Both fonts built into same atlas, merging draw calls (so most windows will still be 2 draw calls at the moment).

\ ImGui styling is still rather awkward, lots styling parameters are specified in pixels and thus may not scale the way you expect if you rescale a font. The library is still intended to minimize screen real estate use but better styling would be helpful. Styling is also awkward because of the semantic of some colors items can sometimes be unclear. That will be a future thing to work on.

I'd appreciate feedback on transitioning to this version. If there's any way I can improve the various bits of documentation, etc.

Flix01 commented 9 years ago

b>@ocornut</b I wonder if you're interested in adding Angel Bitmap .fnt file support again in the master branch.

I'm currently making some early experiments on it. However my results are not as good as I thought: outline text looks a bit blurry (and it seems smaller: is it scaled down by default?) compared to what it used to look in the old ImGui version that supported it. Here is a w.i.p. screenshot (you can compare it to the one I posted some posts above): fntsupportagain

At my current state of the code I don't think .fnt support is worthy of being readded (but if you need it, I can try refining the code and post a .diff file somewhere...).

P.S. I'm not using any embedded image loader, the new AddFont method takes a pointer to the font bitmap (users can use any image library they like).

P.S.2.This is about Signed Distance Fonts. I've just found a free tool that can export a .fnt file of a signed-distance-bitmap-font here: http://www.gamedev.net/topic/491938-signed-distance-bitmap-font-tool/ (the .fnt format exported by this tool is outdated and misses some data; but it's very easy to fix, as its code is very compact, portable and easy to tweak to be compatible with the txt .fnt format I can load into ImGui). The main problem seems to be that signed-distance-bitmap-fonts require a specialized shader program to be used and I'm not sure that in ImGui I can retrieve the rendering lists on a per-font basis. I've done some experiments rendering everything with the signed-distance shader (obviously gui elements look wrong): I've discovered that this kind of fonts can be handy if we want to scale the fonts: bigger scaling looks good, but shrinking doesn't show any improvement AFAICS. The drawback is that a shader becomes mandatory; another advantage beside scaling is that we can optionally draw an outline in the shader. Here are some experimental images: signeddistancefont_fontscale1 6 Font Scale = 1.6 signeddistancefont_outlineinshader_test Outline Drawn In The Shader

ocornut commented 9 years ago

I have so much higher priorities on ImGui now that I'll just let this slide until stb_truetype eventually support outlining. It'll be more sane and simple. We could perfectly request SDF texture data from the Atlas, it'd be up to the user if they want bitmap or SDF. stb_truetype will also probably support outputting SDF at some point, it would make sense (see self-assigned issue https://github.com/nothings/stb/issues/54 )

Flix01 commented 9 years ago

We could perfectly request SDF texture data from the Atlas, it'd be up to the user if they want bitmap or SDF.

As I've written in the last post I'm not sure that ImGui allows retrieving the rendering lists on a per-font basis (SDF fonts require a specialized shader). Unless you mean having a specialized texture for SDF fonts (it's an alpha texture that can be merged to the main ImGui texture).

stb_truetype will eventually support outlining. stb_truetype will also probably support outputting SDF at some point.

That's nice, as long as the creation process will be fast enough (there might be thousands of glyphs to process). Beside that, readding .fnt support wouldn't have broken anything existent. But of course that's up to you. So I accept your decision.

ocornut commented 9 years ago

Yes SDF won't be on by default, hypothetically user would enable it and then the atlas would allow you to retrieve a SDF texture and then it is up to the user to render it.