nothings / stb

stb single-file public domain libraries for C/C++
https://twitter.com/nothings
Other
26.35k stars 7.68k forks source link

SDF generation does not seem to work correctly for Noto fonts #659

Open Bulmanator opened 5 years ago

Bulmanator commented 5 years ago

Generating SDF values using the Noto Font Family (specifically the NotoSansCJK.ttc font file) produces incorrect results. I have also tried Source Han Sans and this also produces incorrect result. I think Noto uses the CJK glyphs from Source Han Sans so this problem must stem from that instead of Noto itself. Incorrect values shown below (all images produced by writing out the data with stbi_write_png):

However, when using the built in Windows font Yu Gothic, the results are perfectly fine:

It can also produced packed atlases with stbtt_PackFontRanges perfectly fine with Noto.

The code used to generate the SDF glyph:

s32 sdf_width, sdf_height, sdf_xoff, sdf_yoff;
s32 glyph = stbtt_FindGlyphIndex(&font_info, 'C');
u8 *sdf_bitmap = (u8 *) stbtt_GetGlyphSDF(&font_info, stbtt_ScaleForPixelHeight(&font_info, 128),
       glyph, 5, 180, 36, &sdf_width, &sdf_height, &sdf_xoff, &sdf_yoff);
wheybags commented 5 years ago

Can confirm this is still happening on latest master.

posila commented 5 years ago

The issue is that stbtt_GetGlyphSDF doesn't handle STBTT_vcubic vertex type. It turns out getting the closest point on cubic bezier curve is not trivial problem, but there are some solvers for this: https://github.com/nrtaylor/CubicSplineClosestPoint, https://github.com/erich666/GraphicsGems/blob/master/gems/NearestPoint.c But it might be easier and/or faster to convert the curve into linear segments and use those to generate SDF.

nothings commented 5 years ago

Yeah, makes sense, I think the vcubic support was probably added after the SDF support. Note we solve quadratics directly (requires solving a cubic), so the non-trivial solution to the cubic curve (with a fifth-degree polynomial solver) isn't impossible, but yeah, it probably makes more sense to tesselate to quadratics or line segments.

rygorous commented 3 years ago

Distance to a cubic requires solving a fifth-order polynomial though. This would require some decidedly non-trivial changes.

zewt commented 2 years ago

Spent some time trying to figure out what I was doing wrong before finding this, would be nice to have a note in the stbtt_GetGlyphSDF docs for this limitation.

bytepatch commented 2 years ago

Has anyone worked out a solution for this?

Jannes1000Orks commented 7 months ago

Hey! I did a very quick and !!!VERY!!! dirty fix this morning. I do not recommed using this the way it is. But for future reference and if anyone wants to pick up where I left off: ` int num_verts = stbtt_GetGlyphShape(info, glyph, &verts);

    //CUSTOM FIXING CODE STARTS HERE
    int num_extraverts = 0;
    constexpr int cubicTesselationUpperBound = 4096;
    for (i = 0, j = num_verts - 1; i < num_verts; j = i++)
    {
        if (verts[i].type == STBTT_vcubic)
        {
            num_extraverts += cubicTesselationUpperBound;
        }
    }
    if (num_extraverts > 0)
    {
        stbtt_vertex* verts2 = (stbtt_vertex*)STBTT_malloc((num_verts+num_extraverts)*sizeof(stbtt_vertex), info->userdata);
        int vc = 0;
        for (i = 0, j = num_verts - 1; i < num_verts; j = i++)
        {
            if (verts[i].type == STBTT_vcubic)
            {
                int num_points = 0;
                stbtt__point points[cubicTesselationUpperBound];
                float Flatness = 0.35f / scale;
                float objspace_flatness_squared = Flatness * Flatness;
                stbtt__tesselate_cubic(points, &num_points, verts[j].x, verts[j].y,
                    verts[i].cx, verts[i].cy,
                    verts[i].cx1, verts[i].cy1,
                    verts[i].x, verts[i].y,
                    objspace_flatness_squared, 0);

                for (int k = 0; k < num_points; ++k)
                {
                    verts2[vc].x = (short)points[k].x;
                    verts2[vc].y = (short)points[k].y;
                    verts2[vc].type = STBTT_vline;
                    vc++;
                }
            }
            else
            {
                verts2[vc] = verts[i];
                vc++;
            }
        }
        STBTT_free(verts, info->userdata);
        verts = verts2;
        num_verts = vc;
    }

    //CUSTOM FIXING CODE ENDS HERE

    data = (unsigned char*)STBTT_malloc(w * h, info->userdata);

`

I essentially used the tesselation code to convert the cubic curve to a bunch of line segments. The main issue is that it is hard to predict how many new points are going to be inserted. I just used a buffer of 4k points. But no idea what is actually needed in which cases. In theory the tesselation function can add 2^16 points per cubic vertex. Sooo....

But it renders the glyphs somewhat nice, while retaining the properties of the stbtt_GetGlyphSDF function.