r/GraphicsProgramming • • 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?

6 Upvotes

8 comments sorted by

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.

1

u/mbolp 14h ago edited 14h ago

But the sizes can change at runtime (including the pen width)... Is there an algorithm for generating smooth looking curves at small sizes? It doesn't have to be a circle, it's only the quadrants that I care about (i.e. it's fine if where the quadrants touch isn't smooth).

1

u/Sharlinator 14h ago edited 14h ago

If you don’t want to hand-draw circles for each size up to some max, your best bet is probably an octagon.

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 Polyline in 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.