Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » [OpenLayer] TextRenderer::getHeight()

Credits go to juvinious, Kirr, and Matthew Dalrymple for helping out!
This thread is locked; no one can reply to it. rss feed Print
[OpenLayer] TextRenderer::getHeight()
tobing
Member #5,213
November 2004
avatar

Currently I'm trying to make an OpenLayer-guichan addons that displays TTF fonts in guichan. To do this correctly, I need to find the height of the font, so I'm currently using TextRenderer::GetHeight(text) for this, which unfortunately does only give me the ascending height, not including what is below the baseline (I hope the wording is correct, I'm not a specialist in fonts). So if the TTF has characters which are drawn below the baseline, the given height is too small, resulting in clipped text output and widgets that are not high enough to hold the complete text.

How should this be done correctly?

Kirr
Member #5,060
September 2004
avatar

I don't know how OpenLayer is doing it. But just in case it is relevant, Glyph Keeper's height functions return complete height, which includes both ascender and descender. (Assuming the TTF font has correct data there). The functions are: gk_char_height(), gk_text_height_utf8(), gk_text_height_utf16() and gk_text_height_utf32().

--
"Go to the NW of Stonemarket Plaza to the Blue Glyph Keeper Library door and enter."
- Lunabean's Thief Deadly Shadows Walkthrough, day eight

tobing
Member #5,213
November 2004
avatar

I'm using OpenLayer with its own native handling of TTF, because the last time I tried with GlyphKeeper 0.29a or so, it didn't work properly. Essentially, OL uses the freetype functions directly, but as I said, I'm not an expert with fonts. Maybe I'll try to find out, or peek into GK to see how you do it (you're also using freetype, right?)...

Edit: Got it. I've added a function to ol::Glyph that returns the total height, ascending + descending, that I can later call from my program. Here's the code, in case the developers are interested in adding it:

In Glyph.cpp:

  int Glyph::GetTotalHeight()
  {
    return size.height * (face->height)/(double)(face->units_per_EM) + 1;
  }

to ba called like this in client code:

  return text_rend_.GetFace()->glyphFace->GetTotalHeight();

Well, the prototype of that function has to be added to Glyph.hpp as well.

With that function added to OpenLayer, the following class provides TTF support for guichan, and I would really like to submit it as an addon there:

1//----------------------------------------------------------------------------
2 
3class OpenLayerFont : public gcn::Font
4{
5public:
6 OpenLayerFont( const char* fn, int font_size );
7 // gcn::Font
8 virtual int getWidth(const std::string& text) const;
9 virtual int getHeight() const;
10 virtual void drawString(gcn::Graphics* graphics, const std::string& text, int x, int y);
11 
12 bool isValid() const { return text_rend_.IsValid(); }
13private:
14 ol::TextRenderer text_rend_;
15};
16 
17OpenLayerFont::OpenLayerFont(const char* fn, int font_size )
18: text_rend_(fn,font_size,font_size)
19{
20}
21 
22int OpenLayerFont::getWidth(const std::string& text) const
23{
24 return text_rend_.Width(text);
25}
26 
27int OpenLayerFont::getHeight() const
28{
29 return text_rend_.GetFace()->glyphFace->GetTotalHeight();
30}
31 
32void OpenLayerFont::drawString(gcn::Graphics* graphics, const std::string& text, int x, int y)
33{
34 const gcn::ClipRectangle& rec = graphics->getCurrentClipArea();
35 text_rend_.Print(text, x + rec.x, y + rec.y + text_rend_.Height(text) );
36}

juvinious
Member #5,145
October 2004
avatar

I suppose I can just add that to getHeight as that would be needed regardless for measuring height accurately. Then there would be no need to access the glyphFace directly, because that would be incompatible if somebody was using glyphkeeper instead of the internal renderer.

[edit]
However that can be problematic if the glyph face doesn't have that data, it'll probably just return 1 or 0 in those cases.

__________________________________________
Paintown

Matthew Dalrymple
Member #7,922
October 2006
avatar

OL Manual said:

TextRenderer(

const char *filename,
int width = 9, int height = 12,
Rgba col = Rgba::BLACK, int italics = 0,
bool useHinting = true );

You have to know the height you want when loading a font so can't you just use the variable in the parameter pass for knowing the height? And if you didn't use a variable in the parameter pass then the default would be 12. It's not as convenient as a Height() method but storing that value should work for now, shouldn't it?

Edit: Wait TextRenderer has a Height() method... I'm confused so disregard this post.

=-----===-----===-----=
I like signatures that only the signer would understand. Inside jokes are always the best, because they exclude everyone else.

tobing
Member #5,213
November 2004
avatar

You need both functions, the TextRenderer::Height() which usually returns the height parameter given in the Load function, and the additional function, that returns the total height, which can be slightly larger, depending on the particular font loaded. Of course, if the glyph face doesn't have the data, the total height would be the same as Height(), you can't do better than, or need not, because the font does not descend below the baseline.

juvinious
Member #5,145
October 2004
avatar

Regardless, the problem like I said before is that if a person were to be using OpenLayer with GlyphKeeper then it would be incompatible. The textRenderer class relies on the glyphkeeper api. I made a wrapper for the glyph class so that it's compatible with textrenderer. Thus in the event that the user has openlayer compiled with glyphkeeper, your text_rend_.GetFace()->glyphFace->GetTotalHeight() would be undefined as GLYPH_FACE in glyphkeeper doesn't have a glyphFace.

__________________________________________
Paintown

tobing
Member #5,213
November 2004
avatar

That's of course correct. So if OL uses GlyphKeeper (I guess I'll try that again with the current version of GK) then the required function has to be implemented in a different way. So it would be good to add the new function to the TextRenderer, and implement it accordingly.

Edit: I have not tried to get OL (SVN) to work with GK (0.32), but it simply does not render fonts correctly. Nevertheless I made some changes to get my OpenLayerTTFont class to work (with OL-GK) and compile (with OL+GK).

1// added in TextRenderer.cpp:
2int TextRenderer::FontHeight() const
3{
4 return gk_rend_ascender_pixels( rend );
5}
6 
7int TextRenderer::FontTotalHeight() const
8{
9 return gk_rend_height_pixels( rend );
10}
11 
12 
13// added in Glyph.hpp:
14 OL_LIB_DECLSPEC int gk_rend_height_pixels( GLYPH_REND* const rend );
15 
16 
17// added in Glyph.cpp:
18 int Glyph::GetTotalHeight()
19 {
20 return size.height * (face->height)/(double)(face->units_per_EM) + 1;
21 }
22 
23// and
24 int gk_rend_height_pixels( GLYPH_REND* const rend )
25 {
26 return rend->glyphFace->GetTotalHeight();
27 }

of course, the prototypes of the added functions have to added to the corresponding classes TextRenderer and Glyph.

I would send this, or a patch to the OpenLayer devs mailing list, but my mails are not accepted and waiting for approval by an administrator, so I can't send changes or proposals that way, sorry.

Edit 2: Finished AllegroTTFont class, using GK this time. Really neat, and gave me some insights I'd like to mention here.

First, using GK functions for rendering without using a GLYPH_KEEP object is really slow. So it's a very good idea to use a GLYPH_KEEP object for a font in use.

Second, comparing the speed of drawing text to the display, I found that OL (using its own rendering functions) is comparingly slow, about the speed I had with GK without GLYPH_KEEP. Debugging into the TextRenderer::Print function shows that there's quite a lot of potential for optimization, so that's what I'll do next.

juvinious
Member #5,145
October 2004
avatar

Ok, I'll need to test this with both GK and without, do you have something to test whether they work properly perhaps the guichan font class? In the meantime, I'll go ahead and implement this in my working copy and play around with it.

[edit]
Oops seems you edited your post.. ok well then, if you would like to optimize it by all means, I'll wait up on making any changes. I've gotten mixed results with the speed between GK and with the internal one so if you can improve it then it would be of great help. :)
Also, once you finish up your changes, maybe you can post a patch here and I can apply it to my working copy to test. Thanks. :)
I'll also look into the issue about you posting to the Mailinglist, it seems absurd.

