I created the following code example to demonstrate my problem. I want to draw into a BufferedImage by a Graphics2D object, but the edges are not sharp. I tried using different renderinghints, but it didn't help. I also tried a BufferedImageOp as you can see in the code, but I don't understand its meaning in drawImage and don't know its possibilites. Can you help me please?
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
public class SmoothGraphics extends JComponent{
private BufferedImage image;
private static JFrame frame;
private int x = 100;
private int y = 100;
private int size = 200;
public static void main(String[] args){
frame = new JFrame();
SmoothGraphics component = new SmoothGraphics();
frame.add(component);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setVisible(true);
frame.pack();
}
public SmoothGraphics(){
super();
setPreferredSize(new Dimension(400,400));
setBounds(100,100,400,400);
}
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int width = getWidth();
int height = getHeight();
GraphicsConfiguration gc = frame.getGraphicsConfiguration();
image = gc.createCompatibleImage(width, height);
Graphics2D imageGraphics = (Graphics2D) image.createGraphics();
imageGraphics.setColor(Color.RED);
/*imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);*/
drawExample(imageGraphics);
imageGraphics.dispose();
/*the following kernel helps with the smoothness of the rectangle, but not of the circle
int kernelWidth = 3;
int kernelHeight = 3;
float[] data = new float[kernelWidth*kernelHeight];
data[4]=1;
Kernel kernel = new Kernel(kernelWidth, kernelHeight, data);
BufferedImageOp op = new ConvolveOp(kernel);
*/
//draw image
BufferedImageOp op = null;
g2d.drawImage(image,op,0,0);
//I don't know if this line is necessary
image.flush();
}
private void drawExample(Graphics2D g2d){
//Square as path
Path2D.Double path = new Path2D.Double();
path.moveTo(x,y);
path.lineTo(x+size,y);
path.lineTo(x+size,y+size);
path.lineTo(x,y+size);
path.closePath();
g2d.draw(path);
//Circle
g2d.fillOval(x+size/4,y+size/4,size/2,size/2);
}
}
The output of my code. Notice that the edges are not very clean/sharp!
My result with antialiasing and kernel usage:
My wished result:
Okay... I believe the real problem you are facing is initial scaling of the component graphics (g
), which makes your image
stretched during drawImage(...)
. I don't get the same problem here.
To get proper smooth (or "sharp" as you call it) rendering, you do want to enable antialiasing. But you also need to paint without pixel scaling (it's possible with image too, by creating a larger image, however, I don't see why you need the extra image in this case).
Here's your sample program rewritten:
import javax.swing.*;
import java.awt.*;
public class SmoothGraphics extends JComponent {
private final int x = 100;
private final int y = 100;
private final int size = 200;
public static void main(String[] args) {
JFrame frame = new JFrame("Smooth");
SmoothGraphics component = new SmoothGraphics();
frame.getContentPane().add(component, BorderLayout.CENTER); // Add to content pane
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack(); // Pack *before* setVisible(true)!
frame.setLocationRelativeTo(null); // Center the frame after pack()
frame.setVisible(true);
}
public SmoothGraphics() {
setOpaque(true); // Minor optimization
setPreferredSize(new Dimension(400, 400)); // No need to use setBounds, pack() will fix that for you
}
@Override
public void paintComponent(Graphics g) {
// No need to call super, as we repaint everything
Graphics2D g2d = (Graphics2D) g;
// Clear background to black
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
// Paint in read, with antialiasing
g2d.setColor(Color.RED);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawExample(g2d);
}
private void drawExample(Graphics2D g2d) {
// Square (this way avoids the odd skewed lines)
g2d.drawRect(x, y, size, size);
// Circle
g2d.fillOval(x + size / 4, y + size / 4, size / 2, size / 2);
}
}
Result:
If you really want/need the BufferedImage
, I found a way that works for me (MacOS). It might need some minor tweaks to work perfectly on other platforms.
@Override
public void paintComponent(Graphics g) {
// No need to call super, as we repaint everything
Graphics2D g2d = (Graphics2D) g;
GraphicsConfiguration gc = g2d.getDeviceConfiguration();
AffineTransform transform = gc.getNormalizingTransform();
BufferedImage image = gc.createCompatibleImage((int) ceil(getWidth() * transform.getScaleX()), (int) ceil(getHeight() * transform.getScaleY()));
Graphics2D graphics = image.createGraphics();
try {
graphics.setTransform(transform);
graphics.setColor(Color.RED);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Need antialiasing on
drawExample(graphics);
}
finally {
graphics.dispose();
}
try {
AffineTransform originalTransform = g2d.getTransform();
originalTransform.concatenate(transform.createInverse());
g2d.setTransform(originalTransform);
}
catch (NoninvertibleTransformException e) {
throw new RuntimeException(e); // API oddity, this should have been a RuntimeException
}
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // Need render QUALITY here, to avoid the stretching/tearing
// g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); // Don't need this on MacOS, but may be needed on other platforms
g2d.drawImage(image, null, 0, 0);
}
Result: