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:
Related
Java JLabel icons are displaying with distorted pixels in JFrame. This is happening consistently with different png images (all 32x32). I am not scaling the images, they are displayed in the program 32x32, which I verified using getWidth and getHeight on the JLabel. The distortions appear in the same place each time the program is run, not randomly.
Screenshot using the example code provided below.
In this screenshot you can see an array of JLabel icons, each affected one differently.
When resizing the window from sideways, as the icon moves with the window, the distortions move across the icon like a vertical line.
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
public class FrameApp extends JFrame
{
public static void main(String[] args) throws IOException
{
FrameApp frameApp = new FrameApp();
}
private FrameApp() throws IOException
{
BufferedImage image;
URL url = new URL("http://i.stack.imgur.com/L5DGx.png");
image = ImageIO.read(url);
JLabel label = new JLabel(new ImageIcon(image));
add(label);
pack();
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
Edit:
I am using JDK 11.0.3, Java SE Runtime Environment build 1.8.0_202, on Windows 8.1 64-bit.
You may think you're displaying the images at 32x32 size, but your example of the tiled images says that's not so. You have a 9x2 grid of icons, which should be 288x64 pixels, but in your sample image the grid is 302x66.
If you carefully examine your tiled image, you can see that the individual tiles are being displayed 34px wide - see the magenta border that extends from 32px to 66px. (Note, some of the tiles are displayed 33px wide; it appears to be 33, 34, 34, 33, 34...)
In order to stretch the tiles to the wider width, certain columns are being doubled (red borders) and this creates the visual glitches you are seeing.
Have you tried fixing the size of the JLabel instead of allowing it to size based on its contents?
First option:
Instead of using ImageIcon, you can try to create your own icon class drawing the Image using graphics.drawImage(x,y,width,height,null) controlling rendering quality (https://docs.oracle.com/javase/tutorial/2d/advanced/quality.html)
an example would be something like this:
public class Icon32 extends ImageIcon {
public Icon32(String f) {
super(f);
BufferedImage i= new BufferedImage(32, 32,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = (Graphics2D) i.getGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(getImage(), 0, 0, 32, 32, null);
setImage(i);
}
public int getIconHeight() {
return 32;
}
public int getIconWidth() {
return 32;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.drawImage(getImage(), x, y, c);
}
}
where the method:
getImage()
is loading your image/icon...
Second option: if you are not happy with the result you can try to use this library:
https://github.com/mortennobel/java-image-scaling
it claims to provides better image scaling options than the Java runtime provides.
Answer is from this link to generate high quality image : https://componenthouse.com/2008/02/08/high-quality-image-resize-with-java/
The appropriate class from the link :
public class ImageResize {
public static void main(String[] args) {
try {
URL url = new URL("http://i.stack.imgur.com/L5DGx.png");
BufferedImage image = ImageIO.read(url);
ImageIO.write(resizeImage(image, 32, 32), "png", new File("D:/picture3.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
private static BufferedImage resize(BufferedImage image, int width, int height) {
int type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
BufferedImage resizedImage = new BufferedImage(width, height, type);
Graphics2D g = resizedImage.createGraphics();
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(image, 0, 0, width, height, null);
g.dispose();
return resizedImage;
}
private static BufferedImage resizeImage(BufferedImage image, int width, int height) {
image = createCompatibleImage(image);
image = resize(image, 100, 100);
image = blurImage(image);
return resize(image, width, height);
}
public static BufferedImage blurImage(BufferedImage image) {
float ninth = 1.0f/9.0f;
float[] blurKernel = {
ninth, ninth, ninth,
ninth, ninth, ninth,
ninth, ninth, ninth
};
Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>();
map.put(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
map.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
map.put(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
RenderingHints hints = new RenderingHints(map);
BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, hints);
return op.filter(image, null);
}
private static BufferedImage createCompatibleImage(BufferedImage image) {
GraphicsConfiguration gc = BufferedImageGraphicsConfig.getConfig(image);
int w = image.getWidth();
int h = image.getHeight();
BufferedImage result = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
Graphics2D g2 = result.createGraphics();
g2.drawRenderedImage(image, null);
g2.dispose();
return result;
}
}
For my game, I paint to a java.awt.Image then draw the Image onto a JPanel. I do this for a couple of reasons, mainly because I didn't want game rendering to hog up cpu cycles on the EDT, and for portability.
A problem arised which caused flickering on the java.awt.JPanel when using
graphics#drawImage(Image img, int x, int y, int width, int height,
ImageObserver observer)
.
However,
graphics#drawImage(Image img, int x, int y, ImageObserver observer)
did not cause this issue.
Here is my code:
Sandbox.java
public class Sandbox implements Paintable {
public static void main(String[] args) throws Exception {
GameWindow window = new GameWindow("Test", 800, 600);
GameScreen screen = new GameScreen(800, 600);
Sandbox sandbox = new Sandbox();
window.add(screen);
window.setVisible(true);
boolean running = true;
while(running) {
sandbox.update();
screen.getPaintBuffer().clear();
screen.getPaintBuffer().paint(sandbox);
screen.repaint();
Thread.sleep(1000 / 60);
}
}
private int x = 0, y = 0;
public void update() {
x++;
y++;
}
public void paint(Graphics g) {
g.drawRect(x, y, 50, 50);
}
}
GameWindow.java
public class GameWindow extends JFrame {
public GameWindow(String title, int width, int height) {
setTitle(title);
setSize(width, height);
setResizable(false);
setLocationByPlatform(true);
setDefaultCloseOperation(3);
}
}
GameScreen.java
public class GameScreen extends JPanel {
private ImageBuffer buffer;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
}
public void paint(Graphics g) {
g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), null);
}
public ImageBuffer getPaintBuffer() {
return buffer;
}
}
Paintable.java
public interface Paintable {
public void paint(Graphics g);
}
ImageBuffer.java
public class ImageBuffer {
private final Image buffer;
private int width, height;
public ImageBuffer(int width, int height) {
buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
this.width = width;
this.height = height;
}
public void paint(Paintable paintable) {
paintable.paint(buffer.getGraphics());
}
public void clear() {
buffer.getGraphics().clearRect(0, 0, width, height);
}
public Image getBuffer() {
return buffer;
}
}
Change the GameScreen's paint method to...
Override paintComponent instead
Call super.paintComponent in order to maintain the paint chain's contract
Pass this as the ImageObsever parameter to drawImage
For example...
public class GameScreen extends JPanel {
private ImageBuffer buffer;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
}
public ImageBuffer getPaintBuffer() {
return buffer;
}
}
Have a look at Painting in AWT and Swing and Performing Custom Painting for more details about how painting works
Updated
The basic problem is a scaling issue...
The image you're using to draw with is 800x600, but the GameScreen is actually 794x572 (on my PC), this causes the image to be scaled. Now, Swing's default scaling isn't pretty and is based on speed over quality generally.
Now, there are a number of ways you could improve this, but a quick way would be to apply some higher rendering hints, for example
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
g2d.dispose();
}
Now, I've gone overboard with this, so you might want to remove some and see what changes
Updated
Rendering hints could slow the rendering process down, so a better solution might to override the getPreferredSize method of GameScreen and return the expected size
public static class GameScreen extends JPanel {
private ImageBuffer buffer;
private Dimension size;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
this.size = new Dimension(width, height);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
Then, rather then passing the size to GameWindow, simply call pack
GameWindow window = new GameWindow("Test");
GameScreen screen = new GameScreen(800, 600);
Sandbox sandbox = new Sandbox();
window.add(screen);
window.pack();
window.setVisible(true);
This way, everybody's on the same size
I hope you can help me concerning a problem in my drawString.
I do some drawing within a Factory class and return a BufferedImage with a String on it.
public class Factory {
public BufferedImage create() {
TempPanel tempPanel = new TempPanel(new Dimension(100, 100));
return tempPanel.createImage();
}
private class TempPanel extends JPanel {
public TempPanel(Dimension d) {
this.setVisible(true);
this.setSize(d);
}
public BufferedImage createImage() {
BufferedImage bi = new BufferedImage(this.getSize().getWidth(), this.getSize().getHeight(), BufferedImage.TRANSLUCENT);
this.paintComponent(bi.createGraphics());
return bi;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponents(g);
// General setup
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Transparent Background
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, graphicalObject.width, graphicalObject.height);
g2.setComposite(AlphaComposite.Src);
// Different settings for a certain graphical object
g2.setFont(new Font("TimesRoman", Font.PLAIN, 12);
// Actual drawing
g2.drawString("Test", 0, 0);
}
}
}
And then I also have a JPanel in which this image should be drawn:
public class Label extends JPanel {
// ...
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Factory factory = new Factory();
BufferedImage img = factory.create();
g2.drawImage(img, 0, 0, null);
}
}
The problem ist that the text only appears if i minimize and restore the window, but as soon as I start moving the mouse within the window, the text disappears again.
When I call drawRect() instead of drawString(), everything works fine?!
Can someone tell me why?
Thanks!
There are a number of things that "weird" me out...
Firstly, you're creating a new Graphics context, but you're not disposing of it. This could be leaving resources open and may actually prevent the contents from been rendered on some OSs...
So instead of...
BufferedImage bi = new BufferedImage(this.getSize().getWidth(), this.getSize().getHeight(), BufferedImage.TRANSLUCENT);
this.paintComponent(bi.createGraphics());
You should be doing something more like...
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TRANSLUCENT);
Graphics2D g2d = bi.createGraphics();
this.paintComponent(g2d);
g2d.dispose();
Secondly, text is not render the same way that most other elements are, that is, it doesn't start at x/y and render down/right, in most cases it starts at x/y and renders, slightly, up/right. The actual coordinate is based on the Fonts FontMetrics getAscent value...
So, instead of...
g2.setFont(new Font("TimesRoman", Font.PLAIN, 12));
// Actual drawing
g2.drawString("Test", 0, 0);
You should be doing something more like...
g2.setFont(new Font("TimesRoman", Font.PLAIN, 12));
FontMetrics fm = g2.getFontMetrics();
// Actual drawing
g2.drawString("Test", 0, fm.getAscent());
See Working with Text APIs or more details...
Thirdly, JComponent and by extension, anything that extends from it, is an ImageObsever, this is important, as not everything that is painted is ready for display immediately, sometimes it needs further processing, rather than having to deal with this annoyance yourself, you can delegate the responsibility, so instead of...
g2.drawImage(img, 0, 0, null);
You should be doing something more like...
g2.drawImage(img, 0, 0, this);
This allows the component to listen to the underlying image and respond to any changes that might happen to the image data and update itself.
Forthly...
private class TempPanel extends JPanel {
public TempPanel(Dimension d) {
this.setVisible(true);
this.setSize(d);
}
public BufferedImage createImage() {
BufferedImage bi = new BufferedImage(this.getSize().getWidth(), this.getSize().getHeight(), BufferedImage.TRANSLUCENT);
this.paintComponent(bi.createGraphics());
return bi;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponents(g);
// General setup
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Transparent Background
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, graphicalObject.width, graphicalObject.height);
g2.setComposite(AlphaComposite.Src);
// Different settings for a certain graphical object
g2.setFont(new Font("TimesRoman", Font.PLAIN, 12);
// Actual drawing
g2.drawString("Test", 0, 0);
}
}
Is freaking me out...Swing components are meant to be attached to a native peer onto which they are rendered, they are also optimised not to paint when they aren't displayable, this could be throwing off your rendering...
Infact, you could achieve the same thing using something like...
public static class Factory {
private static Renderer renderer;
public BufferedImage create() {
if (renderer == null) {
renderer = new Renderer(new Dimension(100, 100));
}
return renderer.createImage();
}
private class Renderer {
private Dimension size;
public Renderer(Dimension size) {
this.size = size;
}
public BufferedImage createImage() {
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TRANSLUCENT);
Graphics2D g2 = bi.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Transparent Background
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, graphicalObject.width, graphicalObject.height);
g2.setComposite(AlphaComposite.Src);
// Different settings for a certain graphical object
g2.setFont(new Font("TimesRoman", Font.PLAIN, 12));
FontMetrics fm = g2.getFontMetrics();
// Actual drawing
g2.drawString("Test", 0, fm.getAscent());
g2.dispose();
return bi;
}
}
}
Which would divorce the actual painting from anything else and provide you with control over it...
Does java Shape interface contract and library routines allow combining multiple shapes into one object extending Shape interface?
For example, may I define class Flower which will consist of several ovals for petals and core?
Or the Shape supposes only one continuous outline? If so then is there any class in Java for holding multiple shapes, may be some class for vectorized graphics?
To manipulate shapes in Java like you're describing, you want to use the Area class, which has these operations. Just convert a Shape to an Area with new Area(Shape).
Here is my attempt - using a rotate transform anchored on the center of the flower shape.
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class DaisyDisplay {
DaisyDisplay() {
BufferedImage daisy = new BufferedImage(
200,200,BufferedImage.TYPE_INT_RGB);
Graphics2D g = daisy.createGraphics();
g.setColor(Color.GREEN.darker());
g.fillRect(0, 0, 200, 200);
Daisy daisyPainter = new Daisy();
daisyPainter.paint(g);
g.dispose();
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(daisy)));
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DaisyDisplay();
}
});
}
}
class Daisy {
public void paint(Graphics2D g) {
Area daisyArea = getDaisyShape();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
Math.PI*1/8,
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
Math.PI*3/8,
100,100);
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
Math.PI*2/8,
100,100));
paintDaisyPart(g,daisyArea);
}
public void paintDaisyPart(Graphics2D g, Area daisyArea) {
g.setClip(daisyArea);
g.setColor(Color.YELLOW);
g.fillRect(0, 0, 200, 200);
g.setColor(Color.YELLOW.darker());
g.setClip(null);
g.setStroke(new BasicStroke(3));
g.draw(daisyArea);
}
public Area getDaisyShape() {
Ellipse2D.Double core = new Ellipse2D.Double(70,70,60,60);
Area area = new Area(core);
int size = 200;
int pad = 10;
int petalWidth = 50;
int petalLength = 75;
// left petal
area.add(new Area(new Ellipse2D.Double(
pad,(size-petalWidth)/2,petalLength,petalWidth)));
// right petal
area.add(new Area(new Ellipse2D.Double(
(size-petalLength-pad),(size-petalWidth)/2,petalLength,petalWidth)));
// top petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,pad,petalWidth,petalLength)));
// bottom petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,(size-petalLength-pad),petalWidth,petalLength)));
return area;
}
}
In very basic terms I have a panel that is drawing a line pixel by pixel and is being updated in real time. On top of this I want another panel that draws a box around the current pixel but clears itself later. Both are in real time
My current situation is a wrapper JPanel that has an OverlayLayout. The bottom panel has the line that is drawn with the Graphics2D object fetched from its JPanel. The top panel the box that follows around that is also drawn with the Graphics2D object fetched from its JPanel.
The issues in this system are many. Since I'm just drawing on the Graphics2D object separately and not overriding the JPanel's paint() not only is all the lines lost when the panel needs to repaint, I think I'm going against Swing's threading model by only having a single thread update the screen. I have also not gotten the top panel to work correctly, it just keeps clearing the screen and won't let the bottom panel draw a line.
What would be the best way to approach this situation? I have little experience with image processing and the low-level displaying of images in Swing, I just know the basics. I've heard of BufferedImage, but not knowing where to put that image, if it will be updated when changed later, the efficiency of doing that, and the fact that its buffered scare me off. I'm not sure what to use
Can someone point me in the right direction of what I need to use to accomplish this?
You can accumulate your points in a suitable Shape, such as GeneralPath, as shown in LissajousPanel. Use a javax.swing.Timer to cause periodic updates, and highlight the most recently added point with a surrounding rectangle.
I suggest
that you do not get your JPanel's Graphics or Graphics2D object via getGraphics as this object is not meant to be stable and will not work once the component has been repainted for any reason.
that you instead do all your drawing in a single JPanel's paintComponent method, but first in that method call the super's method.
that you again draw in only one JPanel, not two,
that you draw your line in a BufferedImage, since that is a more permanent part of your image.
That you get the BufferedImage's Graphics2D object via its createGraphics method
That you properly dispose of the BufferedImage's Graphics object when done using it so as to conserve resources.
that you display your BufferedImage in the paintComponent method of your JPanel (after calling the super method first)
that you draw your box, the more fleeting portion of the image, directly in the paintComponent using int class variables to tell paintComponent where to draw and perhaps a class boolean variable to tell if to draw.
and that most important, you review the tutorials on Swing graphics as to do this properly, you'll have to throw all old assumptions out and learn the correct way from the ground up (as we all had to do).
For example:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
#SuppressWarnings("serial")
public class BufferedImageEg extends JPanel {
private static final int BI_WIDTH = 700;
private static final int BI_HEIGHT = 500;
private static final Color BACKGROUND = new Color(255, 255, 240);
private static final int THIS_PT_WIDTH = 12;
private static final int THIS_PT_HEIGHT = THIS_PT_WIDTH;
private static final float THIS_PT_STROKE_WIDTH = 2f;
private static final Color THIS_PT_BORDER_COLOR = Color.red;
private static final Color THIS_PT_FILL_COLOR = new Color(250, 250, 0, 125);
private static final int TIMER_DELAY = 30;
private static final int X_MIN = 0;
private static final int X_MAX = 100;
private static final double X_STEP = 0.1;
private static final double X_SCALE = (double) BI_WIDTH
/ ((double) X_MAX - X_MIN);
private static final double Y_SCALE = 8;
private static final float LINE_WIDTH = 4;
private static final Color LINE_COLOR = Color.blue;
private Point lastPoint = null;
private Point thisPoint = null;
private BufferedImage bImage = new BufferedImage(BI_WIDTH, BI_HEIGHT,
BufferedImage.TYPE_INT_RGB);
private double xValue = X_MIN;
public BufferedImageEg() {
Graphics biG = bImage.getGraphics();
biG.setColor(BACKGROUND);
biG.fillRect(0, 0, BI_WIDTH, BI_HEIGHT);
setBackground(BACKGROUND);
new Timer(TIMER_DELAY, new ActionListener() {
public void actionPerformed(ActionEvent e) {
timerActionPerformed(e);
}
}).start();
}
private void timerActionPerformed(ActionEvent e) {
if (xValue <= X_MAX) {
lastPoint = thisPoint;
double tempX = xValue;
double yValue = function(xValue);
tempX *= X_SCALE;
yValue *= Y_SCALE;
yValue = BI_HEIGHT / 2.0 - yValue;
thisPoint = new Point((int) tempX, (int) yValue);
if (lastPoint != null) {
drawInBufferedImage();
}
xValue += X_STEP;
} else {
((Timer) e.getSource()).stop();
thisPoint = null;
}
repaint();
}
private void drawInBufferedImage() {
Graphics2D g2 = bImage.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(LINE_WIDTH, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
g2.setColor(LINE_COLOR);
int x1 = lastPoint.x;
int y1 = lastPoint.y;
int x2 = thisPoint.x;
int y2 = thisPoint.y;
g2.drawLine(x1, y1, x2, y2);
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(BI_WIDTH, BI_HEIGHT);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(bImage, 0, 0, null);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if (thisPoint != null) {
drawThisPoint(g2);
}
}
private void drawThisPoint(Graphics2D g2) {
int x = thisPoint.x - THIS_PT_WIDTH / 2;
int y = thisPoint.y - THIS_PT_HEIGHT / 2;
Graphics2D g2b = (Graphics2D) g2.create();
g2b.setStroke(new BasicStroke(THIS_PT_STROKE_WIDTH));
g2b.setColor(THIS_PT_FILL_COLOR);
g2b.fillOval(x, y, THIS_PT_WIDTH, THIS_PT_HEIGHT);
g2b.setColor(THIS_PT_BORDER_COLOR);
g2b.drawOval(x, y, THIS_PT_WIDTH, THIS_PT_HEIGHT);
g2b.dispose();
}
private double function(double x) {
return 24 * Math.sin(x / 12.0) * Math.sin(x);
}
private static void createAndShowGui() {
JFrame frame = new JFrame("BufferedImage Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new BufferedImageEg());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}