__________________________________________
Paintown

Kirr
Member #5,060
September 2004
avatar

tobing said:

First, using GK functions for rendering without using a GLYPH_KEEP object is really slow. So it's a very good idea to use a GLYPH_KEEP object for a font in use.

Right. Glyph cache is there for reason. With cache the speed of Glyph Keeper's text rendering is comparable to Allegro's built-in fonts (often even faster for me actually). That, with antialiasing, transparency, bold, italics, angles, etc..

Glyph Keeper comes with bunch of benchmark programs, please use them to measure the speed in the rendering mode you are going to use.

One comment. Since 0.32 Glyph Keeper supports creation of Allegro's native FONT objects (with Allegro vtable). Can they be used more easily with AllegroGL / OpenLayer? Theoretically this would not need any direct interaction between OpenLayer and Glyph Keeper. I would appreciate any testing and particularly if someone could write a simple examlpe program.

--
"Go to the NW of Stonemarket Plaza to the Blue Glyph Keeper Library door and enter."
- Lunabean's Thief Deadly Shadows Walkthrough, day eight

tobing
Member #5,213
November 2004
avatar

juvinious: Thanks for your kind words, hopefully you can resolve this mailing list issue. I had written a mail to fladimir directly about this, but didn't receive any answer. Well, I'm wondering if I happened to offend anybody, but I have no idea and certainly didn't mean to. If my suggestions, comments or improvements are not welcome, just let me know and I'll keep quiet about OpenLayer.

