Aren't Graphics/JPanel dimensions measured in pixels? - java

When I draw [drawRect(x, y, width, height)] a rectangle on an JPanel inside a JFrame, that has a width of e.g. 500, it is actually wider than 500 Pixels on my Screen. How is this measured?
Whilst messing around with a drawing Rectangles on an JPanel and the size of the JFrame around this, i recognized, that 500 "width" are different things, when it comes to JFrame and JPanel.
A JFrame that is created with a width of 1920 Pixels is exactly 1920 Pixels wide, means, as wide as my screen (1920x1080).
If i draw a rectangle with a width of 1920 on a JPanel, that is inside the JFrame, it extends my screen by exactly 385 Pixels. Respectively: a drawn rectangle as wide as my screen needs a width of 1535.
import javax.swing.*;
import java.awt.*
public class Main {
public static void main(String[] args){
JFrame window = new JFrame();
window.setSize(1920,1080); //Window as wide as the screen
window.add(new Canvas());
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
public class Canvas extends JPanel {
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawRect(0, 0, 1920, 500); //Paints a rectangle on the JPanel
}
}
The window that opens is exactly as wide as my screen, but the rectangle inside extends it.
If I change the width of the rectangle to 1535 [drawRect(0, 0, 1535, 500)], it is as wide as the JFrame/screen. Why is that?
Edit: Since the Windows 10 Frame has no decorations at the side, just the standard menu-bar on top, I don't think this is the problem (as far as I understand decorations).

The short answer: Yes they are.
The explanation: Let us look deeper!
Running Java Swing on MacOS (tested with Metal LAF), the JFrame has insets of zero for left and right. This is similar to rendering for certain themes on Windows 10. I have included code below; the gap between the content pane and the panel's fill rectangle should remain 8 pixels. When the program is running, resize it, and check for yourself. Feel free to comment if this is not the behaviour you experience.
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
final String name;
name = javax.swing.plaf.metal.MetalLookAndFeel.class.getName();
try {
UIManager.setLookAndFeel(name);
}
catch (Throwable e) {
e.printStackTrace();
}
createAndShowWindow();
});
}
private static void createAndShowWindow() {
final int width = 1920;
final int height = 800;
final int padding = 8;
JFrame window = new JFrame();
window.setTitle("Hello World.");
window.setPreferredSize(new Dimension(width, height));
window.setSize(width, height); //Window as wide as the screen
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Canvas canvas = new Canvas(padding);
window.add(canvas, BorderLayout.CENTER);
window.pack();
window.setVisible(true);
System.out.println("w: " + window.getSize());
System.out.println("c: " + window.getContentPane().getSize());
System.out.println("p: " + canvas.getSize());
System.out.println("i: " + window.getInsets());
}
public static class Canvas extends JPanel {
private final int padding;
public Canvas(int padding) {
this.padding = padding;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(180, 120, 16));
//Paints a rectangle on the JPanel
int x = padding;
int y = padding;
int w = getWidth() - 2 * padding;
int h = getHeight() - 2 * padding;
g.fillRect(x, y, w, h);
}
}

Related

Java swing JButton/JLabel: icons are not displayed in their original size

