Reputation: 18305
I have tried using the method drawOval with equal height and width but as the diameter increases the circle becomes worse looking. What can I do to have a decent looking circle no matter the size. How would I implement anti-aliasing in java or some other method.
Upvotes: 39
Views: 66379
Reputation: 38369
As it turns out, Java2D (which I'm assuming is what you're using) is already pretty good at this! There's a decent tutorial here: http://web.archive.org/web/20100330030913/http://www.javaworld.com/javaworld/jw-08-1998/jw-08-media.html
The important line is:
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Upvotes: 64
Reputation: 589
EDITED: 06 September 2017
That's an algorithm invented by me to draw a circle over a integer matrix. The same idea could be used to write a circle inside a BufferedImage. If you are trying to draw that circle using the class Graphics this is not the answare you are looking for (unless you wish to modify each color-assignement with g.drawLine(x, y, x+1, y), but it could be very slow).
protected boolean runOnCircumference(int[][] matrix, int x, int y, int ray, int color) {
boolean ret;
int[] rowUpper = null, rowInferior = null, rowCenterUpper = null, rowCenterInferior = null;
if (ret = ray > 0) {
if (ray == 1) {
matrix[y][x + 1] = color;
rowUpper = matrix[++y];
rowUpper[x] = color;
rowUpper[x + 2] = color;
matrix[y][x] = color;
} else {
double rRay = ray + 0.5;
int r = 0, c = 0, ray2 = ray << 1, ray_1 = ray - 1, halfRay = (ray >> 1) + ray % 2, rInf,
ray1 = ray + 1, horizontalSymmetricOldC;
// draw cardinal points
rowUpper = matrix[ray + y];
rowUpper[x] = color;
rowUpper[x + ray2] = color;
matrix[y][x + ray] = color;
matrix[ray2 + y][x + ray] = color;
horizontalSymmetricOldC = ray1;
rInf = ray2;
c = ray_1;
for (r = 0; r < halfRay; r++, rInf--) {
rowUpper = matrix[r + y];
rowInferior = matrix[rInf + y];
while (c > 0 && (Math.hypot(ray - c, (ray - r)) < rRay)) {
rowUpper[x + c] = color;
rowUpper[x + horizontalSymmetricOldC] = color;
rowInferior[x + c] = color;
rowInferior[x + horizontalSymmetricOldC] = color;
// get the row pointer to optimize
rowCenterUpper = matrix[c + y];
rowCenterInferior = matrix[horizontalSymmetricOldC + y];
// draw
rowCenterUpper[x + r] = color;
rowCenterUpper[x + rInf] = color;
rowCenterInferior[x + r] = color;
rowCenterInferior[x + rInf] = color;
horizontalSymmetricOldC++;
c--;
}
} // end r circle
}
}
return ret;
}
I tried it so many times, verifying manually it correctness, so I think it will work. I haven't made any range-check just to simplify the code. I hope it will help you and everyone wish to draw a circle over a matrix (for example, those programmer who tries to create their own videogames on pure code and need to manage a matrix-oriented game-map to store the objects lying on the game-map [if you need help on this, email me]).
Upvotes: -2
Reputation: 23573
Thanks to Oleg Estekhin for pointing out the bug report, because it explains how to do it.
Here are some small circles before and after. Magnified a few times to see the pixel grid.
Going down a row, they're moving slightly by subpixel amounts.
The first column is without rendering hints. The second is with antialias only. The third is with antialias and pure mode.
Note how with antialias hints only, the first three circles are the same, and the last two are also the same. There seems to be some discrete transition happening. Probably rounding at some point.
Here's the code. It's in Jython for readability, but it drives the Java runtime library underneath and can be losslessly ported to equivalent Java source, with exactly the same effect.
from java.lang import *
from java.io import *
from java.awt import *
from java.awt.geom import *
from java.awt.image import *
from javax.imageio import *
bim = BufferedImage(30, 42, BufferedImage.TYPE_INT_ARGB)
g = bim.createGraphics()
g.fillRect(0, 0, 100, 100)
g.setColor(Color.BLACK)
for i in range(5):
g.draw(Ellipse2D.Double(2+0.2*i, 2+8.2*i, 5, 5))
g.setRenderingHint( RenderingHints. KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON)
for i in range(5):
g.draw(Ellipse2D.Double(12+0.2*i, 2+8.2*i, 5, 5))
g.setRenderingHint( RenderingHints. KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE)
for i in range(5):
g.draw(Ellipse2D.Double(22+0.2*i, 2+8.2*i, 5, 5))
#You'll probably want this too later on:
#g.setRenderingHint( RenderingHints. KEY_INTERPOLATION,
# RenderingHints.VALUE_INTERPOLATION_BICUBIC)
#g.setRenderingHint( RenderingHints. KEY_RENDERING,
# RenderingHints.VALUE_RENDER_QUALITY)
ImageIO.write(bim, "PNG", File("test.png"))
Summary: you need both VALUE_ANTIALIAS_ON
and VALUE_STROKE_PURE
to get proper looking circles drawn with subpixel accuracy.
Upvotes: 6
Reputation: 48619
Two things that may help:
Graphics2D.draw(Shape)
with an instance of java.awt.geom.Ellipse2D
instead of Graphics.drawOval
Graphics2D.setRenderingHint
to enable antialiasingExample
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Shape theCircle = new Ellipse2D.Double(centerX - radius, centerY - radius, 2.0 * radius, 2.0 * radius);
g2d.draw(theCircle);
}
See Josef's answer for an example of setRenderingHint
Upvotes: 18
Reputation: 8395
Inability to draw a "decent looking circle" is related to the very old bug 6431487.
Turning antialiasing on does not help a lot - just check the kind of "circle" produced by the drawOval() or drawShape(Eclipse) when the required circle size is 16 pixels (still pretty common for icon size) and antialiasing is on. Bigger antialiased circles will look better but they are still asymmetric, if somebody will care to look at them closely.
It seems that to draw a "decent looking circle" one has to manually draw one. Without antialiasing it will be midpoint circle algorithm (this question has an answer with a pretty java code for it).
Upvotes: 3
Reputation: 9058
Of course you set your radius to what ever you need:
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
Ellipse2D.Double hole = new Ellipse2D.Double();
hole.width = 28;
hole.height = 28;
hole.x = 14;
hole.y = 14;
g2d.draw(hole);
}
Upvotes: 7
Reputation: 74507
you can set rendering hints:
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Upvotes: 33