For the optimization, I think it's not a very small change, somehow the TextRenderer::Print has to be re-worked completely. The problem is, this function is quite general and can do many things, so specialized functions that can do less faster will probably help very much in increasing speed. Currently, there are many instances of string which are copied, substr'd, converted to char* and back again, so I think much of the performance loss is due to frequent copying and allocating string memory.

I'll try to make a small example to play around with, probably I'll just take the openlayerwidget example of guichan and copy the new font class into that, I'll post this here when I get it complete (weekend has priority...).

Kirr: How do you make this FONT conversion? Is there an example? I guess I might try to create a FONT-wrapper class for use with guichan, so this way would be very nice to test that.

Kirr
Member #5,060
September 2004
avatar

tobing said:

Kirr: How do you make this FONT conversion? Is there an example? I guess I might try to create a FONT-wrapper class for use with guichan, so this way would be very nice to test that.

Function gk_create_allegro_bitmap_font_for_range() does this conversion. It is used in "examples/test_alleg.c" and "benchmark/bench_gk_convert_2_alleg.c" example programs. (In Glyph Keeper 0.32 source distribution).

All testing reports are very much welcome, because I did not test it very much. Mostly I tested it in the benchmark program.

--
"Go to the NW of Stonemarket Plaza to the Blue Glyph Keeper Library door and enter."
- Lunabean's Thief Deadly Shadows Walkthrough, day eight

juvinious
Member #5,145
October 2004
avatar

Well I'll figure out what's going on in regards to the mailinglist, I don't have full admin access to the openlayer project, only svn and shell. I'll need to get in touch with flad about it, however I think the dev mailinglist is only accepting on invite only which could be the problem.
Well about the optimizations I did see that stuff in textrenderer especially the string copying but I haven't really wanted to touch it which is why I made the wrapper for GK. In any event, I tested out 0.32 of glyphkeeper and I don't remember it being as fast as it is now back when it was 0.29. For example the gamedemo is giving me a 450 FPS using GlyphKeeper compared to 60 FPS with the internal renderer, it was quite a shock. Perhaps the glyph class could use some major optimizations. ::)
I've updated the cmake build scripts as well as updated the glyphkeeper source in the utils directory to 0.32. I also created a cmake module for glyphkeeper so it can be built and installed automatically if internal font support is not desired.

__________________________________________
Paintown

tobing
Member #5,213
November 2004
avatar

Kirr: Thanks for the hint, maybe I'll test my AllegroFont guichan wrapper class with that, depends on if I have the time to do that. Anyway, using GK with allegro is really neat.

juvinious: I'll attach my current versions of the Glyph.* and TextRenderer.* files, with the additions I would need to make the OpenLayerTTF guichan wrapper class work. Thanks for all your help so far, and for this in advance.

In general, I must admit that I'm a little tired of trying to get suggestions and improvements to the OL library to the dev team, as I feel that my input is not really welcome. My hobby programming time is quite limited, so I have to decide on what to spend it, and it will be my own game of course. So I'll stop trying to submit my changes, as my mails to the ol-dev mailing list are rejected, and direct mails are ignored. If anyone is interested in my corrected and anhanced version of OL, just contact me directly, I'll happily share my sources. This holds especially for people who are interested in getting guichan + OL to work, I have been through setting up all required stuff (on Windows, using MSVC), and it's not easy.

