Question:
What does calling setVisible(true) on a JFrame which is already visible do? I was digging through the source code of JFrame, and ultimately, it boils down to this function in Component which does nothing to a frame if it is already visible. Why does it act like revalidate(); repaint();? (See SSCCE below)
Motivation:
I am working on a java app, for which I wrote a class JImagePanel which extends JPanel and allows the user to set an image as the background (see SSCCE). I have found that after editing the background of the panel, I was having issues repainting the background to the correct size. After scouring the internet, I found that the following works:
if(frame.isVisible()) frame.setVisible(true);
Ultimately, I solved the issue using
panel.revalidate();
panel.repaint();
, which I think is the better solution, but it got me to thinking what setVisible(true) actually does on an already visible frame. From my viewpoint, it shouldn't work - but in fact it does.
SSCCE
Here is an example that illustrates my issue. If nothing else, hopefully you find this class extremely useful in the future.
NOTE: Updated source of this file can be found on the project homepage on GitHub of the project this was created for.
Enjoy!
package com.dberm22.utils;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class JImagePanel extends JPanel {
private static final long serialVersionUID = 6841876236948317038L;
private Image img = null;
private Position position = Position.CENTER;
public enum Position{
STRETCH,
CENTER,
FIT,
FILL,
NONE;
}
public JImagePanel() {
}
public JImagePanel(String img) {
this(new ImageIcon(img).getImage());
}
public JImagePanel(Image img) {
setBackgroundImage(img);
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(getBackground());
g2.fillRect(0, 0, getWidth(), getHeight());
if (this.position.equals(Position.STRETCH))
{
if(this.img != null) g2.drawImage(img, 0, 0, getWidth(), getHeight(), null);
}
else if (this.position.equals(Position.FILL) || this.position.equals(Position.FIT))
{
if(this.img != null)
{
double scaleFactor = getScaleFactor(new Dimension(img.getWidth(null), img.getHeight(null)), getSize());
int scaleWidth = (int) Math.round(img.getWidth(null) * scaleFactor);
int scaleHeight = (int) Math.round(img.getHeight(null) * scaleFactor);
//Image img_scaled = img.getScaledInstance(scaleWidth, scaleHeight, Image.SCALE_SMOOTH);
g2.drawImage(scaleImage(img, scaleWidth, scaleHeight, getBackground()), (getWidth() - scaleWidth)/2, (getHeight() - scaleHeight)/2, scaleWidth, scaleHeight, null);
}
}
else if (this.position.equals(Position.CENTER)) { if(this.img != null) g2.drawImage(img, (getWidth() - img.getWidth(null))/2, (getHeight() - img.getHeight(null))/2, null); }
}
public void setBackgroundImage(String img)
{
setBackgroundImage(new ImageIcon(img).getImage());
}
public void setBackgroundImage(Image img)
{
this.img = img;
Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
setSize(size);
repaint();
}
public static double getScaleFactor(int iMasterSize, int iTargetSize) {
double dScale = 1;
if (iMasterSize > iTargetSize) {
dScale = (double) iTargetSize / (double) iMasterSize;
} else {
dScale = (double) iTargetSize / (double) iMasterSize;
}
return dScale;
}
public double getScaleFactor(Dimension original, Dimension targetSize) {
double dScale = 1d;
if (original != null && targetSize != null) {
double dScaleWidth = getScaleFactor(original.width, targetSize.width);
double dScaleHeight = getScaleFactor(original.height, targetSize.height);
if (this.position.equals(Position.FIT)) dScale = Math.min(dScaleHeight, dScaleWidth);
else if(this.position.equals(Position.FILL)) dScale = Math.max(dScaleHeight, dScaleWidth);
}
return dScale;
}
public BufferedImage scaleImage(Image img, int width, int height, Color background) {
BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = newImage.createGraphics();
try {
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setBackground(background);
g.clearRect(0, 0, width, height);
g.drawImage(img, 0, 0, width, height, null);
} finally {
g.dispose();
}
return newImage;
}
public void setBackgroundImagePosition(String pos)
{
if("Stretch".equals(pos)) setBackgroundImagePosition(Position.STRETCH);
else if("Center".equals(pos)) setBackgroundImagePosition(Position.CENTER);
else if("Fit".equals(pos)) setBackgroundImagePosition(Position.FIT);
else if("Fill".equals(pos)) setBackgroundImagePosition(Position.FILL);
else if("None".equals(pos)) setBackgroundImagePosition(Position.NONE);
}
public void setBackgroundImagePosition(Position pos)
{
this.position = pos;
repaint();
}
public static void main(String[] args)
{
JFrame frame = new JFrame("JImagePanel Test");
frame.setSize( Toolkit.getDefaultToolkit().getScreenSize());
frame.setPreferredSize( Toolkit.getDefaultToolkit().getScreenSize());
frame.setExtendedState(JFrame.MAXIMIZED_BOTH); //sets appropriate size for frame
JImagePanel panel = new JImagePanel();
frame.add(panel);
frame.setVisible(true);
try {Thread.sleep(2000);} catch (InterruptedException e) {}
panel.setBackgroundImage("C:\\Users\\David\\Pictures\\Wood.jpg");
panel.setBackgroundImagePosition(JImagePanel.Position.STRETCH);
panel.revalidate(); // need to revalidate()
panel.repaint(); //doesnt work by itself
try {Thread.sleep(2000);} catch (InterruptedException e) {}
panel.setBackgroundImage("C:\\Users\\David\\Pictures\\Wood.jpg");
panel.setBackgroundImagePosition(JImagePanel.Position.FIT);
frame.setVisible(true); //also works --why?
}
}
Calling setVisible(true) on a JFrame which is already visible works for you because this ends up calling validate() internally, which in turn revalidates all subcomponents in the frame.
To see why, refer to the implementation of Component.setVisible(boolean b):
public void setVisible(boolean b) {
show(b);
}
public void show(boolean b) {
if (b) {
show();
} else {
hide();
}
}
But the show() method is overriden in Window (of which JFrame is a subclass). So this ends up calling Window.show():
public void show() {
if (peer == null) {
addNotify();
}
validate();
[...]
Hope this explains the behaviour you are seeing.
Assuming you are after a half-way clean implementation of your imagePanel:
let the panel do its own revalidation/-paint as need (vs application code)
do not call setXXSize, ever, not even internally in the panel
That's change your setters and override the getXXSize. Note that basing the sizing hints on the image size alone is not what you would want to do in real world code, you probably want to take super's hint into account as well (f.i. if the panel has children)
#Override
public Dimension getPreferredSize() {
if (img != null) {
return new Dimension(img.getWidth(this), img.getHeight(this));
}
return super.getPreferredSize();
}
public void setBackgroundImage(Image img) {
this.img = img;
// need to revalidate as our sizing hints might have changed
revalidate();
repaint();
}
public void setBackgroundImagePosition(Position pos) {
this.position = pos;
repaint();
}
Application code that uses it now simply calls the setters, nothing else:
JFrame frame = new JFrame("JImagePanel Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// frame.setLayout(new FlowLayout()); // just to see the effect of a pref-respecting layout
final JImagePanel panel = new JImagePanel();
frame.add(panel);
final Image[] images = new Image[]{
XTestUtils.loadDefaultImage(), XTestUtils.loadDefaultImage("500by500.png"), null};
Action toggleImage = new AbstractAction("toggle image") {
int index = 0;
#Override
public void actionPerformed(ActionEvent e) {
panel.setBackgroundImage(images[index]);
index = (index +1) % images.length;
}
};
Action togglePosition = new AbstractAction("toggle position") {
int index = 0;
#Override
public void actionPerformed(ActionEvent e) {
panel.setBackgroundImagePosition(Position.values()[index]);
index = (index +1) % Position.values().length;
}
};
frame.add(new JButton(toggleImage), BorderLayout.NORTH);
frame.add(new JButton(togglePosition), BorderLayout.SOUTH);
// size for frame
//frame.setSize(800, 800);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH); //sets appropriate
frame.setVisible(true);
Related
I have a circular JButton constructed using a circular PNG image with a transparent area.
I want to fill the transparent area of the JButton image with a given colour - but something other than the opaque background colour of the JPanel which contains the JButton. I want to do this programmatically in Java rather than providing pre-coloured images from a graphics package.
I've got as far as the code below, which simply allows the orange background of the opaque panel to colour the transparent area. I can't figure out, though, how to keep the panel background as orange but fill the image transparency with, say, blue (or another colour for rollover and pressed effects).
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.DefaultButtonModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class App extends JFrame implements ActionListener
{
public App()
{
super();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setContentPane(makeContentPane());
}
private final JPanel makeContentPane()
{
JPanel contentPane = new JPanel();
BufferedImage bi = null;
try
{
bi = ImageIO.read(new URL("http://features.advisorwebsites.com/sites/default/files/users/AdvisorWebsitesFeatures/icones/1384148625_twitter_circle_gray.png"));
}
catch (IOException e)
{
e.printStackTrace();
}
ImageIcon icon = new ImageIcon(bi);
MyButton myButton = new MyButton(icon);
myButton.addActionListener(this);
contentPane.add(myButton);
contentPane.setBackground(Color.ORANGE);
contentPane.setOpaque(true);
return contentPane;
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
App app = new App();
app.pack();
app.setVisible(true);
}
});
}
class MyButton extends JButton
{
public MyButton(ImageIcon icon)
{
super(icon);
setMargin(new Insets(0, 0, 0, 0));
setFocusable(false);
setContentAreaFilled(false);
setBorderPainted(false);
setModel(new DefaultButtonModel());
setCursor(new Cursor(Cursor.HAND_CURSOR));
}
}
#Override
public void actionPerformed(ActionEvent arg0)
{
System.out.println("You clicked me");
}
}
I'm guessing I might need to apply Graphics2D transformations to my transparent image to create a set of three new images (for the normal, rollover and pressed states of my JButton). Is this the appropriate way forward and, if so, can you provide me with a code example for the bit I'm missing ?
Thanks
try to override paintComponent() in your custom JButton
Here's what I tried
class MyButton extends JButton {
public MyButton(ImageIcon icon) {
super(icon);
setMargin(new Insets(0, 0, 0, 0));
setFocusable(false);
setContentAreaFilled(false);
setBorderPainted(false);
setModel(new DefaultButtonModel());
setCursor(new Cursor(Cursor.HAND_CURSOR));
}
public void paintComponent(Graphics g) {
g.setColor(Color.green);
g.fillOval(0, 0, this.getWidth(), this.getHeight());
super.paintComponent(g);
}
}
And here's the result:
EDIT:
To get it to change color depending on mouse movement, you need to add a MouseListener to the JButton and add a Color attribute to the class, when a MouseEvent is fired you change the color. Also don't forget to set the graphics to that color in paintComponent()
class MyButton extends JButton {
Color color = Color.GREEN;
public MyButton(ImageIcon icon) {
super(icon);
setMargin(new Insets(0, 0, 0, 0));
setFocusable(false);
setContentAreaFilled(false);
setBorderPainted(false);
setModel(new DefaultButtonModel());
setCursor(new Cursor(Cursor.HAND_CURSOR));
this.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
color=Color.RED;
}
#Override
public void mouseReleased(MouseEvent e) {
color=Color.BLUE;
}
#Override
public void mouseEntered(MouseEvent e) {
color=Color.BLUE;
}
#Override
public void mouseExited(MouseEvent e) {
color=Color.GREEN;
}
});
}
#Override
public void paintComponent(Graphics g) {
g.setColor(color);
g.fillOval(0, 0, this.getWidth(), this.getHeight());
super.paintComponent(g);
}
}
Okay, this is little more then you're asking for, but it demonstrates a means by which you can generate a shape which matches the shape of a image, based on its alpha, all through the magic of AlphaComposite...
So, basically, we're going to take the original image on the left and turn into the image on the right...
First, we load the original image
Next, we create a mask of the shape we want to achieve (a circle in this case)
Next, we mask the two images together, so that the original image is cropped into the mask (the second)
Then we create a new mask, which is filled with the color we want to be used as the outline
We then mask the first masked image with the "outline" image, this basically acts as a cookie cutter, cutting the shape of the first masked imaged with the "block" image.
We take this "outlined" shape and then scale it up slightly
And finally, we combine them.
This example generates two images, a "normal" and a "roll over" which is easily applied to a JButton, for example...
Normal...
Roll over...
Now, if for some reason, you wanted to know when the button was "rolled over", you could simply add a ChangeListener to the ButtonModel, for example...
JButton btn = new JButton(new ImageIcon(normal));
btn.setRolloverIcon(new ImageIcon(rollOver));
btn.setRolloverEnabled(true);
btn.getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
ButtonModel model = (ButtonModel) e.getSource();
System.out.println("Change: " + model.isRollover());
}
});
And the runnable example...
import java.awt.AlphaComposite;
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.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ButtonModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class JavaApplication24 {
public static void main(String[] args) {
new JavaApplication24();
}
public JavaApplication24() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(JavaApplication24.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
public TestPane() throws IOException {
setLayout(new GridBagLayout());
BufferedImage source = ImageIO.read(...));
// This the shape we want the source to be clipped to
int size = Math.min(source.getWidth(), source.getHeight());
BufferedImage mask = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D maskg = mask.createGraphics();
applyQualityRenderingHints(maskg);
maskg.setColor(Color.WHITE);
maskg.fillOval((source.getWidth() - size) / 2, (source.getHeight() - size) / 2, size, size);
maskg.dispose();
// This will mask the source to the shape we've defined
BufferedImage masked = applyMask(source, mask, AlphaComposite.DST_ATOP);
BufferedImage normal = makeOutline(masked, Color.BLACK);
BufferedImage rollOver = makeOutline(masked, Color.RED);
JButton btn = new JButton(new ImageIcon(normal));
btn.setRolloverIcon(new ImageIcon(rollOver));
btn.setRolloverEnabled(true);
btn.getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
ButtonModel model = (ButtonModel) e.getSource();
System.out.println("Change: " + model.isRollover());
}
});
add(btn);
}
protected BufferedImage makeOutline(BufferedImage original, Color color) {
// This generates a image which is completely filled with the provided color
BufferedImage outline = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D outlineg = outline.createGraphics();
applyQualityRenderingHints(outlineg);
outlineg.setColor(color);
outlineg.fillRect(0, 0, outline.getWidth(), outline.getHeight());
outlineg.dispose();
// This applies a AlphaComposite to mask the outline with the shape
// of the original image
outline = applyMask(original, outline, AlphaComposite.SRC_ATOP);
// Now we make it slightly larger...
double scale = 1.05;
outline = getScaledInstanceToFit(outline, scale);
// And we combine the images
outlineg = outline.createGraphics();
int x = (outline.getWidth() - original.getWidth()) / 2;
int y = (outline.getHeight() - original.getHeight()) / 2;
outlineg.drawImage(original, x, y, this);
outlineg.dispose();
return outline;
}
public BufferedImage applyMask(BufferedImage sourceImage, BufferedImage maskImage) {
return applyMask(sourceImage, maskImage, AlphaComposite.DST_IN);
}
public BufferedImage applyMask(BufferedImage sourceImage, BufferedImage maskImage, int method) {
BufferedImage maskedImage = null;
if (sourceImage != null) {
int width = maskImage.getWidth(null);
int height = maskImage.getHeight(null);
maskedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D mg = maskedImage.createGraphics();
applyQualityRenderingHints(mg);
int x = (width - sourceImage.getWidth(null)) / 2;
int y = (height - sourceImage.getHeight(null)) / 2;
mg.drawImage(sourceImage, x, y, null);
mg.setComposite(AlphaComposite.getInstance(method));
mg.drawImage(maskImage, 0, 0, null);
mg.dispose();
}
return maskedImage;
}
public void applyQualityRenderingHints(Graphics2D g2d) {
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.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
public BufferedImage getScaledInstanceToFit(BufferedImage img, double scale) {
int width = (int)(img.getWidth() * scale);
int height = (int)(img.getHeight()* scale);
return getScaledInstanceToFit(img, new Dimension(width, height));
}
public BufferedImage getScaledInstanceToFit(BufferedImage img, Dimension size) {
double scaleFactor = getScaleFactorToFit(new Dimension(img.getWidth(), img.getHeight()), size);
return getScaledInstance(img, scaleFactor);
}
public double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
public double getScaleFactor(int iMasterSize, int iTargetSize) {
return (double) iTargetSize / (double) iMasterSize;
}
public BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
return getScaledInstance(img, dScaleFactor, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
}
protected BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor, Object hint, boolean bHighQuality) {
BufferedImage imgScale = img;
int iImageWidth = (int) Math.round(img.getWidth() * dScaleFactor);
int iImageHeight = (int) Math.round(img.getHeight() * dScaleFactor);
if (dScaleFactor <= 1.0d) {
imgScale = getScaledDownInstance(img, iImageWidth, iImageHeight, hint, bHighQuality);
} else {
imgScale = getScaledUpInstance(img, iImageWidth, iImageHeight, hint, bHighQuality);
}
return imgScale;
}
protected BufferedImage getScaledDownInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint,
boolean higherQuality) {
int type = (img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
if (targetHeight > 0 || targetWidth > 0) {
int w, h;
if (higherQuality) {
w = img.getWidth();
h = img.getHeight();
} else {
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
} else {
ret = new BufferedImage(1, 1, type);
}
return ret;
}
protected BufferedImage getScaledUpInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint,
boolean higherQuality) {
int type = BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
int w, h;
if (higherQuality) {
w = img.getWidth();
h = img.getHeight();
} else {
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w < targetWidth) {
w *= 2;
if (w > targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h < targetHeight) {
h *= 2;
if (h > targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
tmp = null;
} while (w != targetWidth || h != targetHeight);
return ret;
}
}
}
This code borrows heavily from my personal library code, so it might be a little convoluted ;)
I have a JScrollPane implemented like so in the frame:
public ImagePanel imgPane = new ImagePanel();
JScrollPane scrollPane = new JScrollPane(imgPane);
ImagePanel is an extension of JPanel:
public class ImagePanel extends JPanel {
private BufferedImage img;
public ImagePanel(BufferedImage img) {
this.img = img;
}
public ImagePanel() {
}
#Override
public Dimension getPreferredSize() {
return img == null ? super.getPreferredSize() : new Dimension(img.getWidth(), img.getHeight());
}
protected Point getImageLocation() {
Point p = null;
if (img != null) {
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
p = new Point(x, y);
}
return p;
}
public void setImage(BufferedImage bi) {
img = bi;
}
public Point toImageContext(Point p) {
Point imgLocation = getImageLocation();
Point relative = new Point(p);
relative.x -= imgLocation.x;
relative.y -= imgLocation.y;
return relative;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Point p = getImageLocation();
g.drawImage(img, p.x, p.y, this);
}
}
The trouble I'm having is, when I load an image into the Pane:
frame.imgPane.setImage(img);
unless the previous image was the same size, the pane appears blank, no image, until you scroll. imgPane.repaint() has no effect on this. Is there a way to cause the JScrollPane to adjust to the new image without the user having to scroll?
Thanks
The setImage(...) method should also invoke:
img = bi;
revalidate(); // to invoke the layout manager
repaint(); // paint itself
Aim: to place JButton on a top of an image that is loaded from a different class.
Problem: only JButton or image can be displayed
//main class
JPanel mPanel.add(new JButton("ddd") );
mPanel.add(drawbg);
class DrawBg extends JPanel {
public Dimension getPreferredSize() {
return new Dimension(1280, 720);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
// background
Image img = new ImageIcon(getClass().getResource("/images/test_bg.jpg")).getImage();
int imgX = img.getWidth(null);
int imgY = img.getHeight(null);
g.drawImage(img, (getWidth() - imgX), (getHeight() - imgY), imgX, imgY, null);
}
Edit Ah yes, as ControlAltDel points out, you're not adding your JButton to your drawing JPanel instance. Just do that, and your problem is solved.
I don't see the bug in your code, so it might lie in code not shown. I do see a problem in that you're reading the image in, inside of paintComponent, something that should never be done as it will slow down painting and thus slow down the perceived responsiveness of your program. Also, why re-read in the image with each paint. Instead read it in once.
For example:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ButtonOnImg extends JPanel {
public static final String IMG_PATH = "https://duke.kenai.com/gun/Gun.jpg";
private BufferedImage img;
public ButtonOnImg() throws IOException {
URL url = new URL(IMG_PATH);
img = ImageIO.read(url);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, 0, 0, null);
}
}
#Override
public Dimension getPreferredSize() {
if (img == null) {
return super.getPreferredSize();
} else {
int w = img.getWidth();
int h = img.getHeight();
return new Dimension(w, h);
}
}
private static void createAndShowGui() {
try {
ButtonOnImg mainPanel = new ButtonOnImg();
mainPanel.add(new JButton("Button!"));
JFrame frame = new JFrame("ButtonOnImg");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Which displays as:
Hi so I'm writing an simple physics engine to understand object collisions and Java graphics a bit better, and so far I have successfully written the code to add the JPanel to the JFrame and allow them to show up somewhat the correct size, but when I view the actually program, the JPanel seems tobe the right size but it does not start in the upper corner of the window, but rather the upper left of the frame. I seem to have this problem alot where I want something to be at (0, 0) and starts in the upper corner of the frame rather than the panel. Here is my code:
I have an engine class that extends JFrame and contains the main method -->
package io.shparki.PhysicsEngine;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
public class Engine extends JFrame{
public Engine(){
super("Physics Engine and Simulator");
setLayout(new BorderLayout());
add(new EnginePanel(), BorderLayout.CENTER);
pack();
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args){
new Engine();
}
}
and this is my second class, the EnginePanel which extends JPanel and implements Runnable -->
package io.shparki.PhysicsEngine;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import javax.swing.JPanel;
public class EnginePanel extends JPanel implements Runnable{
private static final int WIDTH = 300;
private static final int HEIGHT = WIDTH / 16 * 9;
private static final int SCALE = 4;
public int getWidth() { return WIDTH * SCALE; }
public int getHeight() { return HEIGHT * SCALE; }
#Override
public Dimension getPreferredSize(){ return new Dimension(WIDTH * SCALE, HEIGHT * SCALE); }
private static final int FPS = 85;
private static final int PERIOD = 1000 / FPS;
private int currentFPS = 0;
private Thread animator;
private boolean running = false;
private Graphics dbg;
private Image dbImage = null;
public EnginePanel(){
setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setVisible(true);
}
public void addNotify(){
super.addNotify();
startEngine();
}
public void startEngine(){
running = true;
animator = new Thread(this, "Animator");
animator.start();
}
public void stopEngine(){
running = false;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
if (dbImage != null){
g.drawImage(dbImage, 0, 0, null);
}
}
public void paintScreen(){
Graphics g;
try{
g = this.getGraphics();
if ( g != null && dbImage != null){
g.drawImage(dbImage, 0, 0, null);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
} catch(Exception ex) { System.out.println("Graphics Context Error : " + ex); }
}
public void run(){
running = true;
init();
Long beforeTime, timeDiff, sleepTime;
while(running){
beforeTime = System.currentTimeMillis();
updateEngine();
renderEngine();
paintScreen();
timeDiff = System.currentTimeMillis() - beforeTime;
sleepTime = PERIOD - timeDiff;
if (sleepTime <= 0){
sleepTime = 5L;
}
currentFPS = (int) (1000 / (sleepTime + timeDiff));
try{
Thread.sleep(sleepTime);
} catch (InterruptedException ex) { ex.printStackTrace(); }
}
}
private TextField FPSTextField;
public void init(){
FPSTextField = new TextField("Currnet FPS: " + currentFPS, 25, 25);
}
public void updateEngine(){
FPSTextField.setText("Currnet FPS: " + currentFPS);
}
public void renderEngine(){
if (dbImage == null){
dbImage = createImage((int)getWidth(), (int)getHeight());
if (dbImage == null){
System.out.println("Graphical Context Error : DBImage is Null");
return;
} else {
dbg = dbImage.getGraphics();
}
}
Graphics2D g2d = (Graphics2D) dbg;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
FPSTextField.render(g2d, Color.MAGENTA);
}
}
I'm not quite sure why this keeps happening and I have searched for help but can not find the answer. Thanks in advance for all who help :)
EDIT: Added code for the TextField object:
package io.shparki.PhysicsEngine;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
public class TextField{
private Point location;
public Point getLocation(){ return location; }
public double getX() { return location.getX(); }
public double getY() { return location.getY(); }
private String text;
public void setText(String text) { this.text = text; }
public String getText() { return this.text; }
public TextField(String text, int x, int y){
this.location = new Point(x, y);
this.text = text;
}
public TextField(String text, Point location){
this.location = location;
this.text = text;
}
public void render(Graphics g){
g.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics2D g2d){
g2d.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics g, Color color){
g.setColor(color);
g.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics2D g2d, Color color){
g2d.setColor(color);
g2d.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics g, Color color, Font font){
g.setColor(color);
g.setFont(font);
g.drawString(text, (int)location.getX(), (int)location.getY());
}
public void render(Graphics2D g2d, Color color, Font font){
g2d.setColor(color);
g2d.setFont(font);
g2d.drawString(text, (int)location.getX(), (int)location.getY());
}
}
The preferred size of the JPanel EnginePanel restricts the panel from being resized the JFrame is rendered non-resizable. Invoke JFrame#pack after calling setResizable(false). Also move setLocationRelativeTo after pack so that the frame appears centered.
pack();
setLocationRelativeTo(null);
setVisible(true);
If you use a GridBagLayout in the JFrame, then the JPanel will be centered if it is the only thing you add to the JFrame. It will even stay in the center if you resize the window.
Also, the reason the coordinates seem off to you is that you are viewing it as an (x, y) coordinate while it is actually a (row, col) coordinate like in a 2D Array.
I'm trying to capture the screen and then paint the image to a JFrame recursively while scaling the image (to create that effect you get when you look at a mirror in a mirror).
I'm having trouble with my code - it doesn't paint any graphics. What am I doing wrong?
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class ScreenCapture extends JFrame {
BufferedImage screenCapture;
Graphics screenCaptureGraphics;
private static int recurseCount = 0;
private static float $scale = 0.9f;
private static float scale = 1.0f;
private static int height;
private static int width;
ScreenCapture() {
try {
screenCapture = new Robot().createScreenCapture(
new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()) );
height = screenCapture.getHeight();
width = screenCapture.getWidth();
setSize(new Dimension(width, height));
addWindowListener(new LocalWindowListener());
Graphics g = recursiveDraw(screenCapture, getGraphics());
paint(g);
} catch (HeadlessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (AWTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private Graphics recursiveDraw(BufferedImage img, Graphics imgG) {
updateScale(++recurseCount);
float newWidth = scale*width;
float newHeight = scale*height;
int w = (int) newWidth;
int h = (int) newHeight;
System.out.println("W: " + w + "; H: " + h);
if (w >= 10 && h >= 10) {
//scale image
System.out.print("Creating scaled Image...");
Image scaled = img.getScaledInstance(w, h, Image.SCALE_SMOOTH);
BufferedImage resized = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
imgG = resized.createGraphics();
imgG.drawImage(scaled, 0, 0, null);
System.out.println("...Image drawn to graphics");
//return new graphics
return recursiveDraw(resized, imgG);
} else {
//otherwise return old graphics
System.out.println("Completed.");
return imgG;
}
}
private void updateScale(int count) {
for (int i=0; i<count; i++) {
scale *= $scale;
}
System.out.println("Updated scale: " + scale + "; Recurse count: " + recurseCount);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ScreenCapture().setVisible(true);
}
});
}
private class LocalWindowListener extends WindowAdapter {
#Override
public void windowClosing(WindowEvent e) {
System.exit(0); return;
}
}
}
EDIT:
This is what I tried after #andrew-thompson 's answer:
ScreenCapture() {
try {
screenCapture = new Robot().createScreenCapture(
new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()) );
height = screenCapture.getHeight();
width = screenCapture.getWidth();
setSize(new Dimension(width, height));
addWindowListener(new LocalWindowListener());
setLayout(new GridLayout());
add(new PaintPanel());
} catch (HeadlessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (AWTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private class PaintPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
g=recursiveDraw(screenCapture, g);
//what to do with g?
}
}
I still have the same problem where I don't know how to make the BufferedImage paint to the graphics.
would separate out your Swing code from your recursive image creation code. In fact consider creating a static method that creates and returns the BufferedImage and that has no Swing code in it. Then have your GUI call the method when it wishes, and take the image and either write it to disk or display it in a JLabel's ImageIcon.
When I did this (today in fact), I created a recursive method with this signature
private static void recursiveDraw(BufferedImage img, Graphics imgG, double scale) {
and with this method body (in pseudo-code)
start recursiveDraw method
// most important: all recursions must have a good ending condition:
get img height and width. If either <= a min, return
create a BufferedImage, smlImg, for the smaller image using the height,
width and scale factor
Get the Graphics object, smlG, from the small image
Use smlG.drawImage(...) overload to draw the big image in shrunken
form onto the little image
recursively call recursiveDraw passing in smlImg, smlG, and scale.
dispose of smlG
draw smlImg (the small image) onto the bigger one using the bigger's
Graphics object (passed into this method) and a different
overload of the drawImage method.
end recursiveDraw method
This algorithm resulted in images like:
For example:
import java.awt.*;
import java.awt.Dialog.ModalityType;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class RecursiveDrawTest {
private static final Color BACKGRND_1 = Color.green;
private static final Color BACKGRND_2 = Color.MAGENTA;
private static final Color FOREGRND_1 = Color.blue;
private static final Color FOREGRND_2 = Color.RED;
private static void createAndShowGui() {
final JPanel mainPanel = new JPanel(new BorderLayout());
final JSlider slider = new JSlider(50, 90, 65);
slider.setMajorTickSpacing(10);
slider.setMinorTickSpacing(5);
slider.setPaintLabels(true);
slider.setPaintTicks(true);
JPanel southPanel = new JPanel();
southPanel.add(new JLabel("Percent Size Reduction:"));
southPanel.add(slider);
southPanel.add(new JButton(new AbstractAction("Create Recursive Image") {
#Override
public void actionPerformed(ActionEvent arg0) {
try {
double scale = slider.getValue() / 100.0;
BufferedImage img = createRecursiveImg(scale);
ImageIcon icon = new ImageIcon(img);
JLabel label = new JLabel(icon);
Window win = SwingUtilities.getWindowAncestor(mainPanel);
JDialog dialog = new JDialog(win, "Image", ModalityType.MODELESS);
dialog.getContentPane().add(label);
dialog.pack();
dialog.setLocationRelativeTo(null);
dialog.setVisible(true);
} catch (AWTException e) {
e.printStackTrace();
}
}
}));
mainPanel.add(new JScrollPane(new JLabel(new ImageIcon(createLabelImg()))),
BorderLayout.CENTER);
mainPanel.add(southPanel, BorderLayout.PAGE_END);
JFrame frame = new JFrame("RecursiveDrawTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
// create a background image to display in a JLabel so that the GUI
// won't be boring.
private static BufferedImage createLabelImg() {
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
int width = (5 * d.width) / 6;
int height = (5 * d.height) / 6;
BufferedImage img = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(new GradientPaint(0, 0, BACKGRND_1, 40, 40, BACKGRND_2, true));
g2.fillRect(0, 0, width, height);
g2.setPaint(new GradientPaint(0, height, FOREGRND_1, 40, height - 40, FOREGRND_2, true));
g2.fillOval(0, 0, 2 * width, 2 * height);
g2.dispose();
return img;
}
// non-recursive image to get the initial image that will be drawn recursively
public static BufferedImage createRecursiveImg(double scale) throws AWTException {
Robot robot = new Robot();
Dimension screenSz = Toolkit.getDefaultToolkit().getScreenSize();
Rectangle screenRect = new Rectangle(screenSz);
BufferedImage img = robot.createScreenCapture(screenRect);
Graphics g = img.getGraphics();
recursiveDraw(img, g, scale); // call recursive method
g.dispose();
return img;
}
// recursive method to draw image inside of image
private static void recursiveDraw(BufferedImage img, Graphics g, double scale) {
int w = img.getWidth();
int h = img.getHeight();
int smlW = (int)(w * scale);
int smlH = (int)(h * scale);
// criteria to end recursion
if (smlW <= 1 || smlH <= 1) {
return;
}
BufferedImage smlImg = new BufferedImage(smlW, smlH, BufferedImage.TYPE_INT_ARGB);
Graphics smlG = smlImg.getGraphics();
// draw big image in little image, scaled to little image
smlG.drawImage(img, 0, 0, smlW, smlH, null);
// recursive call
recursiveDraw(smlImg, smlG, scale);
smlG.dispose(); // clear resources when done with them
// these guys center the smaller img on the bigger
int smlX = (w - smlW) / 2;
int smlY = (h - smlH) / 2;
// draw small img on big img
g.drawImage(smlImg, smlX, smlY, smlW, smlH, null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Graphics g = recursiveDraw(screenCapture, getGraphics());
Don't call getGraphics(). Override paint(Graphics)1 & use the supplied Graphics instance.
When using Swing, it is actually best to override the paintComponent(Graphics) method of a JComponent or JPanel. Then add that to the top-level container.
Is this what you are hoping for :
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.*;
public class CaptureScreen extends JPanel
{
private BufferedImage screenShot;
private JLabel imageLabel;
private BufferedImage secondScreenShot;
public void createAndDisplayGUI()
{
JFrame frame = new JFrame("CAPTURE SCREEN");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
//imageLabel = new JLabel();
//getImageForLabel();
//add(imageLabel);
frame.getContentPane().add(this);
frame.setSize(600, 600);
frame.setVisible(true);
}
private void getImageForLabel()
{
Robot robot = null;
try
{
robot = new Robot();
}
catch(Exception e)
{
e.printStackTrace();
}
screenShot = robot.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
ImageIcon icon = new ImageIcon(screenShot);
imageLabel.setIcon(icon);
}
public void paintComponent(Graphics g)
{
Robot robot = null;
try
{
robot = new Robot();
}
catch(Exception e)
{
e.printStackTrace();
}
screenShot = robot.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
secondScreenShot = getScaledImage((Image) screenShot, 300, 300);
g.drawImage(screenShot, 0, 0, null);
g.drawImage(secondScreenShot, 150, 150, null);
}
private BufferedImage getScaledImage(Image srcImg, int w, int h)
{
BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TRANSLUCENT);
Graphics2D g2 = resizedImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(srcImg, 0, 0, w, h, null);
g2.dispose();
return resizedImg;
}
public static void main(String... args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new CaptureScreen().createAndDisplayGUI();
}
});
}
}
Here is the output :
Get rid of the recursion. Create a single buffered image of the correct size and create a Graphics object for it. Just use a loop to draw progressively smaller scaled images down to whatever threshold you choose. Finally use g.drawImage() inside paintComponent() to draw your image to the screen. If you keep the recursion you need to pass the image and continually overlay the scaled down image. You do not need to return anything from the method.