Draw vertical line with thickness?

What’s the best way to draw a vertical line with a thickness greater than 1?

I currently use drawLine and pass 2 as the thickness, but I read that it’s a lot faster to use drawVerticalLine or fillRectangle. However, drawVerticalLine does not have a thickness parameter I can use, so should I use fillRectangle? If so, is simply setting a width of 2 for fillRectangle the same as drawing a line with a thickness of 2, or do I also need to offset the x position to center the rectangle vertically about the desired x position? Or…?

Same question for horizontal lines, I guess.

drawVerticalLine and drawHorizontalLine just call fillRect internally.

void Graphics::drawVerticalLine (const int x, float top, float bottom) const
{
    if (top < bottom)
        context.fillRect (Rectangle<float> ((float) x, top, 1.0f, bottom - top));
}

void Graphics::drawHorizontalLine (const int y, float left, float right) const
{
    if (left < right)
        context.fillRect (Rectangle<float> (left, (float) y, right - left, 1.0f));
}

So just call Graphics::fillRect with floating-point values.

Matt

1 Like
float top = 420.f, bottom = 420.f;
int x = 420;
int thicc = 3;

g.drawVerticalLine(x, top, bottom);
for(auto i = 1; i < thicc; ++i){
    g.drawVerticalLine(x + i, top, bottom);
    g.drawVerticalLine(x - i, top, bottom);
}

you could also do this if you don’t want to use rectangles btw

While this may work, it only does if your physical pixels align with the “int” coordinate you give it.

The moment you add a Transform somewhere, those lines will be rendered with antialiasing, and you’ll get artifacts where the antialiasing of two adjacent lines overlaps.

I guess this would even more visible if the drawing colour is not fully opaque.

And also, the loop above is performed by the CPU, while obtaining the same effect by filling a rectangle is an operation that can be entirely handled in a GPU with a performance gain.

1 Like

I can’t comment on whether or not this is true but I’d recommend to not get caught in the trap of premature optimisations.

It may take 1ms to render a line with drawLine and only 0.95ms to render the same line with some other method, but the difference is so small the only noticeable difference you’ve made is to make your code less readable.

const juce::Point<float> p1 {100.0f, 100.0f};
const juce::Point<float> p2 {100.0f, 300.0f};
const auto thickness = 2.0f;

// Very readable
g.drawLine ({p1, p2}, thickness);

// Very ugly
g.fillRect (p1.x - thickness / 2.0f, p1.y, thickness, p2.y - p1.y);

Probably not very helpful, but that’s my two cents.

From the comments in juce_GraphicsContext.h:

TIP: If you’re trying to draw horizontal or vertical lines, don’t use this -
it’s better to use fillRect() instead unless you really need an angled line.

Since I’m potentially drawing a lot of vertical (and horizontal) lines while scrolling through my graph, I thought it best to take this advice.

Looks like using fillRect, with floats instead of ints, might be the best solution, when I need a specific width.

1 Like

Interesting, I hadn’t seen that comment before.

Personally, I would still do a benchmark test first to ensure that the optimisation is actually necessary.

In that case, I suggest not calling fillRect directly and instead populating a RectangleList, where it is drawn instead. There’s a specialisation of Graphics::fillRectList for float rectangles.

Under the hood in that call, like in CoreGraphics, native drawing optimisations will (usually) be applied­.

Alternatively, if you’re using OpenGL, you can store the data as a bunch of triangles and draw them (it’s way faster to draw this way, even though it’s more maintenance on your end).

3 Likes

fillRectList was one of the first pieces of knowledge you passed to me when we worked together :slight_smile:

2 Likes