rougier / freetype-gl

OpenGL text using one vertex buffer, one texture and FreeType
Other
1.65k stars 266 forks source link

Fix scaling distortion #159

Open filippos-lagopoulos opened 7 years ago

filippos-lagopoulos commented 7 years ago
rougier commented 7 years ago

What is the distortion you're referring to? Would you have some example before/after your fix (because it is not totally clear to me)?

filippos-lagopoulos commented 7 years ago

distorted_scaled clean_scaled I've attached two images of some text rendered using the code and shaders from the sub-pixel example, in the first case without the changes in this branch and in the second with them. The program is using a camera which is positioned close to the text so its actually stretched/zoomed.

It seems odd to me that padding is taken into account when calculating the glyph's texture coordinates. I'm not sure if this is intentional to get smoother edges but it does result in a one pixel larger glyph both in width and height, since the calculation is:

glyph->width    = tgt_w;
glyph->height   = tgt_h;

Where _tgtw and _tgth are:

size_t tgt_w = src_w + padding.left + padding.right;
size_t tgt_h = src_h + padding.top + padding.bottom;

And on master padding is:

struct {
    int left;
    int top;
    int right;
    int bottom;
} padding = { 0, 0, 1, 1 };

Which means the total size say for 20x30px glyph will be

size_t tgt_w = 20 + padding.left + padding.right = 20 + 0 + 1 = 21
size_t tgt_h = 30 + padding.top + padding.bottom = 30 + 0 + 1 = 31

Which means that if we use _tgtw and _tgth we get:

glyph->width    = 21;
glyph->height   = 31;

Shouldn't the glyph size just be 20x30 no matter what the padding is?

Plus the left/top padding values are not taken into account which means that if we use a left/top padding the actual glyph will be positioned at (padding.left, padding.top) texels/pixels in front of the texture coordinates. That's what this bit fixes:

x += padding.left;
y += padding.top;

It might be that I totally misunderstand but it does seem odd. And I get these weird lines when scaling/zooming. The picture I've attached seems to have these lines right at the baseline but this is not always the case. I get them also around the edges. They all seem to go away if I use the actual glyph size (i.e. _src_w,srch) and use at least 1px for left/top padding and offset the uv's by it.

I think also in the case of a distance field (which is actually using a left/top padding of 1 on master) we still want to offset the positions by the left/top padding. Haven't tested this, but I was trying to use it in our application and was getting these weird lines now that I think about it. I might try it again probably in a few days.

rougier commented 7 years ago

Thanks, I see the problem now but there is still problems with the fix you made I think. It's like some glyphs are cut at the bottom (maybe one pixel cut). I don't have much time right now to investigate but I will as soon as I can (unless you fix the remianing bug).

filippos-lagopoulos commented 7 years ago

You're right I noticed that but thought the size must be right when using _srcw and _srch. But the glyphs on master do have 1 additional pixel at the edge which makes them look smoother. I'll keep investigating.

cmrschwarz commented 7 years ago

Very interesting work! I have had similar issues even when unscaled and would appreciate it very much to see this fixed. Unfortunately I also don't have the time to look into it right now.

filippos-lagopoulos commented 7 years ago

Here is a hack to fix this. Although I would suggest to keep this PR open until we find a proper fix and the reason why this is happening. I will use this hack to make the text in our application look better but will not commit to this branch. Its dead easy though. So instead of using the original glyph size (i.e. _srcw and _srch) when calculating the texture coordinates we can use _srcw + 1 and _srch + 1. So the code would look like this:

    float uvx = x + padding.left;
    float uvy = y + padding.top;
    float uvz = src_w+1;
    float uvw = src_h+1;

    glyph = texture_glyph_new( );
    glyph->codepoint = utf8_to_utf32( codepoint );
    glyph->width    = uvz;
    glyph->height   = uvw;
    glyph->rendermode = self->rendermode;
    glyph->outline_thickness = self->outline_thickness;
    glyph->offset_x = ft_glyph_left;
    glyph->offset_y = ft_glyph_top;
    glyph->s0       = uvx/(float)self->atlas->width;
    glyph->t0       = uvy/(float)self->atlas->height;
    glyph->s1       = (uvx + uvz)/(float)self->atlas->width;
    glyph->t1       = (uvy + uvw)/(float)self->atlas->height;

You can obviously skip this bit now (since uvx and uvy already contain the padding):

x += padding.left;
y += padding.top;

This will remove the scaling distortion and it will add the missing pixel that exists on master and not on this branch. You also get cleaner rotated text using this.