nuklear icon indicating copy to clipboard operation
nuklear copied to clipboard

Support for subpixel correct drawing ?

Open LennertSchneider opened this issue 7 years ago • 7 comments

Hi,

I'm trying to render a waveform using nk_stroke_line, but it looks quite horrible, because all the float precision in the supplied coords is lost due to internal conversion to short. I have the same problem with some other custom controls where I want to use lines and circles to construct them. Is there a reason this has been done ? Is this really necessary to abandon the float precision internally ?

Cheers.

LennertSchneider avatar May 03 '17 10:05 LennertSchneider

Before I start converting draw commands internal position type from short to float could you test something for me. nk_convert_config has parameters:

struct nk_convert_config {
    /* ... */
    enum nk_anti_aliasing line_AA;
    unsigned circle_segment_count;
    unsigned arc_segment_count; 
    unsigned curve_segment_count;
    /* ... */
};

First could try turning line_AA anti-aliasing on and off. If that does not help please try to set higher values for each of the segment counts. These basically dictate out of how many triangles shapes/lines generated out of. Also for any curved shapes or lines I would highly recommend overriding NK_SIN and NK_COS to standard library functions sin and cos.

If your problem still persist then I will change internal command types from short to float. As for the reason why they are short. This library started as an UI for X11 which only uses short. So at that time it made sense and took less memory. Especially since I at that time did not know how much memory would, could and should be consumed.

vurtun avatar May 03 '17 13:05 vurtun

I think I might not have been clear with my statement. The problem is not in using the predefined circle/arc/curve-primitives, but with doing my own out of multiple lines. I first stumbled across this problem when trying to draw an arc without the begin/and strokes from the center (really JUST the arc). Since there was no primitive for this I tried to draw a bunch of lines whose coords I calc myself (using the precise stdlib sin/cos). However the resulting arc looks horrorbly deformed due to each start and endpoint of each line segment snapping to fill pixels, because of the internal short conversion.

LennertSchneider avatar May 03 '17 13:05 LennertSchneider

Sorry to bring this issue up again...I'm also facing a similar situation where I would like to have some custom widget displaying a function plot.

Below is a screenshot of my attempt drawing sin(x). image Like @LennertSchneider did, I sampled the function with different x, then plot the curve with nk_stroke_polyline.

Internally, Nuklear does float-to-short conversion: https://github.com/vurtun/nuklear/blob/8f5c1bef9c5fd844b584c00953c64ae238a06f2d/src/nuklear_draw.c#L390-L391

The conversion results in heavily aliased curve, as shown in my screenshot.

As a proposal, there could be a global macro like NK_VERTEX_VALUE_TYPE, which defaults to short, but allow user to override with float to support subpixel drawing. Current draw routines will keep the casting to make sure they don't broke any pixel-perfect drawing for UI elements. Meanwhile, they will have subpixel counterparts, nk_stroke_polyline_subpixel for example, for any user who want advanced control on their drawing.

itsuhane avatar Jun 14 '19 07:06 itsuhane

@LennertSchneider @itsuhane could you maybe first try disabling the conversion from float to short in nuklear.h and post your results here (i.e. pictures and the backends you've used)? I'm asking as I don't expect it to be much better as at the end of the day the same color will be used for all affected pixels, so I'm afraid you might anyway need some more advanced form of anti aliasing.

dumblob avatar Jun 14 '19 09:06 dumblob

Sure, I've tried some PoC. Please see the screenshot below:

image

Unfortunately, most of the commands are using short type internally, simply removing the conversion will only introduce compile warnings (narrowing from float to short).

To make the float version in the screenshot work, I had to make following changes:

In the definition of struct nk_command_polyline, use struct nk_vec2 for points[1]. https://github.com/vurtun/nuklear/blob/181cfd86c47ae83eceabaf4e640587b844e613b6/src/nuklear.h#L4234-L4240

Then I had a subpixel version for nk_stroke_polyline:

NK_API void
nk_stroke_polyline_subpixel(struct nk_command_buffer *b, float *points, int point_count,
    float line_thickness, struct nk_color col)
{
    int i;
    nk_size size = 0;
    struct nk_command_polyline *cmd;

    NK_ASSERT(b);
    if (!b || col.a == 0 || line_thickness <= 0) return;
    size = sizeof(*cmd) + sizeof(float) * 2 * (nk_size)point_count; // has to be sizeof(float)
    cmd = (struct nk_command_polyline*) nk_command_buffer_push(b, NK_COMMAND_POLYLINE, size);
    if (!cmd) return;
    cmd->color = col;
    cmd->point_count = (unsigned short)point_count;
    cmd->line_thickness = line_thickness;
    for (i = 0; i < point_count; ++i) {
        cmd->points[i].x = points[i*2]; // no type-casting
        cmd->points[i].y = points[i*2+1];
    }
}

itsuhane avatar Jun 14 '19 12:06 itsuhane

My changes are on top of version 509c75b. The backend is OpenGL 3 (GLFW+glbinding).

itsuhane avatar Jun 14 '19 12:06 itsuhane

@itsuhane thank you for the reaction. Now I'm thinking again about your proposal:

As a proposal, there could be a global macro like NK_VERTEX_VALUE_TYPE, which defaults to short, but allow user to override with float to support subpixel drawing. Current draw routines will keep the casting to make sure they don't broke any pixel-perfect drawing for UI elements. Meanwhile, they will have subpixel counterparts, nk_stroke_polyline_subpixel for example, for any user who want advanced control on their drawing.

As that actually seems to be the way to go. Make a pull request and I'll merge it :wink:.

dumblob avatar Jun 14 '19 17:06 dumblob