Related
I'm trying to create a drawing on BufferedImage and then copy in onto JPanel.
When I draw directly on JPanel quality of the picture is v.good but when using intermediate BufferedImage quality / resolution is visibly reduced.
I've checked that with zoom option from OSX's Accessibility panel.
I'm developing on MacBook Pro Retina.
Is there some sort of automated scaling happening?
What am I doing wrong with BufferedImage?
Here's the code demonstrating the problem
package com.sample.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class QualityProblem {
private static final double DOT_SIZE = 4;
public static void main(String[] args) {
JFrame frame = new JFrame("ChartPanel demo");
frame.setBackground(Color.WHITE);
// JPanel draw = new DrawingOK();
JPanel draw = new DrawingUgly();
draw.setBackground(Color.BLACK);
frame.getContentPane().add(draw, BorderLayout.CENTER);
frame.setSize(new Dimension(1200, 900));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static class DrawingOK extends JPanel {
private static final long serialVersionUID = 1L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2draw = (Graphics2D) g.create();
try {
g2draw.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2draw.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2draw.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
Ellipse2D.Double e = new Ellipse2D.Double(50, 50, DOT_SIZE, DOT_SIZE);
g2draw.setColor(Color.YELLOW);
g2draw.fill(e);
} finally {
g2draw.dispose();
}
}
}
private static class DrawingUgly extends JPanel {
private static final long serialVersionUID = 1L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension size = getParent().getSize();
BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
Graphics2D ig = image.createGraphics();
ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
ig.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
ig.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
Graphics2D g2draw = (Graphics2D) g.create();
try {
Ellipse2D.Double e = new Ellipse2D.Double(50, 50, DOT_SIZE, DOT_SIZE);
ig.setColor(Color.YELLOW);
ig.fill(e);
g2draw.drawImage(image, 0, 0, null);
} finally {
ig.dispose();
g2draw.dispose();
}
}
}
}
Edited:
Added images with 4 pixel dot and 50D both zoomed in.
Ugly one comes from BufferedImage copied onto screen's Graphics
I fixed up your drawing code.
Here's the ugly GUI.
I moved the sizing of the panel to the panel constructor. Setting the frame size includes the borders. Setting the panel size gives you the drawing area you want.
I moved the black background painting to the paintComponent method. You might as well do all the painting in one place.
I cleaned up your drawing code. You don't need to make a copy of the paintComponent graphics instance to get Graphics2D.
I made the circle bigger so you could see the sharpness. I moved the origin to the center of the circle, and turned the DOT_SIZE into a radius.
Here's the code.
package com.ggl.testing;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class QualityProblem implements Runnable {
private static final double DOT_SIZE = 50D;
public static void main(String[] args) {
SwingUtilities.invokeLater(new QualityProblem());
}
#Override
public void run() {
JFrame frame = new JFrame("ChartPanel demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBackground(Color.WHITE);
// JPanel draw = new DrawingOK();
JPanel draw = new DrawingUgly();
frame.getContentPane().add(draw, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
private class DrawingOK extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingOK() {
this.setPreferredSize(new Dimension(600, 400));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2draw = (Graphics2D) g;
g2draw.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2draw.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2draw.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2draw.setColor(Color.BLACK);
g2draw.fillRect(0, 0, getWidth(), getHeight());
Ellipse2D.Double e = new Ellipse2D.Double(300D - DOT_SIZE,
200D - DOT_SIZE, DOT_SIZE + DOT_SIZE, DOT_SIZE + DOT_SIZE);
g2draw.setColor(Color.YELLOW);
g2draw.fill(e);
}
}
private class DrawingUgly extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingUgly() {
this.setPreferredSize(new Dimension(600, 400));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
BufferedImage image = new BufferedImage(getWidth(), getHeight(),
BufferedImage.TYPE_INT_RGB);
Graphics2D ig = image.createGraphics();
ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
ig.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
ig.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
ig.setColor(Color.BLACK);
ig.fillRect(0, 0, getWidth(), getHeight());
Ellipse2D.Double e = new Ellipse2D.Double(300D - DOT_SIZE,
200D - DOT_SIZE, DOT_SIZE + DOT_SIZE, DOT_SIZE + DOT_SIZE);
ig.setColor(Color.YELLOW);
ig.fill(e);
ig.dispose();
g.drawImage(image, 0, 0, this);
}
}
}
This is simply because in one case, you're drawing on a hardware-supported surface that says it's 640x480 but rendering is done at 2x (or whatever scaling factor of your display) resolution. In the case of BufferedImage you're drawing onto a literal 640x480 pixel buffer. Obviously, that will look worse.
I think that the image and panel are using different rendering hints on OS X for hints you've not explicitly set. Copy/paste the textual output of this code back into the question.
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class QualityProblem {
private static final double DOT_SIZE = 40;
public static void main(String[] args) {
JFrame frame = new JFrame("ChartPanel demo");
frame.setLayout(new GridLayout(0, 1));
frame.getContentPane().add(new DrawingUgly());
frame.getContentPane().add(new DrawingOK());
frame.setSize(new Dimension(400, 300));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static class DrawingOK extends JPanel {
private static final long serialVersionUID = 1L;
DrawingOK() {
setBackground(Color.GREEN);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2draw = (Graphics2D) g.create();
System.out.println("Panel Rendering Hints:");
printRenderingHints(g2draw);
try {
g2draw.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2draw.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2draw.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
Ellipse2D.Double e = new Ellipse2D.Double(50, 50, DOT_SIZE, DOT_SIZE);
g2draw.setColor(Color.YELLOW);
g2draw.fill(e);
} finally {
g2draw.dispose();
}
}
}
private static class DrawingUgly extends JPanel {
private static final long serialVersionUID = 1L;
DrawingUgly() {
setBackground(Color.RED);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension size = getParent().getSize();
BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D ig = image.createGraphics();
System.out.println("Image Rendering Hints:");
printRenderingHints(ig);
ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
ig.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
ig.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
Graphics2D g2draw = (Graphics2D) g.create();
try {
Ellipse2D.Double e = new Ellipse2D.Double(50, 50, DOT_SIZE, DOT_SIZE);
ig.setColor(Color.YELLOW);
ig.fill(e);
g2draw.drawImage(image, 0, 0, null);
} finally {
ig.dispose();
g2draw.dispose();
}
}
}
private static void printRenderingHints(Graphics2D g) {
RenderingHints renderingHints = g.getRenderingHints();
RenderingHints.Key[] renderHintsKeys = {
RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.KEY_ANTIALIASING,
RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.KEY_DITHERING,
RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.KEY_INTERPOLATION,
RenderingHints.KEY_RENDERING,
RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.KEY_TEXT_LCD_CONTRAST
};
for (RenderingHints.Key key : renderHintsKeys) {
Object o = renderingHints.get(key);
String value = o==null ? "null" : o.toString();
System.out.println(key + " \t" + value);
}
}
}
Note that on Windows it produces an identical list of values.
HaraldK in one comments below question gave really good advice. BufferedImage size needs to be multiplied by 2, Graphics2D for that image must be set with scale 2 and target Graphics2D (of the screen device) needs to be scaled with 0.5.
With those settings both circles look exactly the same when zoomed in.
Bellow complete, modified DrawingUgly class.
private static class DrawingUgly extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingUgly() {
this.setPreferredSize(new Dimension(600, 25));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2draw = (Graphics2D) g.create();
double scale = 2;
BufferedImage image = new BufferedImage((int) (getWidth() * scale), (int) (getHeight() * scale), BufferedImage.TYPE_INT_RGB);
Graphics2D ig = image.createGraphics();
ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
ig.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
ig.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
ig.scale(scale, scale);
ig.setColor(Color.BLACK);
ig.fillRect(0, 0, getWidth(), getHeight());
Ellipse2D.Double e = new Ellipse2D.Double(10, 10, DOT_SIZE, DOT_SIZE);
ig.setColor(Color.YELLOW);
ig.fill(e);
ig.dispose();
g2draw.scale(1.0d / scale, 1.0d / scale);
g2draw.drawImage(image, 0, 0, this);
}
}
I am working on a class that can rotate a wheel around the center. The wheel is created using graphics2d, but I can not figure out exactly how to get the wheel to rotate around the center.
Currently, the wheel rotates, but not exactly about the origin.
My ultimate goal here is to create the wheel so that it is multicolored as well as a program around it, but my main concern here is getting the rotating wheel to work. If you could point me in the right direction I would be forever grateful!
Here is my current code:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;
public class RotateApp {
private static final int N = 3;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(N, N, N, N));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new RotatePanel());
frame.pack();
frame.setVisible(true);
System.out.println();
}
});
}
}
class RotatePanel extends JPanel implements ActionListener {
private static final int SIZE = 256;
private static double DELTA_THETA = Math.PI / 90;
private final Timer timer = new Timer(25, this);
private Image image = RotatableImage.getImage(SIZE);
private double dt = DELTA_THETA;
private double theta;
public RotatePanel() {
this.setBackground(Color.lightGray);
this.setPreferredSize(new Dimension(
image.getWidth(null), image.getHeight(null)));
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
image = RotatableImage.getImage(SIZE);
dt = -dt;
}
});
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
g2d.rotate(theta);
g2d.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);
g2d.drawImage(image, 0, 0, null);
}
public void actionPerformed(ActionEvent e) {
theta += dt;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
}
class RotatableImage {
private static final Random r = new Random();
static public Image getImage(int size) {
BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.getHSBColor(r.nextFloat(), 1, 1));
g2d.setStroke(new BasicStroke(10.0f));
g2d.draw(new Line2D.Double(0, 100, 100, 100));
g2d.draw(new Line2D.Double(100, 100, 200, 100));
g2d.draw(new Line2D.Double(100, 0, 100, 100));
g2d.draw(new Line2D.Double(100, 100, 100, 200));
g2d.draw(new Line2D.Double(25, 25, 100, 100));
g2d.draw(new Line2D.Double(100, 100, 175, 175));
g2d.draw(new Line2D.Double(175, 25, 100, 100));
g2d.draw(new Line2D.Double(100, 100, 25, 175));
g2d.draw(new Ellipse2D.Double(0, 0, 200, 200));
g2d.dispose();
return bi;
}
}
You can use the Rotated Icon class to do the rotation for you so you don't have to worry about all the rotation logic and the rotation logic is in a reusable class.
An example of using this class would be:
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation4 extends JPanel
{
private JLabel label;
private RotatedIcon rotated;
private int degrees;
public Rotation4(Image image)
{
setLayout( new GridBagLayout() );
Icon icon = new ImageIcon( image );
rotated = new RotatedIcon(icon, 0);
rotated.setCircularIcon(true);
label = new JLabel(rotated);
label.setOpaque(true);
label.setBackground(Color.RED);
add(label, new GridBagConstraints());
setDegrees( 0 );
}
public void setDegrees(int degrees)
{
this.degrees = degrees;
rotated.setDegrees( degrees );
label.revalidate();
label.repaint();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
Image bi = RotatableImage.getImage(210);
final Rotation4 r = new Rotation4(bi);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
r.setDegrees( value );
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(r));
f.add(slider, BorderLayout.SOUTH);
f.setSize(400, 400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
static class RotatableImage
{
private static final Random r = new Random();
static public Image getImage(int size)
{
BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.getHSBColor(r.nextFloat(), 1, 1));
g2d.setStroke(new BasicStroke(10.0f));
g2d.draw(new Line2D.Double(5, 105, 205, 105));
g2d.draw(new Line2D.Double(105, 5, 105, 205));
g2d.draw(new Line2D.Double(35, 35, 175, 175));
g2d.draw(new Line2D.Double(175, 35, 35, 175));
g2d.draw(new Ellipse2D.Double(5, 5, 199, 199));
g2d.setColor(Color.BLACK);
g2d.fillOval(100, 100, 10, 10);
g2d.dispose();
return bi;
}
}
}
Note I also had to make changes with your image and your painting. These changes will need to be made whether you use the RotatedIcon or do the rotation code yourself:
The image size was changed to 210. This is because your stroke size is 10, so you need to account for the extra pixels in the circle outline.
You need to change the original of the circle by half the stroke size. So in this case the origin becomes (5, 5).
The size of the oval needs to be changed to 199. This is because of the way the outline of the oval is painted. 1 extra pixel is needed for the outline. If you leave the size at 200 then 1 pixel of the outline will be lost. This is not very noticeable when using a stroke size of 10, but if you use a size of 1, then the outline will be missing at the right and bottom edges.
The locations of your lines needs to be changes. you don't want the line right to the edge of the circle because then you will get a flat line at the edge instead of the rounded line. So I started the line 5 pixels from the start and ended it 5 pixels from the end.
Ok, with little modification and fewer "spokes", I got your wheel rotate centric (1.) and multicolor (2.):
UPDATE on 1.) To make centric rotation in your (original) code just chage SIZEto 200!
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;
public class RotateApp {
private static final int N = 3;
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(N, N, N, N));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new RotatePanel());
frame.pack();
frame.setVisible(true);
System.out.println();
});
}
}
class RotatePanel extends JPanel implements ActionListener {
private static final int SIZE = 256;
private static final double DELTA_THETA = Math.PI / 90;
private final Timer timer = new Timer(25, this);
private Image image = RotatableImage.getImage(SIZE);
private double dt = DELTA_THETA;
private double theta;
public RotatePanel() {
this.setBackground(Color.lightGray);
this.setPreferredSize(new Dimension(SIZE, SIZE));
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
dt = -dt;
image = RotatableImage.getImage(SIZE);
}
});
timer.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.rotate(theta,128,128);
g2d.drawImage(image, 0, 0, null);
}
#Override
public void actionPerformed(ActionEvent e) {
theta += dt;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
}
class RotatableImage {
private static final Random r = new Random();
static public Image getImage(int size) {
BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
final Color c1 = Color.getHSBColor(r.nextFloat(), 1, 1);
final Color c2 = Color.getHSBColor(r.nextFloat(), 1, 1);
g2d.setPaint(c1);
g2d.setStroke(new BasicStroke(10.0f));
g2d.draw(new Line2D.Double(0, size/2, size, size/2));
g2d.setPaint(c2);
g2d.draw(new Line2D.Double(size/2, 0, size/2, size));
g2d.setPaint(c1);
g2d.draw(new Ellipse2D.Double(0, 0, size, size));
g2d.dispose();
return bi;
}
}
Explanation:
So the "wobble" in your solution came from the fact, that you sized the image and the container/panel 256x256, but "based" your wheel layout on 200x200 only. I fixed all dimensions, and drew a correct cross, the g2d.rotate(theta,128,128); (!) relates then to the center.
Multi-color(easy): You can invoke setPaint() between each shape! ;)
So, I have a JPanel where I draw a triangle. My intent is to turn the triangle to arbitrary angles that the user chooses. Now in order to be able to rotate the triangle without having it looking cropped I need a JPanel bigger than the triangle. So to achieve all this, my paintcomponent looks like this:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Path2D p = new Path2D.Float();
p.moveTo(getWidth() / 4, getHeight() / 4);
p.lineTo(getWidth()-(getWidth() / 4), getHeight() / 2);
p.lineTo(getWidth() / 4, getHeight()-(getHeight() / 4));
p.closePath();
Graphics2D g2d = (Graphics2D) g.create();
g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
g2d.rotate(Math.toRadians(rotationAngle), getWidth() / 2, getHeight() / 2);
g2d.fill(p);
g2d.dispose();
}
It works, but not how I would like it. Right now, it paints a green triangle over a transparent JPanel. The issue is that I want to keep the transparency on the JPanes when rotating the triangle. I know that I'm supposed to clear the contents of the JPanel if I want to redraw the JPanel and not end up with the old and new content, but all the responses I've seen ask to use clearRect which doesn't work here. clearRect will paint with the background color making the JPanel opaque. Can't I reinitialize the graphics component and draw again?
Right now, trying to set the background with something like
g2d.setBackground(new Color(0,0,0,0));
g2d.clearRect(0,0, getWidth(), getHeight());
Ends up just making a black background and the thing that seems more promising is maybe something like:
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 0.3f));
But I don't know how to use the composite options and I keep making the triangle transparent and not the background
This should create the version with the clearRect() and the black background:
package clicknturn;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ClickNTurn extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ClickNTurn ex = new ClickNTurn();
ex.setVisible(true);
}
});
}
public ClickNTurn() {
setTitle("Simple example");
setSize(500, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
Entry tmpEntry = new Entry();
JPanel container = new JPanel();
container.setBackground(Color.GRAY);
container.setLayout(null);
this.add(container);
container.add(new Entry());
}
}
class Entry extends JPanel{
private int rotationAngle;
public Entry(){
this.setBounds(10,10, 200, 200);
this.setSize(200,200);
Entry me = this;
rotationAngle = 0;
setLayout(new GridBagLayout());
setOpaque(true);
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
rotationAngle += 10;
me.repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Path2D p = new Path2D.Float();
p.moveTo(getWidth() / 4, getHeight() / 4);
p.lineTo(getWidth()-(getWidth() / 4), getHeight() / 2);
p.lineTo(getWidth() / 4, getHeight()-(getHeight() / 4));
p.closePath();
Graphics2D g2d = (Graphics2D) g.create();
g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
g2d.setBackground(new Color(0,0,0,0));
g2d.clearRect(0,0, getWidth(), getHeight());
g2d.setBackground(null);
g2d.rotate(Math.toRadians(rotationAngle), getWidth() / 2, getHeight() / 2);
g2d.fill(p);
g2d.dispose();
}
}
Ty!
Axel
Did you try setting the background color to "null"?
g2d.setBackground(null);
As I mentioned in my comment, set opaque on your drawing JPanel to false appears to fix your problem.
This was the program that I created several hours ago to test it:
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ClickNTurn extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ClickNTurn ex = new ClickNTurn();
ex.setVisible(true);
}
});
}
public ClickNTurn() {
setTitle("Simple example");
setSize(500, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
Entry tmpEntry = new Entry();
JPanel container = new JPanel();
container.setBackground(Color.GRAY);
container.setLayout(null);
this.add(container);
container.add(new Entry());
}
}
class Entry extends JPanel {
private int rotationAngle;
public Entry() {
this.setBounds(10, 10, 200, 200);
this.setSize(200, 200);
// !! Entry me = this;
rotationAngle = 0;
setLayout(new GridBagLayout());
//!! setOpaque(true);
setOpaque(false); //!!
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
rotationAngle += 10;
// !! me.repaint();
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Path2D p = new Path2D.Float();
p.moveTo(getWidth() / 4, getHeight() / 4);
p.lineTo(getWidth() - (getWidth() / 4), getHeight() / 2);
p.lineTo(getWidth() / 4, getHeight() - (getHeight() / 4));
p.closePath();
Graphics2D g2d = (Graphics2D) g.create();
g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
// g2d.setBackground(new Color(0, 0, 0, 0));
// g2d.clearRect(0, 0, getWidth(), getHeight());
g2d.setBackground(null);
g2d.rotate(Math.toRadians(rotationAngle), getWidth() / 2, getHeight() / 2);
g2d.fill(p);
g2d.dispose();
}
}
Use setOpaque and pass it false, this will make the component transparent, as well as let the paint engine know that it needs to take special care painting it, like clearing the Graphics context properly and painting beneath it.
There is nothing special you need to do in your code, simply continue painting as you normally would and the API will take care of the rest
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JPanel;
public class ClickNTurn extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ClickNTurn ex = new ClickNTurn();
ex.setVisible(true);
}
});
}
public ClickNTurn() {
setTitle("Simple example");
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().setBackground(Color.RED);
add(new Entry());
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
class Entry extends JPanel {
private int rotationAngle;
public Entry() {
Entry me = this;
rotationAngle = 0;
setLayout(new GridBagLayout());
setOpaque(false);
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
rotationAngle += 10;
me.repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Path2D p = new Path2D.Float();
p.moveTo(getWidth() / 4, getHeight() / 4);
p.lineTo(getWidth() - (getWidth() / 4), getHeight() / 2);
p.lineTo(getWidth() / 4, getHeight() - (getHeight() / 4));
p.closePath();
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
g2d.rotate(Math.toRadians(rotationAngle), getWidth() / 2, getHeight() / 2);
g2d.fill(p);
g2d.dispose();
}
}
}
I want to set up a mathematical (where y grows up not down) coordinate space from (-1, -1) to (+1, +1) and have it fit in the window regardless of the window size.
I am using an anonymous JComponent subclass in Java SE 7 and casting the incoming Graphics in paintComponent to Graphics2D and then drawing on the Graphics2D
But the Graphics2D is set to a computer coordinate space that changes with the size of the window. How to get it to rescale according to window size and have Y go upwards? The following program should show a dark square in upper right quadrant.
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class G {
public static void main (String [] args) {
JFrame frame = new JFrame(G.class.getCanonicalName());
frame.setUndecorated(true);
JComponent component = new JComponent() {
private static final long serialVersionUID = 1L;
#Override
protected void paintComponent (Graphics g) {
super.paintComponent(g);
paint2D((Graphics2D)g);
}
protected void paint2D (Graphics2D g2) {
g2.draw(new Rectangle2D.Double(0.1, 0.1, 0.9, 0.9));
}
};
frame.add(component);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setVisible(true);
}
}
Setup the coordinate system how you want, using transform() and translate(). So:
you want the origin to be at (0, height); bottom left.
then you want to flip the Y axis.
Example code:
AffineTransform tform = AffineTransform.getTranslateInstance( 0, height);
tform.scale( 1, -1);
g2.setTransform( tform);
[My edited version]:
public static void main (String [] args) {
JFrame frame = new JFrame( G2dTransform_Question.class.getCanonicalName());
JComponent component = new JComponent() {
private static final long serialVersionUID = 1L;
#Override
protected void paintComponent (Graphics g) {
super.paintComponent(g);
paint2D((Graphics2D)g);
}
protected void paint2D (Graphics2D g2) {
AffineTransform tform = AffineTransform.getTranslateInstance( 0, getHeight());
tform.scale( getWidth(), -getHeight()); // NOTE -- to make 1.0 'full width'.
g2.setTransform( tform);
g2.setColor( Color.BLUE); // NOTE -- so we can *see* something.
g2.fill( new Rectangle2D.Double(0.1, 0.1, 0.8, 0.8)); // NOTE -- 'fill' works better than 'draw'.
}
};
frame.setLayout( new BorderLayout()); // NOTE -- make the component size to frame.
frame.add( component, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setVisible(true);
}
[Hovercraft's version]: Thanks Hover!
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class G {
public static final int PREF_W = 400;
public static final int PREF_H = PREF_W;
public static void main (String [] args) {
JFrame frame = new JFrame(G.class.getCanonicalName());
frame.setUndecorated(true);
JComponent component = new JComponent() {
private static final long serialVersionUID = 1L;
#Override
protected void paintComponent (Graphics g) {
super.paintComponent(g);
AffineTransform tform = AffineTransform.getTranslateInstance( 0, getHeight());
tform.scale( 1, -1);
Graphics2D g2 = (Graphics2D) g.create();
g2.setTransform( tform);
paint2D(g2);
g2.dispose();
}
protected void paint2D (Graphics2D g2) {
g2.draw(new Rectangle2D.Double(10, 10, 20, 30));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
};
frame.add(component);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
I am developing an app using Swing. While user interact with the app, help data may become available in a closed section of the screen, and I'd like to draw a small arrow pointing to that section when this happens.
To do this, I extended a JPanel and added it as the glasspane of the app jframe. The name of the custom glass pane class is AlertGlassPane.
The AlertGlassPane do this: waits until new help data is available. When this happens and the help section is closed, it finds the position of the help section on the screen and then draws an animated arrow at its side.
To draw the arrow I extended the method paintComponent of the glass pane.
To animate the arrow I created a small thread that loop every 100 ms, calling repaint on the glass pane.
The problem: Java ignores my drawing... if a trigger the start of the animation and stand still, nothing happens. No drawing is shown on the app.
I know that the loop is running and paintComponent is being called, but the custom paint is not taking effect.
But if I move the mouse over the region where the arrow should be rendered, it does! But just when the mouse is moving. If a stop move the mouse, the animation stops too, and the arrow freezes on the last position before the mouse stop (or leaves the area).
I tried a lot of things (as set the clip region of the drawing, for example), but nothing seems to work. The only thing that works was when I called by mistake repaint from inside the paintComponent.
At this moment I'm looking for a way to comunicate to the rendering system that a givem region of my glasspane needs to be repainted (repaint(x, y, w, h) didn't work...). Maybe moving the custom rendering to a JLabel and then adding this label to the glasspane? I don't like this approach...
I'll try to clean up the code before post a snippet here. Would it help?
Thanks in advance!
snnipet:
package br.com.r4j.test;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.image.BufferedImage;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.text.JTextComponent;
/**
*
* #author
*/
public class TestGlassPaneAnimation extends JPanel
{
private static TestGlassPaneAnimation gvp = new TestGlassPaneAnimation();
public static void main(String args[])
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame f = new JFrame("Anitest in glass");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//f.setResizable(false);
f.setLayout(new GridLayout(5, 3));
f.add(new JLabel("First Name :"));
f.add(new JTextField(20));
f.add(new JLabel("Last Name :"));
f.add(new JTextField(20));
f.add(new JLabel("Phone Number :"));
f.add(new JTextField(20));
f.add(new JLabel("Email:"));
f.add(new JTextField(20));
f.add(new JLabel("Address :"));
f.add(new JTextField(20));
JButton btnStart = new JButton("Click me, please!");
f.add(btnStart);
btnStart.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
gvp.startAnimation();
}
});
f.setGlassPane(gvp);
f.pack();
f.setVisible(true);
gvp.setVisible(true);
}
});
}
private BufferedImage icon;
private boolean animate = false;
private long timeStart = 0;
private Thread thrLastActive = null;
public TestGlassPaneAnimation()
{
setLayout(null);//this is the exception to the rule case a layoutmanager might make setting Jlabel co-ords harder
setOpaque(false);
Icon icon1 = UIManager.getIcon("OptionPane.warningIcon");
int imgW = icon1.getIconWidth();
int imgH = icon1.getIconHeight();
this.icon = ImageUtilities.getBufferedImageOfIcon(icon1, imgW, imgH);
this.animate = false;
}
public void startAnimation()
{
this.animate = true;
this.timeStart = (new Date()).getTime();
if (this.thrLastActive != null)
this.thrLastActive.interrupt();
this.thrLastActive = new Thread(new Runnable()
{
public void run()
{
try
{
while (true)
{
// int x = 250, y = 250;
// int width = 60, height = 60;
Thread.currentThread().sleep(100);
// frmRoot.invalidate();
// repaint(x, y, width, height);
repaint(new Rectangle(x, y, width, height));
// repaint(new Rectangle(10, 10, 2000, 2000));
// repaint();
// paintComponent(getGraphics());
}
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
this.thrLastActive.start();
}
protected void paintComponent(Graphics g)
{
try
{
// enables anti-aliasing
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
java.awt.Composite composite = g2.getComposite();
System.err.println("b1: " + g.getClipBounds());
if (this.animate)
{
long timeSpent = (new Date()).getTime() - timeStart;
int x = 10, y = 150;
int width = 60, height = 60;
float maxAlpha = 0.8f;
x += (-100*Math.sin(5*2*Math.PI*timeSpent/10000)+50)/15;
System.err.println("painting::x: " + x + ", y: " + y + ", sin: " + (Math.sin(6*2*Math.PI*timeSpent/10000)));
// g.setClip(x-10, y-10, width, height);
System.err.println("b2: " + g.getClipBounds());
AlphaComposite alpha2 = AlphaComposite.SrcOver.derive(maxAlpha);
g2.setComposite(alpha2);
g2.drawImage(this.icon, x, y, null);
g2.setComposite(composite);
g2.setComposite(composite);
}
}
catch (Throwable e)
{
System.err.println("Errr!");
e.printStackTrace();
}
}
}
class ImageUtilities {
public static BufferedImage resize(BufferedImage image, int width, int height) {
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TRANSLUCENT);
Graphics2D g2d = (Graphics2D) bi.createGraphics();
g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
g2d.drawImage(image, 0, 0, width, height, null);
g2d.dispose();
return bi;
}
public static BufferedImage getBufferedImageOfIcon(Icon icon, int imgW, int imgH) {
BufferedImage img = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) img.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
icon.paintIcon(null, g2d, 0, 0);
g2d.dispose();
return img;
}
}
Hmm not sure exactly whats going on with the snippet you gave but as it looks like it was taken from mine I wrote another example (I had to change a 1 or 2 lines of code in showWarningIcon(Component c) and refreshLocations() methods but nothing major:
If anything except david is typed and the button (click me, please) clicked it will show this:
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class TestGlassPaneAnimation {
private static GlassValidationPane gvp = new GlassValidationPane();
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame("Anitest in glass");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//f.setResizable(false);
f.setLayout(new GridBagLayout());
GridBagConstraints gc = new GridBagConstraints();
gc.fill = GridBagConstraints.HORIZONTAL;
gc.weightx = 1;
gc.weighty = 1;
gc.insets = new Insets(15, 15, 15, 15);//give some space so icon doesnt cover components when shown
gc.gridx = 0;
gc.gridy = 0;
f.add(new JLabel("First Name:"), gc);
final JTextField jtf = new JTextField(20);
gc.gridx = 1;
f.add(jtf, gc);
gc.gridx = 0;
gc.gridy = 1;
f.add(new JLabel("Surname:"), gc);
final JTextField jtf2 = new JTextField(20);
gc.gridx = 1;
f.add(jtf2, gc);
JButton btnStart = new JButton("Click me, please!");
gc.gridx = 2;
f.add(btnStart, gc);
btnStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!jtf.getText().equalsIgnoreCase("david")) {
gvp.showWarningIcon(jtf);
}
}
});
f.addComponentListener(new ComponentAdapter() {//so wjen frame is resized icons follow
#Override
public void componentResized(ComponentEvent ce) {
super.componentResized(ce);
gvp.refreshLocations();
}
});
f.setGlassPane(gvp);
f.pack();
f.setVisible(true);
gvp.setVisible(true);
}
});
}
}
class GlassValidationPane extends JPanel {
private HashMap<Component, JLabel> warningLabels = new HashMap<>();
private ImageIcon warningIcon;
public GlassValidationPane() {
setLayout(null);//this is the exception to the rule case a layoutmanager might make setting Jlabel co-ords harder
setOpaque(false);
Icon icon = UIManager.getIcon("OptionPane.warningIcon");
int imgW = icon.getIconWidth();
int imgH = icon.getIconHeight();
BufferedImage img = ImageUtilities.getBufferedImageOfIcon(icon, imgW, imgH);
warningIcon = new ImageIcon(ImageUtilities.resize(img, 24, 24));
}
void showWarningIcon(Component c) {
if (warningLabels.containsKey(c)) {
return;
}
JLabel label = new JLabel();
label.setIcon(warningIcon);
//int x=c.getX();//this will make it insode the component
int x = c.getX() - warningIcon.getIconWidth();//this makes it appear outside/next to component if space
int y = c.getY();
label.setBounds(x, y, warningIcon.getIconWidth(), warningIcon.getIconHeight());
add(label);
revalidate();
repaint();
warningLabels.put(c, label);
}
public void removeWarningIcon(Component c) {
for (Map.Entry<Component, JLabel> entry : warningLabels.entrySet()) {
Component component = entry.getKey();
JLabel jLabel = entry.getValue();
if (component == c) {
remove(jLabel);
revalidate();
repaint();
break;
}
}
warningLabels.remove(c);
}
public void refreshLocations() {
for (Map.Entry<Component, JLabel> entry : warningLabels.entrySet()) {
Component c = entry.getKey();
JLabel label = entry.getValue();
//int x=c.getX();//this will make it insode the component
int x = c.getX() - label.getIcon().getIconWidth();//this makes it appear outside/next to component
int y = c.getY();
label.setBounds(x, y, label.getIcon().getIconWidth(), label.getIcon().getIconHeight());
revalidate();
repaint();
}
}
}
class ImageUtilities {
public static BufferedImage resize(BufferedImage image, int width, int height) {
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TRANSLUCENT);
Graphics2D g2d = (Graphics2D) bi.createGraphics();
g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
g2d.drawImage(image, 0, 0, width, height, null);
g2d.dispose();
return bi;
}
public static BufferedImage getBufferedImageOfIcon(Icon icon, int imgW, int imgH) {
BufferedImage img = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) img.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
icon.paintIcon(null, g2d, 0, 0);
g2d.dispose();
return img;
}
}
If you are looking for a more mature library have a look at JXLayer - Validation Overlays and Validation overlays using glass pane.
Also might want to have a read here which shows many ways of validating a textfields data (other than button press) like DocumentFilter and InputVerifier etc.