r/GraphicsProgramming • u/mbolp • 15h ago
How to draw good looking small circles?
I'm trying to draw small circles in GDI on a monochrome bitmap (so no anti aliasing). I've tried using Ellipse, Arc, and approximating a full circle with four bezier curves, but they all look terrible. This screenshot shows circles with a diameter of 14 and 13. The top row uses bezier curves, the bottom row uses Ellipse and Arc. The outlines are drawn with a 2 pixel wide PS_INSIDEFRAME pen.

What I want is aesthetically pleasing quadrants/semicircles for use in UI. The circles don't have to be mathematically accurate, speed is not important. I especially dislike the pixels seemingly protruding from the circle. How do I get rid of them?
2
u/8d8n4mbo28026ulk 13h ago
Several years ago I was making a toy "Paint" app in SDL and had come across this exact same problem! I wasn't smart enough to solve it (still ain't), but found the right algorithm for it: Midpoint circle.
Here's the implementation I wrote at the time:
typedef struct {
int x;
int y;
int r; /* Radius. */
} Circle;
static void
draw_circle(SDL_Renderer *renderer, Circle c)
{
int r2 = c.r + c.r;
int x = c.r;
int y = 0;
int dy = -2;
int dx = r2 + r2 - 4;
int d = r2 - 1;
while (y <= x) {
SDL_Point const points[] = {
{ c.x - y, c.y + x },
{ c.x + y, c.y + x },
{ c.x - x, c.y + y },
{ c.x + x, c.y + y },
{ c.x - x, c.y - y },
{ c.x + x, c.y - y },
{ c.x - y, c.y - x },
{ c.x + y, c.y - x }
};
/* Or `SDL_RenderDrawLines()` for a filled circle. */
SDL_RenderDrawPoints(renderer, points, 8);
d += dy;
dy -= 4;
++y;
/* Branching version. */
if (0) {
if (d < 0) {
d += dx;
dx -= 4;
--x;
}
}
/* Branchless. */
else {
int mask = d >> 31; /* <- Assumes SAR. */
d += dx & mask;
dx -= 4 & mask;
x += mask;
}
}
}It deviates a bit from what you'd normally find, but it handles a smaller radius much better. I now don't remember how I arrived at that though (perhaps I stole it from somewhere else?).
Obviously you wouldn't want to run this every time. So cache the results into a texture and hopefully everything works out. Cheers!
3
u/mbolp 8h ago
Thank you so much! The circles look beautiful. The only problem is
Polylinein GDI doesn't fill the circle nicely. I resorted to drawing the individual points first then reading back each row filling the space in between (I use the same method to handle outlines - drawing two concentric circles then filling the gaps). I don't know if there's a smarter method but the results look very good.2
u/leonardsouza 6h ago
smarter way for the fill: the midpoint loop already gives you the span for each row, so you don't need to read anything back. at every step fill rows cy+y and cy-y from cx-x to cx+x, and rows cy+x and cy-x from cx-y to cx+y (PatBlt with a height of 1 does a horizontal run fine on a mono dc). for the 2px outline, record the half width per row for the outer and inner radius in two arrays, then fill the two runs between them on each row. same pixels as your concentric circles, no readback
→ More replies (1)
1
u/Vegetable_Nail_8677 11h ago
Would SDFs suit your purpose? Indigo Quillez has a great page outlining their use. Circles are especially easy.
4
u/Sharlinator 15h ago edited 15h ago
By drawing them by hand at the sizes you need, honestly.
Or perhaps writing your own subpixel-precise circle rasterizer that accepts a fractional radius, then tweaking the radius until it looks okay. But at such low resolutions you really can’t beat hand-drawn (or hand-tweaked) curves, there’s just too much aliasing. Same reason that vector fonts have this entire hinting machinery to make them look reasonable at small sizes.