Why isn't JFrame displaying the entire image? - java

Recently I have been experimenting with java graphics and decided to make a program that can print a Collage of images. The constructor for this program takes in an array of Images and the width that each image will be displayed at.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
public class CollageTest extends JComponent
{
private Image [][] board; //Holds images within it.
private int pixel;//width of image.
public CollageTest(Image x[][], int pixel)
{
board = x;
this.pixel = pixel;
}
public int getPixel()
{
return pixel;
}
public void paint(Graphics g)
{
for (int i = 0; i < board.length; i++)
{
for (int j = 0; j < board[0].length; j++)
{
g.drawImage(board[i][j], (i * pixel) , (j * pixel), pixel, pixel, null);
}
}
}
public static void main(String args[])
{
BufferedImage[][] images = new BufferedImage[20][20];
BufferedImage img = null;
try {
img = ImageIO.read(new File("gorge3.jpg"));
} catch (IOException e) {
}
for (int i = 0; i < images.length; i++)
{
for (int j = 0; j < images[0].length; j++)
{
images[i][j] = img;
}
}
CollageTest test = new CollageTest(images, 20);
JFrame frame = new JFrame("MyCollage");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(test);
frame.setSize(test.getPixel() * images.length,test.getPixel() * images[0].length);
frame.setVisible(true);
test.repaint();
}
}
To test the I initialized a CollageTest using an 20 x 20 array filled entirely with this Image.
When I ran the program originally I used The following to set the size of the JFrame's window:
frame.setSize(test.getPixel() * images.length,test.getPixel() * images[0].length);
Unfortunately the JFrame did display part of the image. The JFrame window did not go low enough down to display all of the window.
To double double check that my math wasn't off, I tested using the following line in replacement:
frame.setSize(400, 400);//Math 20 images * 20 pixels = 400 pixels
The same problem occured.
It was not until I used the following line that the entire image was displayed:
frame.setSize(400, 437);
So my question is, why is my JFrame misbehaving and is their a better way to fix it?

The problem lies in here:
frame.setSize(test.getPixel() * images.length,test.getPixel() * images[0].length);
You are manually setting the frame size according to the image size. However the frame size takes the title-bar and possibly borders into considerations. If the title-bar takes up 37 pixels, it leaves you 37 pixels lesser for the image.
What I would do is:
Add the image to a JPanel. You may set the size of the panel according to the image. pack() your JFrame after adding the panel containing your image so that it will determine the preferred size of the JFrame by itself.

Related

How to draw adjacents single pixels with windows 10 scaling of over 100% in java awt graphics

I'm drawing a minimap for a game and every 40x40 pixels in the game is a pixel on the minimap.
The problem starts when I have a screen which uses windows 10 scaling for example of 125% scaling.
Pixels drawn with g.drawLine(x,y,x,y) fill 1 pixel in a 2x2 space with scaling 125%, but are adjacent with 100% scaling.
https://prnt.sc/10uiez6
https://prnt.sc/10uieai
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class WindowsScaleTesting {
public static void main(final String[] args) {
final JFrame frame = new JFrame("windows 10 scaling minimum example line");
frame.setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
final JPanel board = new JPanel() {
#Override
public void paint(final Graphics g) {
for(int x = 0; x < 100; x++) {
for(int y = 0; y < 100; y++) {
g.setColor(Color.gray);
g.drawLine(x, y, x, y);
}
}
}
};
board.setSize((1900),(1070));
board.setDoubleBuffered(true);
frame.add(board);
frame.setSize((1900),(1070));
frame.setVisible(true);
frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
}
}
Pixels drawn with g.drawRect(x,y,1,1) fill 3 pixels in a 2x2 space with scaling 125%, but are adjacent with 100% scaling.
https://prnt.sc/10uig1w
https://prnt.sc/10uieai
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class WindowsScaleTesting2 {
public static void main(final String[] args) {
final JFrame frame = new JFrame("windows 10 scaling minimum example rectangle");
frame.setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
final JPanel board = new JPanel() {
#Override
public void paint(final Graphics g) {
for(int x = 0; x < 100; x++) {
for(int y = 0; y < 100; y++) {
g.setColor(Color.gray);
g.drawRect(x, y, 1, 1);
}
}
}
};
board.setSize((1900),(1070));
board.setDoubleBuffered(true);
frame.add(board);
frame.setSize((1900),(1070));
frame.setVisible(true);
frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
}
}
To reproduce the pictures above you need 2 screens with different scaling (100% and 125%) or you need to have 1 screen and switch the scaling from 100% to 125%.
How do I draw pixels with no spaces in between on 125% or other scaling and how do I recognize windows 10 scaling in java?
One solution to the problem is to fix the scaling in the vm arguments.
Adding -Dsun.java2d.uiScale=1.0 as a vm argument to the programm in eclipse solves the scaling issue, credits to Christian Hujer in this answer https://superuser.com/a/1194728/377633