Thanks again for all help so far!

juvinious
Member #5,145
October 2004
avatar

Well it is due to the fact that the person who set up the mailinglist last year just upped and dissapeared. I don't have access to manage the mailinglist nor most of the rest of the project on berlios else I would correct these issues. As far as it goes with devs for openlayer, it is mostly Fladimir but he only works on it when he has time himself. I've been assisting partly for some time now trying to help get more community support and involvement, which in turn your work and suggestions are greatly appreciated. As soon as I can get a fix on the mailinglists I'll let you know. :)
In the meantime, I'll check out your changes for textrenderer/glyph and get them in. Thanks.

[edit]
Done, I've committed the changes to svn.

__________________________________________
Paintown

tobing
Member #5,213
November 2004
avatar

Thank you very much, juvinious, I'll check it out as soon as I come home later.

Edit: Got everything from the depot, works fine, and thanks again.

Now I started to look deeper into the TextRenderer.Print method, replaced some const std::string& by const char*, then I implemented a new function Glyph::renderFixed(double x, double y, const Rgba& col, Bitmap bmp, int alignment, const char text) consisting of the second half of the original render function and finally ended up in Glyph::drawCharacter. Before going into detail, the summary is: Drawing is faster, but not fast enough.

First observation. Look at the code of renderFixed, which is just the second half of the original render function:

1 void Glyph::renderFixed(double x, double y, const Rgba& col, Bitmap *bmp, int alignment, const char* text)
2 {
3#ifdef USE_NEW_TTF
4 if(faceLoaded)
5 {
6 double rend_x=0;
7 double rend_y=0;
8 
9 switch(alignment)
10 {
11 case 0:
12 rend_x=x;
13 rend_y=y;
14 break;
15 case 1:
16 rend_x = x - getLength(text)/2;
17 rend_y=y;
18 break;
19 case 2:
20 rend_x = x - getLength(text);
21 rend_y=y;
22 break;
23 }
24 
25 // Get the point size and set it to the primary size in order to avoid messed up fonts
26 
27 GLfloat psGrab, psizes[2];
28 glGetFloatv(GL_POINT_SIZE_RANGE, psizes);
29 glGetFloatv(GL_POINT_SIZE, &psGrab);
30 glPointSize(psizes[0]);
31 
32 // Save the old transform state
33 ol::Transforms::PushPlacement();
34 
35 // Also set the projection mode and switch back if necessary
36 ol::Settings::SetOrthographicProjection();
37 
38// int previous = 0;
39// int next = 0;
40 const size_t text_length = strlen(text);
41 for(size_t i = 0; i<text_length;++i)
42 {
43// if(kerning && previous && next)
44// {
45// next = FT_Get_Char_Index( face, text<i> );
46// FT_Vector delta;
47// FT_Get_Kerning( face, previous, next, FT_KERNING_DEFAULT, &delta );
48// rend_x += delta.x >> 6;
49// previous = next;
50// }
51 drawCharacter(text<i>,rend_x, rend_y, bmp, col);
52 }
53 
54 // Set previous pointsize
55 glPointSize(psGrab);
56 
57 // Restore last mode
58 ol::Settings::RestoreOldProjection();
59 
60 // Restore transform
61 ol::Transforms::PopPlacement();
62 
63 }
64#endif
65 }

I have commented the part involving previous and next, because both being always zero, the code is never executed. Maybe this is obsolete stuff that should be removed?

Second observation. Below the code of drawCharacter with comments. Some consts have been added, but that's of course not relevant to performance.