I have png icons and use them as icons in JButton / JLabel.
The problem is that the image displayed at runtime is larger than the original icon, and because of this resizing, it's super ugly.
Here is an example:
Original icon (left) and how it's rendered in the JButton (right)
The source code for this minimal example is simply:
public class Main {
public static void main(String... args) {
JFrame frame = new JFrame("Test");
frame.setBounds(0, 0, 120, 80);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new FlowLayout());
ImageIcon icon = new ImageIcon("icon.png");
frame.getContentPane().add(new JButton("Test", icon));
frame.setVisible(true);
}
}
Is this expected? If not, how can I avoid this? I tried many things around forcing the size of the image, the button, etc. but could not get a proper image displayed.
I have tested with icons of various sizes: 16x16, 17x17, 18x18, 19x19, 20x20, and each time the icon displayed on the JButton is a bit larger than the original which makes it look ugly:
Thank you!
Cheers.
This is because you are using Windows scaling. The entire component is scaled, both the icon and the text.
You could turn the scaling of the Icon off by using a wrapper Icon:
import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
public class NoScalingIcon implements Icon
{
private Icon icon;
public NoScalingIcon(Icon icon)
{
this.icon = icon;
}
public int getIconWidth()
{
return icon.getIconWidth();
}
public int getIconHeight()
{
return icon.getIconHeight();
}
public void paintIcon(Component c, Graphics g, int x, int y)
{
Graphics2D g2d = (Graphics2D)g.create();
AffineTransform at = g2d.getTransform();
int scaleX = (int)(x * at.getScaleX());
int scaleY = (int)(y * at.getScaleY());
int offsetX = (int)(icon.getIconWidth() * (at.getScaleX() - 1) / 2);
int offsetY = (int)(icon.getIconHeight() * (at.getScaleY() - 1) / 2);
int locationX = scaleX + offsetX;
int locationY = scaleY + offsetY;
AffineTransform scaled = AffineTransform.getScaleInstance(1.0 / at.getScaleX(), 1.0 / at.getScaleY());
at.concatenate( scaled );
g2d.setTransform( at );
icon.paintIcon(c, g2d, locationX, locationY);
g2d.dispose();
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI()
{
JButton button = new JButton( "Button" );
NoScalingIcon icon = new NoScalingIcon( new ImageIcon("box.jpg") );
button.setIcon( icon );
JPanel panel = new JPanel( );
panel.add( button );
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(panel);
f.setSize(200, 200);
f.setLocationRelativeTo( null );
f.setVisible(true);
}
}
The scaling adjustment will position the Icon at the top/left of the button area.
The offset adjustment will then attempt to center the Icon in the scaled icon painting area.
Using the default transform will have a scaling factor of 0 for the Icon.
Thank you all.
The problem was the default scaling factor (which was 1.25).
As I want to be fully in control of the size independently from DPI, I solved my issue by forcing the scaling factor to 1.0.
This answer was helpful
So, either pass to the command line
-Dsun.java2d.uiScale=1.0,
or set it programmatically
System.setProperty("sun.java2d.uiScale", "1.0")
Look at the source code for the constructor of class ImageIcon that takes a string parameter. It uses class java.awt.Toolkit to create the image from the file. This made me think that it must be doing some scaling. So I thought of creating the icon differently. ImageIcon has another constructor that takes an Image parameter. So I created a BufferedImage from the file and then used that image to create an ImageIcon. The BufferedImage is not scaled.
Note that your link to the icon file didn't work for me so I just downloaded a different 16x16 icon.
java.awt.image.BufferedImage img = javax.imageio.ImageIO.read(new java.io.File("cellphon.png"));
javax.swing.Icon ico = new javax.swing.ImageIcon(img);
javax.swing.JButton button = new javax.swing.JButton("Test", ico);

Add extended rectangle class to jpanel

I have a class, SheetGood, which extends Rectangle. At the moment I place these SheetGoods onscreen using absolute positions based off of the users resolution, but I'd like to let a layoutmanager take over this aspect.
To do so I'd like to add a SheetGood object to a JPanel, but can't as SheetGood does not extend JComponent.
Any ideas as to how I can get around this?
//edit//
Will I run into issues if I force my program to run at a certain size and remove resizing options?
Ie, a fixed size of 1280x1024 so I can continue placing SheetGoods how I have been and not have to worry about the other controls clipping them when their layout manager moves them around.
To use absolute positioning, dont use a layout manager. You should set layout to null.
I suggest that: extends JPanel as rectangle and set a background color, and set bounds to the positions you want to place.
static class MyRectangle extends JPanel {
int x,
y,
width,
height;
Color bg;
public MyRectangle(int x, int y, int width, int height, Color bg) {
super();
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.bg = bg;
setBounds(x, y, width, height);
setBackground(bg);
}
}
public static void main(String[] args) throws Exception {
JFrame frame = new JFrame("Test rectangle");
MyRectangle rect1 = new MyRectangle(10, 10, 90, 90, Color.red),
rect2 = new MyRectangle(110, 110, 90, 90, Color.yellow);
JPanel contentPane = (JPanel)frame.getContentPane();
contentPane.setLayout(null); //to make things absolute positioning
contentPane.add(rect1);
contentPane.add(rect2);
frame.setSize(400, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}

Resizing JPanel when JFrame is maximized

Alright, so the following code shows a JPanel within a JFrame when the program is first run. If the window is re-sized by dragging one of the sides or corners of the frame, the JPanel re-sizes itself and maintains the aspect ratio of the monitor.
NOTE: The JPanel is set to remain within the bounds of the window on a 1920x1080 resolution monitor only. On any other monitor size, the JPanel may get cut off. See my comment above setPreferredSize() in the updatePanelSize() method.
public class Frame extends JFrame {
Panel panel = new Panel();
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Frame();
}
});
}
// Setup the window, add the panel, and initialize a "window" listener.
public Frame() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(1280, 720);
setLocationRelativeTo(null);
setVisible(true);
setTitle("Frame");
setLayout(new GridBagLayout());
add(panel);
initListeners();
}
public void initListeners() {
/** When the window is resized, the panel size is updated. */
addComponentListener(new ComponentListener() {
#Override
public void componentResized(ComponentEvent e) {
panel.updatePanelSize();
}
#Override
public void componentHidden(ComponentEvent evt) {}
#Override
public void componentShown(ComponentEvent evt) {}
#Override
public void componentMoved(ComponentEvent evt) {}
});
}
}
public class Panel extends JPanel {
public Panel() {
setBackground(new Color(100, 0, 0));
setPreferredSize(new Dimension(1052, 592));
}
// Resizes the JPanel while maintaining the same aspect ratio
// of the monitor.
public void updatePanelSize() {
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
float monitorWidth = gd.getDisplayMode().getWidth();
float monitorHeight = gd.getDisplayMode().getHeight();
// Aspect ratio of the monitor in decimal form.
float monitorRatio = monitorWidth / monitorHeight;
JComponent parent = (JComponent) getParent();
float width = parent.getWidth();
float height = parent.getHeight();
width = Math.min(width, height * monitorRatio);
height = width / monitorRatio;
// I am subtracting the width and height by their respected aspect ratio
// coefficients (1920x1080 -> 16:9 (width:height)) and multiplying them
// by some scale (in this case 10) to add a "padding" to the JPanel.
// The ratio coefficients and scale will need to be edited based upon the
// resolution of your monitor.
setPreferredSize(new Dimension((int)width - (16 * 10), (int)height - (9 * 10)));
System.out.println("PanelRes: " + ((int)width - (16 * 10)) + "x" + ((int)height - (9 * 10)));
System.out.println("PanelRatio: " + getWidth() / getHeight());
}
}
The problem I am having is that if I maximize the window by double clicking the window toolbar (or whatever the correct term for the top of the window would be) or by clicking the maximize button, the JPanel does not re-size like it ought to. The Overridden componentResized() method is called when the window is maximized, but the JPanel doesn't resize. Any help on solving this issue would be great.
On resize the panel is immediately accepting the new preferred dimensions in updatePanelSize(), but on maximize/restore the panel is apparently ignoring the new preferred dimensions.
I've added a call to revalidate(), to force the panel to update in those cases where it hasn't applied the new preferred dimensions.
public void updatePanelSize() {
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice();
float monitorWidth = gd.getDisplayMode().getWidth();
float monitorHeight = gd.getDisplayMode().getHeight();
// Aspect ratio of the monitor in decimal form.
float monitorRatio = monitorWidth / monitorHeight;
JComponent parent = (JComponent) getParent();
float width = parent.getWidth();
float height = parent.getHeight();
width = Math.min(width, height * monitorRatio);
height = width / monitorRatio;
// I am subtracting the width and height by their respective aspect ratio...
int paddedWidth = (int) width - (16 * 10);
int paddedHeight = (int) height - (9 * 10);
setPreferredSize(new Dimension(paddedWidth, paddedHeight));
int resultWidth = getWidth();
int resultHeight = getHeight();
if (paddedWidth != resultWidth && paddedHeight != resultHeight) {
revalidate(); // preferred dimensions not applied, so force them
}
System.out.println("PreferredSize: " + paddedWidth + "x" + paddedHeight);
System.out.println("PanelRes: " + resultWidth + "x" + resultHeight);
System.out.println("PanelRatio: " + (float)resultWidth / resultHeight);
}