Java drawing to JPanel Graphics, using byte array, blinking white

So I am trying to update a JPanel using a byte array with a specified byte/color
This is a very simple version.
When I start the program it's white for half a second, then becomes the right color, and then after 1 second it's going back to being white, I tried to print out the current color, and sometimes it's changing to '0'.
What am I doing wrong?
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
public static Random ran = new Random();
public static byte[] buffer;
public static int num = 0;
public static JFrame frame = new JFrame();
public static JPanel panel = new JPanel() {
private static final long serialVersionUID = 1L;
#Override
public void paint(Graphics g) {
num = 0;
byte[] current_buffer = buffer.clone();
for (int y = 0; y < panel.getHeight(); y++) {
for (int x = 0; x < panel.getWidth(); x++) {
g.setColor(new Color(current_buffer[num], current_buffer[num], current_buffer[num]));
g.fillRect(x, y, 1, 1);
num++;
}
}
}
};
public static void main(String[] args) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1200, 800);
frame.setLocationRelativeTo(null);
frame.setContentPane(panel);
frame.setResizable(false);
frame.setVisible(true);
engine.run();
}
public static boolean running = true;
public static Thread engine = new Thread() {
#Override
public void run() {
buffer = new byte[panel.getWidth() * panel.getHeight()];
for (int i = 0; i < buffer.length; i++) {
buffer[i] = (byte) 34;
}
while (running) {
panel.repaint(10L);
}
}
};
}
Not really understanding your logic:
for (int i = 0; i < buffer.length; i++) {
buffer[i] = (byte) 34;
}
You assign the same value to the buffer.
Then you create a Color object using the same value.
g.setColor(new Color(current_buffer[num], current_buffer[num], current_buffer[num]));
So every pixel will be the same Color.
And since the buffer is always built with the same values, the color won't change.
Also, why would you use
byte[] current_buffer = buffer.clone();
You are just using the values in the buffer, not updating the values in the buffer so I see no reason for the clone.
Don't use a Thread with a while loop. For animation you should be using a Swing Timer. When the Timer fires you update the values in the buffer and then invoke repaint().
When I start the program it's white for half a second, then becomes the right color, and then after 1 second it's going back to being white,
I don't see that behaviour.
it blinks a light color
it stays painted at a darker color
The above makes sense because:
Your paintCompnent() method does not invoke super.paintComponent(...) to make sure the background is cleared
The array is not initialized to any values when it is created so the default value will be 0.
After the Thread takes over the array will alway contain the same value so the same color will be painted.
What do you expect it to do?

How to combine images in a JFrame?