1 // Render a character from the lookup table (utilizing the workBitmap)
2 void Glyph::drawCharacter(signed long unicode, double &x1, double &y1, Bitmap *bitmap, const Rgba & col)
3 {
4#ifdef USE_NEW_TTF
5 std::map<dimension, std::map<signed long, character> >::const_iterator ft = fontTable.find(size);
6 if(ft!=fontTable.end())
7 {
8 std::map<signed long, character>::const_iterator p = (ft->second).find(unicode);
9 if(p!=(ft->second).end())
10 {
11 const character *tempChar = &p->second;
12 
13 unsigned char *line = tempChar->line;
14 for (int y = (int)y1; y < (int)(y1)+tempChar->rows; y++)
15 {
16 unsigned char *buffer = line;
17 for (int x = (int)x1; x < (int)(x1)+tempChar->width; x++)
18 {
19// this inner loop is executed very often (once for each pixel),
20// so it is a good idea to look very carefully what might be slow
21 
22// color conversion is executed for every pixel, so I looked into this in particular
23// this function results in r,g,b,a being identical
24 Rgba checkCol = colorConvert(buffer++,tempChar->grays);
25// so this could be easier
26 if(checkCol.r==0 && checkCol.g==0 && checkCol.b==0 && checkCol.a==0)
27 continue;
28// and this as well
29 const double intensity = ( checkCol.r * 0.30 + checkCol.g * 0.59 + checkCol.b * 0.11);
30// now the color is again changed
31 checkCol.r = col.r * intensity;
32 checkCol.g = col.g * intensity;
33 checkCol.b = col.b * intensity;
34 checkCol.a = col.a * intensity;
35 
36// and one pixel is drawn in that color
37 ol::Point(float(x + tempChar->left),float(y - tempChar->top)).Draw( checkCol );
38 }
39 line += tempChar->pitch;
40 }
41 x1+=tempChar->right;
42 }
43 }
44#endif
45 }

I'm not sure about how to get rid of the color conversion in the innermost loop, I don't understand what this really is supposed to do.

Anyway, for the last observation, look at the code of colorConvert. The origianl version of this function was using the rgba(int,int,int,int) constructor, which in turn converted the arguments to float, so I replaced the code by the equivalent, using the correct constructor directly:

  Rgba colorConvert(const unsigned char *c,short ext)
  {
    const float component = *c / (float)(ext-1);
    return Rgba(component, component, component, component);
  }

All these changes have been made to Glyph.hpp and Glyph.cpp, so should I attach the current files? The most relevant things are already contained in this post.

In summary, while there's definitely some improvement, there will be more performance gain if the glyphs are somehow cached, but I guess that this depends on the current colors? Here, OL works a little different from GK (as far as I understand it), but it may be possible to use caching of already (pixelwise) drawn characters to blit the character when it's drawn the second time.

juvinious
Member #5,145
October 2004
avatar

Quote:

I have commented the part involving previous and next, because both being always zero, the code is never executed. Maybe this is obsolete stuff that should be removed?

Oops you know what that's a typo, they should be set to 1. If the face has kerning capabilities you want to add those details when drawing the characters so that their spacing is proportional and nice.
The changes to colorConvert makes is easier, so thats good. The added consts are justified so thats good to.
About your confusion on the color conversion itself. Glyphs are stored in grayscale in freetype, so in order to apply a color to the glyph I need to extract the intensity which would be (red * 0.30 + green * 0.59 + blue * 0.11) of the grayscale color then multiply the user given color by that intensity which properly converts it to the desired color keeping the proper luminance.
I already cache the glyph data from the freetype face itself but if I were able to cache the glyphs I wouldn't be doing pixel per pixel drawing, which is the major cause of slowdown. Their exists two issues or difficulties from what I can see that prevents me from caching. 1: Caching would needed to be done per color, size, and italics into a bitmap. 2: In order for this caching to work, you cannot be using framebuffer emulation (not every card supports this).
I'm out of ideas on how to do it, I spent way too much time trial and error to only conclude with this realization.
If you have any ideas on how we can overcome this obstacles maybe we can improve it. :)

[edit]
nevermind... I read it wrong, why use a const char * :o ... use c++ strings. :P

__________________________________________
Paintown

tobing
Member #5,213
November 2004
avatar

That's what I was thinking after reading and stepping through that code. Not easy to apply any caching, so maybe we just leave it so for the moment.

Quote:

I read it wrong, why use a const char * ... use c++ strings.

I use const char* to avoid copying std::string objects, or implicitely creating such, as it happenend on the way from TextRenderer.Print to Glyph.render for example. Finally, only a const char* is needed, and most of the time, the string is not changed in any way, so passing around const char* is the fastest you can do. Having a mix of const char* and const std::string& makes the compiler generate implicit conversions, and that means creation of temporary objects.

Ah, the formula

Quote:

(red * 0.30 + green * 0.59 + blue * 0.11)

simplifies a little, because in that situation, r,g,b are all equal. So this would become red * (0.30+0.59+0.11) which is red * 1.0. 8-)

Go to: