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

fix(canvas): compute circle coordinates with Pyth thm #917

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

SOF3
Copy link
Contributor

@SOF3 SOF3 commented Feb 4, 2024

The original algorithm may cause holes when the circle is large

discussed in https://github.com/SOF3/plotters-ratatui-backend/pull/2/files#r1461295810

The test case changes by one pixel, but the output was very approximate anyway

The original algorithm may cause holes when the circle is large
Copy link

codecov bot commented Feb 4, 2024

Codecov Report

Attention: 5 lines in your changes are missing coverage. Please review.

Comparison is base (61a8278) 92.0% compared to head (26f05ec) 92.0%.

Files Patch % Lines
src/widgets/canvas.rs 94.3% 5 Missing ⚠️
Additional details and impacted files
@@          Coverage Diff          @@
##            main    #917   +/-   ##
=====================================
  Coverage   92.0%   92.0%           
=====================================
  Files         61      61           
  Lines      15493   15591   +98     
=====================================
+ Hits       14259   14352   +93     
- Misses      1234    1239    +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@joshka joshka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally for a circle, every point is either one across or one up (or both) from the previous point. The circles we end up drawing here tend to be too boxy as they're filling multiple points per grid point.

It needs to work entirely in terms of grid points, which I think this change does, but it needs to go one step further:

The algorithm you're looking for for this is this one - https://en.wikipedia.org/wiki/Midpoint_circle_algorithm (you might have heard people refer to Bresenhams algorithm).

Instead of iterating over all the points in the area, it's most performant (and leads to good looking results) to iterate over the points that are in the circle. Start at the top point, iterate while moving either to the right or downwards or both only until you hit 45º and then mirror all those points on the x/y axis.

I suspect this should be an concise but understandable ~25 LoC (which is a large difference from 159 LoC here).

@SOF3
Copy link
Contributor Author

SOF3 commented Feb 4, 2024

The algorithm I am using here is, for each horizontal line (with the same y), identify the pixels that the circle intersects with, and color those points only. I have not looked into how different it is from the midpoint circle algorithm in terms of performance and accuracy, but the core implementation is just the 40 lines in circle.rs

The other 100 lines of change are related to the translation between world and screen coordinates, which is in the canvas.rs Painter impls. I believe we would face a similar problem with midpoint circle algorithm since it needs to work with the screen coordinates rather than the world coordinates, which is not very well-supported in the original Painter API.

@SOF3
Copy link
Contributor Author

SOF3 commented Feb 4, 2024

The algorithm I implemented computes the range of coordinates to be plotted on each line in constant time (except the use of sqrt), which should not cause any performance issues. As for the part about mirroring the rasterization for 8 times, I believe (I haven't read about the implementation details of your proposed algorithm yet) that it is inappropriate for the case here since the center coordinates are not necessarily perfectly aligned with the screen coordinates, i.e. the result drawn by the ideal algorithm is not necessarily symmetric on any plane, and thus should not be mirrored directly.

@SOF3
Copy link
Contributor Author

SOF3 commented Feb 4, 2024

Another possible motivation for computing the range of X pixels on each horizontal line is that the implementation in #920 would be simpler — non-filled circle draws -dx_end..-dx_start, dx_start..dx_end, while filled circle draws -dx_end..dx_end directly, such that the same algorithm can be largely reused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants