1
votes

I'm trying to use QPainter::drawEllipse to draw circles. I want to be able to:

  • set the width of the stroke of the circle (QPen::width)
  • choose the shape of the pixels that are at the center of the circle (1x1, 1x2, 2x1 or 2x2)
  • optionally make the circle filled instead of stroked
  • ensure that the circle has the correct radius (even when the stroke width is greater than 1)

These goals are surprisingly difficult to achieve. This is an example of what I want to render (drawn by hand):

Example

The image is 32x32 (scaled up to 512x512). The red center point is at (15, 15). The center is 1x2 so there's an extra red pixel below the center pixel. The stroke has a width of 2 pixels. If the stroke was made wider, pixels would be added to the inside of the circle. The bounding box of the circle is the same regardless of stroke width. The radius is 8 pixels. Each of the blue lines is 8 pixels long. Just to be clear, the red and blue pixels are just there for describing the circle. They are not part of my desired output.

What my problem really boils down to is rendering an ellipse that fits perfectly inside a rectangle. I can calculate the rectangle using the center point, the radius, and the center shape. That part is easy. Simply calling drawEllipse with this rectangle doesn't work. I think I have to adjust this rectangle somehow before calling drawEllipse but I'm not too sure how to adjust it. I've tried fiddling around with it and I found some solutions that work for some pen widths but not others.

Does the pen cap matter? I've been using RoundCap. Should I be using a different cap?

I'm almost at the point where I'm considering doing the pixel manipulation myself. I'm rendering onto a QImage and using the Source composite operation so my code might be slightly faster than drawEllipse. memset is about 10x faster than QImage::fill so writing faster code probably won't be too hard! I'd rather not have to do that though.

2
Can you show us some code that you've tried that doesn't quite meet the requirements? That would likely get you some better answers.Toby Speight

2 Answers

2
votes

I stumbled upon a section in the docs that talks about how QRects are rendered. It describes the relationship between the rendered pixels and the logical rectangle. The rendered rectangle is bigger than the logical rectangle. All I have to do is make the logical rectangle smaller to compensate.

QRect adjustStrokedRect(const QRect rect, const int thickness) {
  return QRect{
    rect.left() + thickness / 2,
    rect.top() + thickness / 2,
    rect.width() - thickness,
    rect.height() - thickness
  };
}

Ok, so now I can get stroked rectangles to render in the right place. An ellipse is described by a QRect so what if I just apply this transformation to that rectangle?

Nope.

It sort of works if the thickness is 1, 2, 4, 6 but not 3, 5, 7. The circle is one pixel too small when the thickness is 3, 5, 7. So I tried adding 1 to the rectangle size if thickness % 2 == 1 && thickness != 1 but then an asymmetric circle is rendered from a square. For some combinations of position and size, a wonky asymmetric circle is rendered even when the size is square.

Here's a weird image that you can easily reproduce:

Example

Produce it with this code:

QImage image{32, 32, QImage::Format_ARGB32_Premultiplied};
QPainter painter{&image};
QPen pen{Qt::NoBrush, 3.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin};
pen.setColor(QColor{0, 255, 0, 255});
painter.setPen(pen);
painter.drawEllipse(8, 8, 17, 17);
image.save("weird.png");

I simply don't understand how that's even possible. To me, it seems like drawEllipse is rendering an ellipse that just roughly fits within the rectangle. I haven't been able to find the relationship between the rectangle and the ellipse anywhere in the docs. Perhaps this is because it's a very loose relationship.

I have no trouble getting QPainter::drawEllipse to draw circles with a stroke width of 1 so for now I just won't allow thick circles in my application. If I can’t render it perfectly, I won’t render it at all. I'm not marking this answer as accepted though as I would still like this to work.

1
votes

I probably am too late for this, but still, for future reference:

Unfortunately, Qt plots ellipses using Bezier curves (as of now, which might just change soon) , which is a pretty good approximation of an ellipse, but isn't perfect. Plotting a pixel-perfect ellipse would require a manual implementation at the pixel level.