Draw a 1-pixel line


#1

I tried to draw a simpe black vertical line of one pixel thickness.
Choosing an integer-x value resulted in a 2-pix grayed line.
From Antigrain I know the trick to add 0.5f to x. It did not work.

So I tried to find the required offset by experimenting: Draw 50 lines and Increase the offset in steps of 0.02:

float x = 100.f;
for (int i=0; i<50; ++i) {
	x += 5.02f;
	g.drawLine(x, y1, x, y2, 1.f);
}

No success either: each line covers a rectangle of width 2 or even 3 pixel of various gray tones.

How can I draw a vertical or horizontal line of 1 pixel thickness?


#2

Graphics::drawHorizontalLine or Graphics::drawVerticalLine ?


#3

Thanks, but this brings no improvement. For instance

g.drawVerticalLine(100, 200.f, 250.f);		// which linethickness??
g.drawVerticalLine(110, 200.f, 250.f);		// which linethickness??
g.drawVerticalLine(120, 200.f, 250.f);		// which linethickness??

gives me:


#4

Here, x = value.5f in g.drawLine(x, y1, x, y2) results in a perfectly fine vertical one pixel line. I’m using the 4 argument drawLine() though. And I’m on a non-retina display.

Btw: An offset of 0.5f makes perfect sense. Integer values are exactly at the border between two pixels.


#5

Juce GUI is vector based, not pixel-based.
Are you on Windows, standalone app, with an uneven global scale factor? Than this is the normal behavior. You may disable the dpi-awareness (there is a macro for that), but than maybe windows does the scaling, result is may even more blurry (fonts).
Mac retina works different, scaling is done by CoreGraphics.
(There should be a explanation added to the Juce Docs)


#6

At last I found the reason: In ComponentPeer::handlePaint an implicit transformation is applied.
It results in a scaling factor of 1.25, which means, that my 1-pix line is rendered to 1.25 pixel and by such has no chance at all to cover just one pixel.

   g.addTransform (AffineTransform::scale (peerBounds.getWidth()  / (float) component.getWidth(),
                                            peerBounds.getHeight() / (float) component.getHeight()));

By commenting out the above the effect is gone.

Thanks for all suggestions.


#7

Look in your windows monitor settings, there is a somewhere a 125% setting.

You could simple set the GlobalScaleFactor to 1.0, than again if you have a very high resolution screen, your GUI has very small controls.


#8

@Christof_Schardt That is equivalent to setting the global scale factor to 1.0 .

Given a scale factor of 1.25 you can work out that one physical pixel equals 0.8 logical pixels. But JUCE doesn’t tell you where the physical pixel grid is, so you have no way of aligning that line properly. The consequence is that JUCE apps look blurry at scale factor 1.25 . There’s not much you can do about that.

You can improve the appearance somewhat by:

  • When drawing a shape with both a stroke and a fill, first draw the fill, it should extend to halfway the stroke. Then draw the stroke.
  • Alternatively, with opaque fills, you can draw the stroke first, but draw it bigger on the inside of your shape. Then fill the area inside the stroke.
  • Always use floating-point coordinates. When using ints, the scaling is applied to integer coordinates and you’ll get ugly artefacts around your borders. Look for instance at the text boxes in the ProJucer.

#9

All modern apps on all modern desktop and mobile OSes may “look blurry” because scaling factors are a fact of life nowadays. And that’s a good thing!

But there’s a lot that people can do about it. I.e. design GUIs that use generous, well-proportioned and reactive layouts. It’s crazy to sweat over drawing a line exactly one pixel wide, when that line will be almost invisible on a phone screen or high-DPI display. If your UI is locked to a single, physical scale factor, it’s going to be laughably small and unusable for a huge proportion of your users, even if it seems OK on your own low-res monitor.


#10

@jules That first sentence is just plain wrong. You’re conflating two things:

  • GUIs that use generous, well-proportioned and reactive layouts. [and get bigger at higher DPI]
  • Making sure your UI elements are aligned on pixels.

These are two separate issues, and you can do both.

Here’s a screencast of Paint (at 120 DPI)

The UI is scaled up, but note how (1) all lines are still 1px wide and aligned, and (2) the image and the icons are not scaled (they would look a lot worse otherwise).

Is it a problem? I don’t think so, if you pay some attention—see my points above—your app will still look reasonably good. I think JUCE strikes a good balance between looking reasonably good, and being easy on programmers.

125% is the most annoying to support, you get these issues and the resolution is still low enough to notice the blurriness. Apple uses a different approach, the scale factor is either 100% or 200%, which makes all those annoying alignment issues go away.


#11

Fair enough, 125% is an annoying ratio, especially on low-res monitors. In software I’ve worked on, we generally avoid this by designing graphics that doesn’t rely on things like the lines in your example image above in order to work.

But really, the issue isn’t about drawing 1-pixel lines. In a case like the paint example above, what you really want is not something that draws a specific number of physical pixels, you want a method that draws a rectangle that’s snapped to the nearest pixel boundary, but without defining the rectangle’s position in actual physical pixels.