Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot plot a line of width=1 without antialiased #6134

Closed
sylvain-bougnoux opened this issue Mar 8, 2016 · 7 comments
Closed

Cannot plot a line of width=1 without antialiased #6134

sylvain-bougnoux opened this issue Mar 8, 2016 · 7 comments
Labels
backend: agg status: needs clarification Issues that need more information to resolve.
Milestone

Comments

@sylvain-bougnoux
Copy link

A line has at least a width of size 2, I could not get down to 1.
Using v1.5.1 both with wx and qt backend.

import matplotlib.pyplot as plt
plt.plot([0,1,1,2],lw=1,antialiased=None)

When looking at the pixels, the horizontal segment is of width 2. Same with lw=1.0 & 0.1.
Using lw=5 makes a line of width 6.
Using lw=6 makes a line of width 8.

Regards

@tacaswell tacaswell added this to the 2.1 (next point release) milestone Mar 8, 2016
@tacaswell tacaswell added backend: agg status: needs clarification Issues that need more information to resolve. labels Mar 8, 2016
@tacaswell
Copy link
Member

attn @mdboom

@tacaswell
Copy link
Member

Can you post an image of what you are talking about? Is this DPI or line position dependent?

@sylvain-bougnoux
Copy link
Author

I am talking about pixel width (I don't know what is "line position dependent"). Using a backend such as wx or qt, when plotting the width of the line2D is independent of the zoom factor, but when you capture the screen (or save the picture), the width of the lines in pixel is not the one specified by linewidth (lw). It can be seen on the image below where I superimposed 3 zooms on the horizontal part of the plot with respectively lw=1,5,6. The grid represents the pixels.

lw

Therefore I cannot have thin lines & import figures by capturing the screen window. Maybe I can do this by saving the image specifying a larger resolution but it is a pity? When saving the image using the "save icon", ie without specifying the DPI, the default png has the same issue with sometimes slightly different widths.

OK my confusion is that lw is in points not in pixels. When using lw=0.01, the width in pixel on the screen is still 2, but when saving the width is 1.

I did some more investigation to find the right lw to get w=1pixel on the screen:
lw=0.01 makes w=2pixel on the screen for the horizontal segment.
lw=0.001 plots nothing
lw=0.004 makes w=2pixel
lw=0.003 makes the horizontal segment disappear but not the slope=1 segments.
No way to get w=1pixel for the horizontal line segment on the screen.

Maybe it is due to the fact that during your transformation from plot to pixel, the logical y=1 falls always exactly between 2 pixels and not in the center of a pixel?

Thanks.

@mdboom
Copy link
Member

mdboom commented Mar 9, 2016

Our non-antialised rendering doesn't get much testing or use.

We do have a code path to ensure that linewidth is always at least 0.5 when antialiasing is off.

https://github.com/matplotlib/matplotlib/blob/master/src/_backend_agg.h#L411

However, I don't think that's the issue, since disabling it does not fix the issue.

In fact, you are right that the line is falling between two pixels and drawing in both. This is, unfortunately, just how non-antialiased rendering works. It's either in a pixel (or in this case 2) or not, and there's no gradation in between.

As a workaround, you can turn on snapping, which will snap to pixel centers when the linewidth (in pixels) is odd, or snap to pixel boundaries when it is even. Then with a linewidth of 0.5 points, you can get a single pixel line.

plt.plot([0,1,1,2], lw=0.5, antialiased=None, snap=True)

I'm going to close this as "not a bug", but feel free to continue to comment if you have further questions.

@mdboom mdboom closed this as completed Mar 9, 2016
@sylvain-bougnoux
Copy link
Author

Thanks for this nice answer.
However your workaround partially fixes it. Indeed the horizontal line is now 1pixel width (thanks) but the “slope=1 or diagonal” segments are still large with an “orthogonal width pattern” as 1,2,1,2,.. making a “ugly/unbalanced” drawing. It is like you keep to 4-pixel-connection instead of 8-connection. What I would like to have is a simple diagonal  with only a list of “north-east/south-west” connections; is there an option for 8-connectivity?

PS: strangely when using lw<0.5, the horizontal segment may get back to 2 pixel width (e.g. lw=0.1). It is not easy to tune :-). What does “odd” mean for floats?

PS: Actually I don’t care for antialiased; I just want a thin line with non-fading colors. The best I could get is with lw in [0.5;1] plt.plot([0,1,1,2], lw=0.5, snap=True)
The snap option does make sense.

@ptr-br
Copy link

ptr-br commented Feb 4, 2023

Is there any update in the issue?

@jklymak
Copy link
Member

jklymak commented Feb 4, 2023

@ptr-br if you want to discuss linewidths etc, perhaps open a question at https://discourse.matplotlib.org

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend: agg status: needs clarification Issues that need more information to resolve.
Projects
None yet
Development

No branches or pull requests

5 participants