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):
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.
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:
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.
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.
Try setting this QPainter flag to true:
painter->setRenderHint(QPainter::Antialiasing, true);
Did the trick for me!
Related
Image negative effect is typically done like this:
pixel = rgb(1, 1 ,1) - pixel
but if the pixel color is close to gray, than:
pixel = rgb(1, 1, 1) - rgb(0.5, 0.5, 0.5) = 0.5
That's not a problem and it's how it should be, but for me it is, I am making a crosshair texture in my 3D game, which will be drawn in the center of the screen and I want it to have negative effect, reason for it is clearity, if I were to make crosshair white, it would not be visible when looking on white objects (I know I can make it with black outline so it is visible, but thats ugly), but it still has problems for grayish colors as I described, what can be done to fix that?
I have followed this gtkmm tutorial on how to draw shapes and fill them with colors (e.g. A red disc on a transparent background). I was also able, from this example, to derive another example with a red disc on a blue background.
However, what I would really need is a transparent disc with a blue background that fills everything minus the disc area, which should stay transparent.
So with cairo, the usual workflow is:
Create a surface
Draw a shape (e.g. draw a circle)
Fill the circle, so that it becomes a disc.
I would need some workflow that achieves something like this instead:
Create a surface
Draw a shape (e.g. draw a circle)
Fill the area outside the circle, so that I have a colored background with a transparent "hole" in the middle.
I have done some research on this on the web but all examples seem to assume that we want to fill the inner region of a shape (which I must admit is more typical).
How could I do this?
P.S. I have added the C tag because I don't mind if you prefer to use C (or even Python).
Draw your circle and draw a rectangle containing all the visible area. Set the cairo fill rule to even/odd. Fill. Done.
cairo_save(cr); // Save the state
cairo_arc(cr, 42, 42, 21, 0, 2*M_PI); // Draw circle
cairo_rectangle(cr, 0, 0, width, height); // Rectangle containing everything
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
cairo_fill(cr);
cairo_restore(cr); // Restore default fill rule (optional; pairs with save above)
IMHO, The function of 'Draw outside the circle' is complex to the graphic framework. It may also be ambiguous if you draw more than one circle filled outside.
As graphic shapes drawn later are placed 'on' the ones drawn former. What is needed is that draw a rectangle to fill the entire graphic context before drawing other shapes. This is defined as clear with the background color in some frameworks.
the workflow would seem like:
1. Create the surface.
2. Draw the background colored with what outside the circle.
3. Draw the circle filled with a specific color, e.g. white.
As a result, the circle would cover the background.
If insist on draw the circle first, please search Flood Fill Algorithm, which is used to draw on images. However, it is needless and costly to achieve the screen pixels and play such algorithms when drawing on screen.
I find
Example Application: Creating a Clock with Cairo in the later section of the book you provide.
That seems help.
I have a rectangle r with the size of 1536x720 which I rotate around its own center point p(768, 360), which is easy enough with the built in sf::Sprite::rotate function
Now for unrelated reasons I have to split up the rectangle into 6 smaller rectangles (each sized 512x360).
These smaller rectangles are just the bigger rectangle r sliced up; they are positioned in such a way that "stitched together" they form the original rectangle (so it looks like it's still the original big rectangle)
Illustration: image
Since rectangle r was constantly rotating around its own center, splitting it into smaller rectangles has introduced the problem:
How do I rotate the stitched together rectangle (i.e. the smaller rectangles) around the original center p so that the original bigger rectangle is maintained? Is this even possible?
My English isn't that great so it's a bit tricky to explain. If you need more explaining I can draw the scenario in paint or something
You can use sf::Transformable::setOrigin to set transform origin of each small rectangle to the center point of the large one.
Notice that it will become the origin of all transformations, e.g. small rectangles will scale with respect to that new center too, but probably that's the desired behavior.
I have found some code that will allow me to draw a rounded rectangle in OpenGL immediate mode (here).
What I would like to do it decrease the alpha, the further away from the centre of the rectangle - as would find under a Windows/Mac window or dialog for example where a shadow is drawn.
Can somebody point me to an example on how to do this?
This is relatively easy to do with geometry.
Create the vertices for two rounded rectangles: an inner one and an outer one. Assign an alpha of 1 to the inner rectangle, and an alpha of 0 to the outer rectangle. Triangulate both the inner rectangle and the space between the two rectangles. Unless you specifically ask otherwise, the alpha will be interpolated smoothly between the inner rectangle and the outer rectangle.
Something like this:
You may have better results using a texture and slicing the rectangle into 9 parts—this may give you better output with simpler geometry and simpler code, depending on your application.
I understand i need to render only 1x1 or 3x3 pixel part of the screen where the mouse is, with object id's as colors and then get id from the color.
I have implemented ray-cast picking with spheres and i am guessing it has something to do with making camera look in direction of the mouse ray?
How do i render the correct few pixels?
Edit:
setting camera in direction of mouse ray works, but if i make the viewport smaller the picture scales but what (i think) i need is for it to be cropped rather than scaled. How would i achieve this?
The easiest solution is to use the scissor test. It allows you to render only pixels within a specified rectangular sub-region of your window.
For example, to limit your rendering to 3x3 pixels centered at pixel (x, y):
glScissor(x - 1, y - 1, 3, 3);
glEnable(GL_SCISSOR_TEST);
glDraw...(...);
glDisable(GL_SCISSOR_TEST);
Note that the origin of the coordinate system is at the bottom left of the window, while most window systems will give you mouse coordinates in a coordinate system that has its origin at the top left. If that's the case on your system, you will have to invert the y-coordinate by subtracting it from windowHeight - 1.