how to get the image of a Swing widget? [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Java Swing : Obtain Image of JFrame
I am working on a little drag-and-drop Java GUI builder. It works so far, but the widgets I'm dragging and dropping are just rectangles I'm dynamically drawing on a canvas.
If I have a rectangle that represents a widget like a JButton, is there a way for me to create a JButton, set the size and get the image of that JButton if it was drawn on the screen? Then I could paint the image to the screen instead of just my boring rectangle.
For example, I'm currently doing this to draw a (red) rectangle:
public void paint(Graphics graphics) {
int x = 100;
int y = 100;
int height = 100;
int width = 150;
graphics.setColor(Color.red);
graphics.drawRect(x, y, height, width);
}
How can I do something like:
public void paint(Graphics graphics) {
int x = 100;
int y = 100;
int height = 100;
int width = 150;
JButton btn = new JButton();
btn.setLabel("btn1");
btn.setHeight(height); // or minHeight, or maxHeight, or preferredHeight, or whatever; swing is tricky ;)
btn.setWidth(width);
Image image = // get the image of what the button will look like on screen at size of 'height' and 'width'
drawImage(image, x, y, imageObserver);
}
Basically, you'll paint your component to an image, and then paint that image wherever you want. In this case it's okay to call paint directly because you're not painting to the screen (but to a memory location).
If you wanted to optimize your code more than I've done here, you can save the image, and just repaint it in a different location whenever it's moved (instead of calculating the image from the button every time the screen repaints).
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class MainPanel extends Box{
public MainPanel(){
super(BoxLayout.Y_AXIS);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
// Create image to paint button to
BufferedImage buttonImage = new BufferedImage(100, 150, BufferedImage.TYPE_INT_ARGB);
final Graphics g2d = buttonImage.getGraphics();
// Create button and paint it to your image
JButton button = new JButton("Click Me");
button.setSize(button.getPreferredSize());
button.paint(g2d);
// Draw image in desired location
g.drawImage(buttonImage, 100, 100, null);
}
public static void main(String[] args){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new MainPanel());
frame.pack();
frame.setSize(400, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}

centre an image in the middle of a panel

I am coming to grips with Graphics in Java and created a circle on a JPanel.
How would a center the circle in the JPanel?
package exerciseninetwo;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
public class ExerciseNineTwo extends JFrame
{
public ExerciseNineTwo()
{
super("My Frame");
setSize(500, 500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new CanvasPanel());
setVisible(true);
}
public static void main(String[] args)
{
new ExerciseNineTwo();
}
}
class CanvasPanel extends JPanel
{
CanvasPanel()
{
setSize(120, 120);
//setBackground(Color.cyan);
}
protected void paintComponent(Graphics g)
{
Graphics2D comp = (Graphics2D)g;
Ellipse2D circle = new Ellipse2D.Float(200, 200, 200, 200);
comp.draw(circle);
comp.setColor(Color.cyan);
comp.fillRect(0,0,500,500);
comp.setClip(circle);
comp.setColor(Color.magenta);
comp.fillRect(0,0,500,500);
}
}
Just draw it in the middle of your panel.
float x = getWidth()/2 - ELLIPSE_WIDTH/2;
float y = getHeight()/2 - ELLIPSE_HEIGHT/2;
Ellipse2D circle = new Ellipse2D.Float(x, y, ELLIPSE_WIDTH, ELLIPSE_HEIGHT);
Use getWidth()/getHeight() of the panel.
int x=(getWidth()-ovalWidth)/2;
int y=(getHeight()-ovalHeight)/2;
Check that panel width is bigger than oval width, and the same with height.
Take the panel object and query the X and Y size parameters(, or width and height). Divide each by 2 will give you the center of the frame. Create a circle using the result as the X and Y coordinates.
like
float x = (width-width of oval) /2;
float y = (height-height of oval) /2;
now set the x and y in the constructor of eclipse
You may easily get the size of your panel and place the circle accordingly:
Dimension size = getSize();
Ellipse2D circle = new Ellipse2D.Float(
(size.width - 200) / 2, // -200 due to the width/height of the circle
(size.height - 200) / 2,
200, 200);

Categories