I have been working on a project that is displaying a grid 16 x 16 of images, based on user interaction this grid follows the user on a dynamically larger base (an example would be a base that is 50 x 50) than the 16 x 16 display.
However, I am using JLabel components to display these images, and every time the user interacts I have to move each of the 256 images and erase the ones that are no longer in the 16 x 16 display grid. This results in a lag that is close to a second per key press and is close to nonfunctional.
What I am looking to try to do is to chain these images together in the total width of the ground and simply move the focus to the portion that is within the 16 x 16 grid, making the process no longer have to use nested for loops for the display.
Is it possible that I could dynamically store and create these chained images for display using a label? If are there other ways to display .png files in Java that could be stored and used in a similar manner?
An example of my current methodology of having to draw every image upon every user interaction:
User user = game.user;
int floorWidth = game.floorWidth;
int floorHeight = game.floorHeight;
int pX = user.getTile().getX();
int pY = user.getTile().getY();
int minX = Math.max(pX - GameConstants.USER_DRAW_DISTANCE, 0);
int maxX = Math.min(floorWidth, pX + GameConstants.USER_DRAW_DISTANCE);
int minY = Math.max(pY - GameConstants.USER_DRAW_DISTANCE, 0);
int maxY = Math.min(floorHeight, pY + GameConstants.USER_DRAW_DISTANCE);
for (int i = minY; i < maxY; i++)
{
for (int x = minX; x < maxX; x++)
{
Tile tile = floor.getTile(x, i);
if (tile.getHasSeen())
{
JLabel cLabel = tile.imageLabel;
cLabel.setLocation(340 + x * 32, 140 + i * 32);
cLabel.setSize(64, 64);
cLabel.setVisible(true);
panel.add(cLabel, 1);
}
}
}
In principle your idea should work. So you're probably doing something else wrong.
I've made an example, where it displays a 16x16 square of JLabels out of 256x256 JLabels. When you move the mouse over the panel, it changes the layout to show a new set of 16x16 JLabels. The change is pretty snappy, definitely not a 1 second delay.
import javax.swing.*;
import java.awt.EventQueue;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.event.*;
import java.util.*;
public class GridViewer{
int x0, y0;
int N = 256;
int display = 16;
int length = 32;
List<JLabel> showing = new ArrayList<>();
List<JLabel> available = new ArrayList<>();
JPanel panel = new JPanel(){
Dimension sz = new Dimension(length*display, length*display);
#Override
public Dimension getPreferredSize(){
return sz;
}
};
public void showGui(){
JFrame frame = new JFrame();
panel.setLayout(null);
panel.addMouseMotionListener( new MouseAdapter(){
Random r = new Random();
#Override
public void mouseMoved(MouseEvent evt){
int x = evt.getX();
int y = evt.getY();
//map to position on the image to the position on the grid.
x0 = x/2;
x0 = Math.min(x0, N-display);
y0 = y/2;
y0 = Math.min(y0, N-display);
updateLayout();
}
});
for(int i = 0; i<N*N; i++){
available.add(createItem(i));
}
updateLayout();
frame.setContentPane(panel);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
/**
* Creates a solid color jlabel, could be used to load an image
* as an icon.
**/
JLabel createItem(int i){
JLabel l = new JLabel("");
int r = (i/256);
int g = (0)&255;
int b = (i%256);
int c = (r << 16 ) + ( g << 8 ) + b;
l.setBackground(new Color(c));
l.setOpaque(true);
l.setSize(length, length);
return l;
}
public void updateLayout(){
for(JLabel l: showing){
panel.remove(l);
}
for(int i = 0; i<display; i++){
for(int j = 0; j<display; j++){
JLabel l = available.get((i + x0) + (j+y0)*N);
panel.add(l);
l.setLocation( i*length, j*length);
showing.add(l);
}
}
}
public static void main(String[] args){
EventQueue.invokeLater( () -> new GridViewer().showGui() );
}
}
Some variations.
Use a GridLayout
Using a layout manager has a lot of advantages. Especially when it comes to using different displays, fonts and platforms? When adding and removing elements, it could make partially showing elements tough.
Use a large JPanel with a ScrollPane
We could create a single JPanel and add all 256x256 components to it, then use a scroll pane to set the view. This would have an advantage of completely separating the layout and the view. Somebody wants a larger window, you don't have to change the layout, the view gets bigger and you just see more of the layout. For 256x256 components, it should perform well but if you have too many components you might want to reconsider it.
Use a JPanel and override paintComponent
This would involve loading your 'png' files as awt Images (probably BufferedImages) and drawing them with the graphics object. You would need to handle all of the layout and rendering. It gives you quite a bit of power over how you want to render your components.

Java JFrame Plotting Multiple Lines

So the crux of my problem is plotting multiple components into one JFrame in Java. I'm trying to use the same component twice to plot two different lines, but only one appears. I'm working across three separate classes in separate files, which might be making it more difficult for me. I have tried possible solutions to no avail here, here, here, here, and elsewhere. I suspect I am doing multiple things wrong, as I'm still trying to fully understand JFrame, JPanel, and LayoutManagers. Can anyone show where I went wrong?
My tester class is as follows:
import javax.swing.JFrame;
public class TransportSlabTester
{
public static void main(String[] args)
{
System.out.println("Estimation at 100 sections: ");
TransportSlab slab1 = new TransportSlab(10000,1,5,100);
System.out.println();
JFrame frame = new JFrame("Attenuated Profile");
frame.setSize(600,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TransportSlabGraph component = new TransportSlabGraph();
//analytical is a method from a 3rd class that returns double[]
component.attProfileArray(slab1.analytical(),slab1.getThickness());
frame.add(component);
component = new TransportSlabGraph();
//euler is a method from a 3rd class that returns double[]
component.attProfileArray(slab1.euler(),slab1.getThickness());
frame.add(component);
frame.setVisible(true);
}
}
Now, the class that extends JPanel:
import java.awt.*;
import java.awt.geom.Line2D;
import java.math.*;
import javax.swing.JPanel;
public class TransportSlabGraph extends JPanel
{
double[] N, xAxes, yAxes;
final int edge = 100; //Distance from edge of frame
String[] xlabel = new String[11];
String[] ylabel = new String[11];
/**
*
* #param inputN Data array of type {#code double[]}
* #param thickness Thickness set by the original constructor
*/
public void attProfileArray(double[] inputN, double thickness)
{
N = new double[inputN.length];
//Create labels for the tick marks of the x and y axis from rounded #'s
BigDecimal bd1, bd2;
for (int i = 0; i <= 10; i++)
{
bd1 = new BigDecimal((thickness/10)*i);
MathContext mc = new MathContext(2); //Round to one decimal place
bd2 = bd1.round(mc);
xlabel[i] = String.valueOf(bd2.doubleValue());
ylabel[i] = String.valueOf((inputN[0]*i)/(inputN.length-1));
}
//Set up data array and the axes
for (int i = 0; i < N.length; i++)
{
N[i]=inputN[i];
xAxes = new double[N.length];
yAxes = new double[N.length];
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
//Get frame dimensions to scale drawn components
int w = getWidth();
int h = getHeight();
double xInc = (double)(w-2*edge)/(N.length-1);
double scale = (double)(h-2*edge)/N[0];
g2.draw(new Line2D.Double(edge, h-edge, w-edge, h-edge)); //draw x axis
g2.draw(new Line2D.Double(edge, edge, edge, h-edge)); // draw y axis
//Create evenly spaced tick marks for both axes and label them
for (int i = 0; i <= 10; i++)
{
g2.draw(new Line2D.Double(edge+((w-edge-edge)/10.0)*i, h-edge-10, edge+((w-edge-edge)/10.0)*i, h-edge+10)); //x ticks
g2.draw(new Line2D.Double(edge-10, h-edge-((h-edge-edge)/10.0)*i, edge+10, h-edge-((h-edge-edge)/10.0)*i)); //y ticks
g2.drawString(xlabel[i],(int)(edge+((w-edge-edge)/10.0)*i),h-edge+20);
g2.drawString(ylabel[i],edge-30,(int)(h-edge-((h-edge-edge)/10.0)*i));
}
//Scale data and convert to pixel coordinates
for (int i = 0; i < N.length; i++)
{
xAxes[i] = edge+i*xInc;
yAxes[i] = h-edge-scale*N[i];
}
//Only set the data line's color
g2.setPaint(Color.BLUE);
//Draw the data as a series of line segments
for (int i = 1; i < N.length; i++)
{
g2.draw(new Line2D.Double(xAxes[i-1],yAxes[i-1],xAxes[i],yAxes[i]));
}
}
}
Problem #1
An instance of a Component may only reside within a single Container (once).
You will need to create a new instance of each Component you want to add. I would recommend a factory pattern...
Problem #2
JFrame, but default, uses a BorderLayout, which will only allow a single component to reside at each of it's 5 available layout positions.
You will also have problems because your TransportSlabGraph class doesn't override it's getPreferredSize method, which means that, by default, instance of the component will be provided with a default size of 0x0 by many of the layout managers.
Consider changing the layout manager to something like GridLayout to start with.
Take a look at Laying Out Components Within a Container for more details

GridLayout stacks all JPanels on first cell

I am trying to create a map for a game I am making with a JPanel that uses gridLayout. In my first tests I use a 5x5 grid and create my small panels which are a subclass of JPanel. My program creates them fine but when I add all of the panels into the larger panel and display it, only the first square shows up and the rest is blank. Why does it do that?
Here is my code for the MapSpace(smaller panel):
import javax.swing.*;
import java.awt.*;
public class MapSpace extends JPanel{
private int ownerTag;
private int xPos, yPos;
public MapSpace(){
xPos = 0;
yPos = 0;
ownerTag = 0;
setBackground(Color.WHITE);
}
public MapSpace(MapSpace m){
xPos = m.getX();
yPos = m.getY();
ownerTag = m.getID();
setBackground(m.getColor());
}
and here is my code for the Map:
import javax.swing.*;
import java.awt.*;
import java.util.*;
public class Map extends JPanel{
private int cols, rows;
private int randCol, randRow;
private MapSpace[][] spaces;
Random gen = new Random();
public Map(int w, int h){
cols = h;
rows = w;
setLayout(new GridLayout(cols, rows));
setBackground(Color.WHITE);
spaces = new MapSpace[cols][rows];
for(int i = 0; i < cols; i++){
for(int j = 0; j < rows; j++){
MapSpace panel = new MapSpace(i, j);
spaces[i][j] = panel;
}
}
assignSpaces(3);
setColors();
for(int i = 0; i < cols; i++){
for(int j = 0; j < rows; j++){
MapSpace spot = new MapSpace(spaces[i][j]);
add(spot);
}
}
setSize(400, 400);
}
the second nested for loop is where all the mapSpaces are added but when I put the map in a JFrame and display it in a GUI window only one small square in the top left corner appears.
Why are you trying to create a MapSpace with an instance of MapSpace?
Just create the MapSpace with the parameters you want and then add the MapSpace to your Array and the panel at the same time.
and display it in a GUI window only one small square in the top left corner appears.
Probably because you don't give a preferredSize() size to your MapSpace class so it defaults to (10, 10) which is the size for a panel using a FlowLayout with no components added to it. So since you create a 5x5 grid you probably see a (50, 50) white square.
Override the getPreferredSize() of your MapSpace class to return the default Dimension for each square.

Categories