Ok so I am very new to Java Swing and a beginner in Java in general. My current problem is I have designed a "cityscape". I am working on a UFO flying around, but my randomly generated buildings continue to get regenerated. I am wondering if there is a way to save my instance of buildings to an ArrayList as I have attempted, and paint that selection from that list each time paint is called. I tried what I thought of and I believe it just crashed it when run, because it didn't even open a JFrame and instead produced errors upon errors. Here is what I have:
CityScape class (the main class):
import java.awt.*;
import javax.swing.*;
public class CityScape extends JPanel
{
Buildings a = new Buildings ();
UFO b = new UFO();
#Override
public void paint (Graphics g)
{
//RememberBuildings.buildingList.get(1).paint(g);
a.paint(g);
b.paint(g);
}
public void move()
{
b.move();
}
public static void main(String[] args) throws InterruptedException
{
JFrame frame = new JFrame("Frame");
CityScape jpe = new CityScape();
frame.add(jpe);
frame.setSize(800, 750);
frame.setBackground(Color.BLACK);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
System.out.println(frame.getContentPane().getSize());
while (true)
{
jpe.move(); //Updates the coordinates
jpe.repaint(); //Calls the paint method
Thread.sleep(10); //Pauses for a moment
}
}
}
Buildings class (the class that generates the buildings):
import java.awt.*;
public class Buildings
{
private int maxX = 784;
private int maxY = 712;
private int width = (int)(Math.random()*100+100);
private int height = (int)(Math.random()*350+100);
private int rows = Math.round((height)/25);
private int columns = Math.round(width/25);
public void addBuilding()
{
RememberBuildings.addBuilding();
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
Color transYellow = new Color (255, 255, 0, 59);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, maxX, maxY);
g2d.setColor(Color.WHITE);
g2d.fillRect(5, 5, 25, 25);
int a = 0;
for (int i =10; i<634; i+=(a+10))//buildings
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY-height, width, height);
g2d.fillRect(i, maxY-height, width, height);
rows = Math.round((height)/25);
columns = Math.round(width/25);
for (int j = 1; j<=columns; j++)//windows
{
for (int k = 1; k<=rows; k++)
{
g2d.setColor(Color.BLACK);
g2d.drawRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
if (Math.random()<0.7)
{
g2d.setColor(Color.YELLOW);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
else
{
g2d.setColor(Color.BLACK);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
g2d.setColor(transYellow);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
}
}
addBuilding();
a = width;
height = (int)(Math.random()*462+100);
width = (int)(Math.random()*100+100);
}
}
}
RememberBuildings class (the point of this is to add an instance to an ArrayList):
import java.util.*;
public class RememberBuildings
{
public static ArrayList<Buildings> buildingList = new ArrayList<Buildings>();
public static void addBuilding()
{
buildingList.add(new Buildings());
}
}
And finally my UFO class (creates the UFO flying by):
import java.awt.*;
import javax.swing.*;
public class UFO extends JPanel
{
private int x = 20; //x and y coordinates of the ball
private int y = 20;
private int xa = 1;
public void move() //Increase both the x and y coordinates
{
if (x + xa < 0) {
xa = 1;
}
if (x + xa > 784-75)
{
xa = -1;
}
x = x + xa;
}
public void paint(Graphics g)
{
super.paint(g); //Clears the panel, for a fresh start
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillOval(x,y,75,25); //Draw the ball at the desired point
}
}
Avoid overriding paint, use paintComponent instead. Always call the super paint method before you do any custom painting to ensure that the paint chain is maintained. See Painting in AWT and Swing and Performing Custom Painting for more details
Beware, Swing is not thread safe and it's unwise to update any component (or any variable that a component may rely on) from outside the context of the Event Dispatching Thread. A simple solution might be to use a Swing Timer instead of a while (true) loop and Thread.sleep. See How to use Swing Timers for more details.
You should also only create and modify UI components from within the context of the event dispatching thread, see Initial Threads for more details
If you have a problem with your code not working, you should consider providing a runnable example which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses. Providing code which is not runnable and is missing classes makes it difficult to know why it's not working and how to fix it.
A few things here:
To address the paintComponent note and view an example, check out this other thread: Concerns about the function of JPanel: paintcomponent()
There seems to be a bit of a disconnect between the logic you've got going and the object-oriented programming logic that I think will help sort things out (for general info on OOP: https://en.wikipedia.org/wiki/Object-oriented_programming):
What You've Got:
The Structure you've got going is as follows:
CityScape :: here's where you've extended JPanel and setup the main function
UFO :: an object class that represents 1 UFO
Building :: a class that has methods for drawing randomized buildings and calling methods in RememberBuildings
RememberBuildings :: I think this is intended to track buildings that have been drawn
The issue here is that your Building class's paint method continually draws multiple newly randomized buildings instead of a set building that retains its structure.
My Suggestion:
There are plenty of solutions to this issue and different ways to implement each solution, but my recommendation is to remodel your Building class in an OOP fashion, meaning that it would represent 1 single building (truer to the name of the class). This would contain a constructor that initializes all of the randomized dimensions of that single building once and draws that single building on the jpanel. Then you would need to keep an array or list of some sort in the cityscape that contains buildings that are part of the cityscape, eliminating the need for a "RememberBuildings" class. so roughly:
CityScape extends JPanel:
variables:
Building[] buildings; //might be useful to use an arraylist/stack/queue instead of an array depending on implementation
UFO craft;
constructor:
setup new Building objects and add to list buildings
initialize craft to new UFO
paintComponent:
calls the paint methods for each building & the ufo craft
Building:
variables:
int x, y; // position of building
int height, width; // of this building
constructor:
initializes x, y // probably needs to be inputed from CityScape with this setup
calc height and width randomly // stored in this.height/width
paint:
paints single building based on it's variables
//side-note, you'll probably need getters for the x/y/width to build each building from CityScape
Everything else should be much the same.
Good Luck !
So, every time Buildings#paint is called, it regenerates all the builds, which is done randomly.
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Color transYellow = new Color(255, 255, 0, 59);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, maxX, maxY);
g2d.setColor(Color.WHITE);
g2d.fillRect(5, 5, 25, 25);
int a = 0;
for (int i = 10; i < 634; i += (a + 10))//buildings
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY - height, width, height);
g2d.fillRect(i, maxY - height, width, height);
rows = Math.round((height) / 25);
columns = Math.round(width / 25);
for (int j = 1; j <= columns; j++)//windows
{
for (int k = 1; k <= rows; k++) {
g2d.setColor(Color.BLACK);
g2d.drawRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
if (Math.random() < 0.7) {
g2d.setColor(Color.YELLOW);
g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
} else {
g2d.setColor(Color.BLACK);
g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
g2d.setColor(transYellow);
g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
}
}
}
addBuilding();
a = width;
height = (int) (Math.random() * 462 + 100);
width = (int) (Math.random() * 100 + 100);
}
}
There's two ways you might be able to solve this, which you use will depend on what you want to achieve. You could render the buildings directly to a BufferedImage and simply paint that on each paint cycle or you could cache the information you need in order to re-create the buildings.
The BufferedImage approach is quicker, but can't be animated, so if you want to animate the buildings in some way (make the lights flicker), you will need to build up a series of information which allows you to simply repaint them.
I'm going for the second, as you've asked about painting assets from a ArrayList.
I started by translating your "paint" code into a single concept of a virtual building, which has also has information about it's own lights.
public class Building {
protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);
private int x, y, width, height;
private List<Light> lights;
public Building(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
lights = new ArrayList<>(25);
int rows = Math.round((height) / 25);
int columns = Math.round(width / 25);
for (int j = 1; j <= columns; j++)//windows
{
for (int k = 1; k <= rows; k++) {
Color color = null;
if (Math.random() < 0.7) {
color = Color.YELLOW;
} else {
color = TRANS_YELLOW;
}
lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
}
}
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.GRAY);
g2d.drawRect(x, y, width, height);
g2d.fillRect(x, y, width, height);
for (Light light : lights) {
light.paint(g2d);
}
}
public class Light {
private int x, y;
private Color color;
public Light(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(x, y, 20, 20);
g2d.setColor(color);
g2d.fillRect(x, y, 20, 20);
}
}
}
This allows you to generate the primary parameters for the Building and simple cache the results and when needed, simply paint it.
For example...
public class Buildings {
private int maxX = 784;
private int maxY = 712;
private List<Building> buildings;
public Buildings() {
buildings = new ArrayList<>(25);
for (int i = 10; i < 634; i += 10)//buildings
{
int width = (int) (Math.random() * 100 + 100);
int height = (int) (Math.random() * 350 + 100);
int x = i;
int y = maxY - height;
buildings.add(new Building(x, y, width, height));
}
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
for (Building building : buildings) {
building.paint(g2d);
}
}
}
I also changed your UFO class so it no longer extends from JPanel, as it just doesn't need to and is probably the primary cause of confusion with your painting.
I then updated your paint method in your CityScape to use paintComponent instead...
public class CityScape extends JPanel {
Buildings a = new Buildings();
UFO b = new UFO();
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
a.paint(g);
b.paint(g);
}
As a runnable example...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class CityScape extends JPanel {
Buildings a = new Buildings();
UFO b = new UFO();
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
a.paint(g);
b.paint(g);
}
public void move() {
b.move();
}
public static void main(String[] args) throws InterruptedException {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Frame");
CityScape jpe = new CityScape();
frame.add(jpe);
frame.setSize(800, 750);
frame.setBackground(Color.BLACK);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
System.out.println(frame.getContentPane().getSize());
Timer timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
jpe.move(); //Updates the coordinates
jpe.repaint(); //Calls the paint method
}
});
timer.start();
}
});
}
public class Buildings {
private int maxX = 784;
private int maxY = 712;
private List<Building> buildings;
public Buildings() {
buildings = new ArrayList<>(25);
for (int i = 10; i < 634; i += 10)//buildings
{
int width = (int) (Math.random() * 100 + 100);
int height = (int) (Math.random() * 350 + 100);
int x = i;
int y = maxY - height;
buildings.add(new Building(x, y, width, height));
}
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
for (Building building : buildings) {
building.paint(g2d);
}
}
}
public static class Building {
protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);
private int x, y, width, height;
private List<Light> lights;
public Building(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
lights = new ArrayList<>(25);
int rows = Math.round((height) / 25);
int columns = Math.round(width / 25);
for (int j = 1; j <= columns; j++)//windows
{
for (int k = 1; k <= rows; k++) {
Color color = null;
if (Math.random() < 0.7) {
color = Color.YELLOW;
} else {
color = TRANS_YELLOW;
}
lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
}
}
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.GRAY);
g2d.drawRect(x, y, width, height);
g2d.fillRect(x, y, width, height);
for (Light light : lights) {
light.paint(g2d);
}
}
public class Light {
private int x, y;
private Color color;
public Light(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(x, y, 20, 20);
g2d.setColor(color);
g2d.fillRect(x, y, 20, 20);
}
}
}
public class UFO {
private int x = 20; //x and y coordinates of the ball
private int y = 20;
private int xa = 1;
public void move() //Increase both the x and y coordinates
{
if (x + xa < 0) {
xa = 1;
}
if (x + xa > 784 - 75) {
xa = -1;
}
x = x + xa;
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillOval(x, y, 75, 25); //Draw the ball at the desired point
}
}
}
Related
I am trying to generate two continous graphs in a JPanel without blocking the JFrame, with Graphics and Graphics2D library.
Example of one: https://gyazo.com/2027cab6799d8416b1df8ee953d71b21
What I plan to do with this is to generate the graph at the same time that the user can change values and automatically the graph changes and is generated with these.
I am trying using Threads
Thread:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Thread3 extends Thread {
static int state;
double X = 0;
// Workspace state = 1
#Override
public void run() {
if (state == 1) {
// Graphic 2
Graphics g = Thread1.ws.graphic2.getGraphics();
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
double lastX = 3;
double lastY = 0;
double Y = 0;
double XonScreen = 0;
int width = Thread1.ws.graphic2.getWidth();
int height = Thread1.ws.graphic2.getHeight();
while (Thread1.active) {
if (Thread1.ws.graphic2Active) {
try {
Thread.sleep(1);
} catch (InterruptedException ex) {
}
g.clearRect(0, 0, width, height);
XonScreen = 0;
for (int j = 0; j < 48; j++) {
lastX = XonScreen;
lastY = Y;
X += 0.005 * 10000;
XonScreen += 0.005 * 10000;
Y = Math.sin(X) * 120;
g2.drawLine(0, height / 2, width, height / 2);
g2.drawLine((int) XonScreen, ((int) Y + height / 2), (int) lastX, ((int) lastY + height / 2));
g2.drawLine(0, 0, 0, height);
}
} else {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(Thread3.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
}
}
In this case I just set the JPanel to public and instantiated the JFrame in a static way. So far it works correctly but when running it, the graph does not update, it only updates when there is an event running.
https://gyazo.com/dc72d3c1a119b2ec93b442063f32c797
I tried using frame.repaint(); but it just makes all the JPanel flick. I also tried using a timer with an ActionPerformed event:
private Timer timer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
repaint();
}
});
but it also didn't work (I'm not sure if I implemented it correctly; if you have any suggestions about this, please let me know.) .
Do you know of any way that I can make the graphs update constantly and still manage the rest of the frame without any problems?
The lower source code, with the pictured example, from the post
Circular Progress Bar for Java Swing not working, is a great Swing feature.
I'd like to be able to use it with a "transparent" JFrame or glass pane
but the graphic "petals", in paint(), want to interact with the background,
so if the opacity of the background is very low, you can barely
see the "petals". Not being familiar with the Graphics2D functions there, I've taken many stabs in the dark to try to adjust the code, but no luck, so could someone who knows how those functions work,
suggest changes so that the "petals" don't interact with the background,
and start out solid white, and gradually fade, as the code does?
I also don't need any fade-in or fade-out delays, and I'm also
having difficulty with that, but if someone could just suggest
modifications for the "petals", that would be great!
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import javax.swing.*;
import javax.swing.plaf.LayerUI;
public class Loading_Test {
static final WaitLayerUI layerUI = new WaitLayerUI();
JFrame frame = new JFrame("JLayer With Animated Gif");
public Loading_Test() {
JPanel panel = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 300);
}
};
JLayer<JPanel> jlayer = new JLayer<>(panel, layerUI);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(jlayer);
frame.pack();
frame.setVisible(true);
layerUI.start();
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Loading_Test loading_Test = new Loading_Test();
}
});
}
}
class WaitLayerUI extends LayerUI<JPanel> implements ActionListener {
private boolean mIsRunning;
private boolean mIsFadingOut;
private Timer mTimer;
private int mAngle;
private int mFadeCount;
private int mFadeLimit = 15;
#Override
public void paint(Graphics g, JComponent c) {
int w = c.getWidth();
int h = c.getHeight();
super.paint(g, c); // Paint the view.
if (!mIsRunning) {
return;
}
Graphics2D g2 = (Graphics2D) g.create();
float fade = (float) mFadeCount / (float) mFadeLimit;
Composite urComposite = g2.getComposite(); // Gray it out.
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f * fade));
g2.fillRect(0, 0, w, h);
g2.setComposite(urComposite);
int s = Math.min(w, h) / 5;// Paint the wait indicator.
int cx = w / 2;
int cy = h / 2;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(s / 4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.setPaint(Color.white);
g2.rotate(Math.PI * mAngle / 180, cx, cy);
for (int i = 0; i < 12; i++) {
float scale = (11.0f - (float) i) / 11.0f;
g2.drawLine(cx + s, cy, cx + s * 2, cy);
g2.rotate(-Math.PI / 6, cx, cy);
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, scale * fade));
}
g2.dispose();
}
#Override
public void actionPerformed(ActionEvent e) {
if (mIsRunning) {
firePropertyChange("tick", 0, 1);
mAngle += 3;
if (mAngle >= 360) {
mAngle = 0;
}
if (mIsFadingOut) {
if (--mFadeCount == 0) {
mIsRunning = false;
mTimer.stop();
}
} else if (mFadeCount < mFadeLimit) {
mFadeCount++;
}
}
}
public void start() {
if (mIsRunning) {
return;
}
mIsRunning = true;// Run a thread for animation.
mIsFadingOut = false;
mFadeCount = 0;
int fps = 24;
int tick = 1000 / fps;
mTimer = new Timer(tick, this);
mTimer.start();
}
public void stop() {
mIsFadingOut = true;
}
#Override
public void applyPropertyChange(PropertyChangeEvent pce, JLayer l) {
if ("tick".equals(pce.getPropertyName())) {
l.repaint();
}
}
}
One problem I see is that the code is setting the composite in the wrong place in the loop. It works, but as you've discovered, it's difficult to maintain or change.
g2.setComposite is being called at the end of the loop. This sets the alpha for the next petal drawn. This means there is no easy change you can make to adjust the alpha of the very first petal.
First, I would make the code more in line with the way humans think (at least, the way I think): Set the alpha of the line you're about to draw, right before you draw it:
for (int i = 0; i < 12; i++) {
float scale = (12 - i) / 12f;
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, scale * fade));
g2.drawLine(cx + s, cy, cx + s * 2, cy);
g2.rotate(-Math.PI / 6, cx, cy);
}
Now, making it work with any arbitrary background alpha is easy. We merely adjust the value of scale:
float componentAlpha = 0.5f;
for (int i = 0; i < 12; i++) {
float scale = (12 - i) / 12f;
// Give petals the same relative alpha as the component
// they're overlaying.
scale *= componentAlpha;
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, scale * fade));
g2.drawLine(cx + s, cy, cx + s * 2, cy);
g2.rotate(-Math.PI / 6, cx, cy);
}
I would like to know if there is any algorithm that does something like this:
Given a specific surface it divides it into smaller rectangles of the same size.
Something like this example figure:
The grey area is the surface, and the red squares is the partition itself.
I am thinking if there is a optimized way to do this.
A very bad approach would be a for loop in all the pixels and check if there is a rectangle for that specific spot, if not, would create a rectangle, and so on..
Maybe someone knows a algorithm already done? or a better solution?
Thanks alot in advance ;)
Here's one way to go about it.
Create a mask of the image. (I just used Photoshop)
Steal AndrewThompson's code for Creating an Area from an Image and use it to create an Area of the image.
Area imageArea = getOutline(Color.BLACK, imageMask);
Create a grid Rectangle2D objects for the entirety of the image.
Rectangle2D[][] grid = new Rectangle2D[rows][cols];
for (int i = 0; i < grid.length; i++) {
int y = i * CELL_SIZE;
for (int j = 0; j < grid[i].length; j++) {
int x = j * CELL_SIZE;
grid[i][j] = new Rectangle2D.Double(x, y, cellSize, cellSize);
}
}
Once you have the grid, you can just traverse the Rectangle2D objects and check if the Area.contains each individual Rectangle2D in the grid, and you can just add it to a List<Rectangle2D>. Only rectangles contained in the area will be added, giving you your final grid of rectangles to draw. In the example below, I just painted to rectangles as a visual.
for (Rectangle2D[] rects : imageGrid) {
for (Rectangle2D rect : rects) {
if (imageArea.contains(rect)) {
g2.drawRect((int) rect.getX(), (int) rect.getY(),
(int) rect.getWidth(), (int) rect.getHeight());
}
}
}
Full example
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SquaresInArea extends JPanel {
private static final int CELL_SIZE = 30;
BufferedImage image;
BufferedImage imageMask;
Area imageArea;
Rectangle2D[][] imageGrid;
public SquaresInArea() {
try {
image = ImageIO.read(getClass().getResource("/resources/floorplan.png"));
imageMask = ImageIO.read(getClass().getResource("/resources/floorplan-black.png"));
} catch (IOException ex) {
Logger.getLogger(SquaresInArea.class.getName()).log(Level.SEVERE, null, ex);
}
imageArea = getOutline(Color.BLACK, imageMask);
imageGrid = createGrid();
}
private Rectangle2D[][] createGrid() {
int width = image.getWidth();
int height = image.getHeight();
int rows = height / CELL_SIZE;
int cols = width / CELL_SIZE;
Rectangle2D[][] grid = new Rectangle2D[rows][cols];
for (int i = 0; i < grid.length; i++) {
int y = i * CELL_SIZE;
for (int j = 0; j < grid[i].length; j++) {
int x = j * CELL_SIZE;
grid[i][j] = new Rectangle2D.Double(x, y, CELL_SIZE, CELL_SIZE);
}
}
return grid;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(image, 0, 0, this);
g2.setColor(Color.YELLOW);
g2.setStroke(new BasicStroke(3f));
for (Rectangle2D[] rects : imageGrid) {
for (Rectangle2D rect : rects) {
if (imageArea.contains(rect)) {
g2.drawRect((int) rect.getX(), (int) rect.getY(),
(int) rect.getWidth(), (int) rect.getHeight());
}
}
}
}
#Override
public Dimension getPreferredSize() {
return image == null ? new Dimension(300, 300)
: new Dimension(image.getWidth(), image.getHeight());
}
private Area getOutline(Color target, BufferedImage bi) {
// construct the GeneralPath
GeneralPath gp = new GeneralPath();
boolean cont = false;
int targetRGB = target.getRGB();
for (int xx = 0; xx < bi.getWidth(); xx++) {
for (int yy = 0; yy < bi.getHeight(); yy++) {
if (bi.getRGB(xx, yy) == targetRGB) {
if (cont) {
gp.lineTo(xx, yy);
gp.lineTo(xx, yy + 1);
gp.lineTo(xx + 1, yy + 1);
gp.lineTo(xx + 1, yy);
gp.lineTo(xx, yy);
} else {
gp.moveTo(xx, yy);
}
cont = true;
} else {
cont = false;
}
}
cont = false;
}
gp.closePath();
// construct the Area from the GP & return it
return new Area(gp);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new SquaresInArea());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
here's another view for clarity
private final BasicStroke thin = new BasicStroke(1f);
private final BasicStroke thick = new BasicStroke(4f);
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(image, 0, 0, this);
for (Rectangle2D[] rects : imageGrid) {
for (Rectangle2D rect : rects) {
if (imageArea.contains(rect)) {
g2.setStroke(thick);
g2.setColor(Color.GREEN);
g2.draw(rect);
} else {
g2.setStroke(thin);
g2.setColor(Color.RED);
g2.draw(rect);
}
}
}
}
Do you just want to fill it with squares - or do you want to fill it with the optimal number of squares?
An algorithm for the second is harder.
For the first just step through the image a square-size at a time. If the pixel at that point is filled then scan the full square, if it's all filled in then draw the square. If not then step to the next point.
i.e. if squares are 10*10 pixels:
for (int x=0;x<width;x+=SQUARE_SIZE) {
for (int y=0;y<height;y+=SQUARE_SIZE) {
// Now check if you can put a valid square here, if so draw it
}
}
Here's my original question on SO kindly answered. The height is now set at what I think is the correct size. But I can't see the bottom 2/3s of the panel.
I have read, and asked, and mused, and experimented, but I still cannot find an answer. I don't need code, just a little help.
My JFrame class;
public Frame(String title) throws FileNotFoundException {
super(String.format("Title", title));
this.panel = new Panel();
this.panel.drawLinesAndTab();
this.panel.setSize(this.panel.getPreferredSize());
this.panel.validate();
this.scroller = new JScrollPane(this.panel);
//this.scroller.setPreferredSize(new Dimension(this.panel.getPreferredSize()));
this.scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
this.scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
//this.scroller.setSize(new Dimension(this.panel.getPreferredSize()));
this.scroller.getVerticalScrollBar().setUnitIncrement(20);
this.getContentPane().add(this.scroller);
//this.pack();
}
and this is the JPanel class. I know it's huge, and I do have plans to re-write this code, but I'm under time limitations and have to try and get it at least seeing all of the output.
public Panel() throws FileNotFoundException {
this.tab = new ReadTabFile("tabSource.txt");
this.image = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);
Graphics g = this.image.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.image.getWidth(), this.image.getHeight());
setBorder(BorderFactory.createLineBorder(Color.black));
this.setFocusable(true);
}
public void drawLinesAndTab() {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
this.list = tab.readTabCode();
this.a = 20;
this.b = 100;
this.c = 60;
this.x = 40;
this.y = 100;
this.beginBarlineX = 20;
this.beginBarlineY = 100;
this.endBarY = 980;
this.endBarY = 100;
this.title = tab.getTitle();
g.drawString(this.title, 40, 20);
for (int i = 0; i < this.list.size(); i++) {
Bar theBar = (Bar) this.list.get(i);
drawBarline(a, b, a, b + 125);
ArrayList<String> stuff = theBar.getLinesInBar();
for (int j = 0; j < stuff.size(); j++) {
String line = stuff.get(j);
theFlag = line.substring(0, 1);
theNotes = line.substring(1, line.length());
if (newLine = true) {
}
try {
System.out.println(theNotes);
if (c <= (width - 40)) {
newLine = false;
String zero = theFlag;
drawFlag(zero, x + 5, y - 20);
String one = theNotes.substring(0, 1);
g.drawLine(a, b, c, b);
drawLetter(one, x, y);
String two = theNotes.substring(1, 2);
drawLetter(two, x, y += 25);
g.drawLine(a, b += 25, c, b);
String three = theNotes.substring(2, 3);
drawLetter(three, x, y += 25);
g.drawLine(a, b += 25, c, b);
String four = theNotes.substring(3, 4);
drawLetter(four, x, y += 25);
g.drawLine(a, b += 25, c, b);
String five = theNotes.substring(4, 5);
drawLetter(five, x, y += 25);
g.drawLine(a, b += 25, c, b);
String six = theNotes.substring(5, 6);
drawLetter(six, x, y += 25);
g.drawLine(a, b += 25, c, b);
this.repaint();
b -= 125;
y -= 125;
x += 40;
a += 40;
c += 40;
} else {
if (height < (b - 100)) {
height += 205;
}
newLine = true;
a = 20;
x = 20;
b += 225;
c = 60;
y += 225;
beginBarlineX = 20;
beginBarlineY += 100;
endBarX += 100;
endBarY = 100;
this.repaint();
}
} catch (Exception ex) {
System.err.println(ex + " within if drawtab/line for loop");
}
}
}
}
public void drawBarline(int xTop, int yTop, int xBot, int yBot) {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
g.drawLine(xTop, yTop, xBot, yBot);
}
public Point makeBarline(int xTop, int yTop, int xBot, int yBot) {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
g.drawLine(xTop, yTop, xBot, yBot);
return (new Point());
}
public Point drawLetter(String letter, int x, int y) throws FontFormatException, IOException {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
g.setFont(letterFont(letter).deriveFont(20.0f));
g.drawString(letter, x, y);
return (new Point());
}
public Point drawFlag(String letter, int x, int y) throws FontFormatException, IOException {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
g.setFont(flagFont(letter).deriveFont(30.0f));
g.drawString(letter, x, y);
return (new Point());
}
public Font letterFont(String fontString) throws FontFormatException, IOException {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
if (!Character.isDigit(fontString.charAt(0))) {
this.letterFont = Font.createFont(Font.TRUETYPE_FONT, new File("LeRoy.ttf"));
g.getFontMetrics(this.letterFont);
g.setFont(this.letterFont);
return this.letterFont;
} else {
return null;
}
}
public Font flagFont(String fontString) throws FontFormatException, IOException {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
if (!Character.isDigit(fontString.charAt(0))) {
this.flagFont = Font.createFont(Font.TRUETYPE_FONT, new File("LeroyLuteNotes1.ttf"));
g.getFontMetrics(this.flagFont);
g.setFont(this.flagFont);
return this.flagFont;
} else {
return null;
}
}
#Override
public Dimension getPreferredSize() {
return (new Dimension(this.width, this.height));
}
public BufferedImage getImage() {
return this.image;
}
#Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics g = graphics.create();
g.drawImage(this.image, 0, 0, null);
}
}
I'm not sure what you are attempting to do with that code. It looks like you might be trying to do some custom painting on top of an image. If so then I have the following suggestions:
public Dimension getPreferredSize() {
return (new Dimension(this.width, this.height));
}
This makes no sense, you are saying that the preferred size is equal to the actual size of the component. The preferred size should be the size of the image.
Your custom painting code is completely wrong. All custom painting code should be done from the paintComponent() method. So first you would paint the image as the background of your component. Then you would invoke the other painting methods to paint stuff on top of the image. You would pass the Graphics object from the paintComponent() method to all of these other painting methods, instead of using image.getGraphics().
Start with a simple custom painting example from the Swing tutorial on Custom Painting. Once you learn the basics you customize your code one step at a time. That is first paint the background. Make sure the size is correct and scrolling works. Then move to the next step and add another method to paint something else on top of the image.
I never understand why people write hundreds of lines of code without doing basic testing along the way to make sure the code is working.
I am creating line graph using outputs from a thread, the threads are simulations of incoming and outgoing bill that run over a course of 52 seconds and this will be dipicted on a line graph as shown below to show the bank balance over the 52 seconds!
Currently the program runs fine, when I click start the point gets updated but every a new point is placed on the graph the previous one disappears. How i can i keep all the points on the graph.
I would assume i would have to create 2 new int variable... prevX & prevY.
import java.awt.*;
import javax.swing.*;
public class DrawPanel extends JPanel {
private static final int X_AXIS_LENGTH = 700;
private static final int Y_AXIS_LENGTH = 230; // could be changed
private static final int X_AXIS_OFFSET = 200;
private static final int Y_AXIS_OFFSET = 85;
private static final int PanelHeight = 365;
private static final int PanelWidth = 1000;
public DrawPanel() {
this.setBackground(Color.white);
this.setPreferredSize(new Dimension(PanelWidth, PanelHeight));
}
public void paintComponent(Graphics g) {
int y = ControlPanel.bk1.getBalance(); // balance
int x = ControlPanel.bk1.getWeek(); // weeks //
int prevX, prevY;
int maxX = ContentPanel.controlPanel.getDuration();
int maxY = 100000;
int Xleft = 200;
int Xright = 900;
int Ytop = 50;
int Ybottom = 330;// defining axis
while (ControlPanel.bk1.getBalance() > maxY) {
int i = maxY / 4;
maxY = maxY + i;
}
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g2);
g2.setColor(Color.BLUE);
BasicStroke pen = new BasicStroke(4F);
g2.setStroke(pen);
g2.drawLine(Xleft, Ytop, Xleft, Ybottom); // set axis
g2.drawLine(Xleft, 280, Xright, 280);
int i = X_AXIS_OFFSET + (X_AXIS_LENGTH / 2);
int ii = X_AXIS_OFFSET + (X_AXIS_LENGTH / 4);
int iii = ((X_AXIS_LENGTH / 4)) * 3 + X_AXIS_OFFSET;
BasicStroke spaces = new BasicStroke(1F);
g2.setStroke(spaces);
g2.drawLine(i, 280, i, 300);
g2.drawLine(ii, 280, ii, 300);
g2.drawLine(iii, 280, iii, 300);
g2.setStroke(pen);
Font f = new Font("Serif", Font.BOLD, 14);
g2.setFont(f);
g2.drawString("Account Balance (£)", 35, 200);
g2.drawString("Elapsed Time (Weeks)", 475, 340);
g2.setColor(Color.BLACK);
String maxXDisplay = Integer.toString(maxX);
String maxYDisplay = Integer.toString(maxY);
g2.drawString(maxYDisplay, 160, 45);
g2.drawString(maxXDisplay, 900, 300);
// retrieve values from your model for the declared variables
// calculate the coords line on the canvas
double balance = PanelHeight
- ((((double) y / maxY) * Y_AXIS_LENGTH) + Y_AXIS_OFFSET);
double weeks = (((double) x / maxX) * X_AXIS_LENGTH) + X_AXIS_OFFSET;
int xPos = (int) Math.round(weeks);
int yPos = (int) Math.round(balance); // changing back to int to be used
// in drawing oval
g2.setColor(Color.RED);
g.drawOval(xPos, yPos, 2, 2);
}
public void reDraw() {
repaint();
}
}
You appear to be only trying to plot one point in your paintComponent method:
g.drawOval(xPos, yPos, 2, 2);
Usually you'll loop through a for loop drawing all the points in this method. For example something like:
for (int j = 0; j < maxPointCount; j++) {
x = someMethodToGetX(j);
y = someMethodToGetY(j);
double balance = PanelHeight - ((((double) y / maxY) *
Y_AXIS_LENGTH) + Y_AXIS_OFFSET);
double weeks = (((double) x / maxX) * X_AXIS_LENGTH) +
X_AXIS_OFFSET;
int xPos = (int) Math.round(weeks);
int yPos = (int) Math.round(balance);
g2.setColor(Color.RED);
g.drawOval(xPos, yPos, 2, 2);
}
Edit 1
Regarding your recent comment:
Tryed that for loop and it makes no difference to the program
My code above is certainly not code that can be cut and pasted into your program and be expected to work, but rather is only to be seen as an example of a concept. A for loop will work if implemented correctly as it's worked for me many times, but yours is not working, so we have to fix your implementation, and in order to do that, we need more information:
How are you generating your data points?
Are you using a Swing Timer to imitate real-time collection of data?
You will need to store your data points as you collect them so your paintComponent can iterate over them. How are you storing your data points? Is it in an ArrayList? Can we see that code?
Once we see all this, can we see the code where you try to implement a for loop to draw all the data points?
You will need to make an edit to your question to show this new information. If you do this, please notify me by commenting in this answer.
Edit 2
This is a more complete example of what I'm describing, one with a functioning for loop that draws all scaled data points. Of course none of this code can be copied and pasted into your app, but hopefully the concepts contained can be transferred. Please ask if anything looks confusing:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class TestShowGraph {
private static final int MAX_POINTS = 30;
private static final int TIMER_DELAY = 800;
private static void createAndShowGui() {
ShowGraph showGraphPanel = new ShowGraph(MAX_POINTS);
TimerListener timerListener = new TimerListener(MAX_POINTS, showGraphPanel);
JFrame frame = new JFrame("TestShowGraph");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(showGraphPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
new Timer(TIMER_DELAY, timerListener).start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class TimerListener implements ActionListener {
private static final double EXP_MULTIPLIER = 0.2;
// array of points created in constructor to hold data that
// will eventually be displayed in real time.
// A Swing Timer will copy a point into the pointsList above.
private Point2D[] initPoints;
private int maxPoints;
private int count = 0;
private ShowGraph showGraph;
public TimerListener(int maxPoints, ShowGraph showGraph) {
initPoints = new Point2D[maxPoints];
this.maxPoints = maxPoints;
this.showGraph = showGraph;
// create all data points that will eventually be
// graphed. This is to simulate real-time data collection
for (int i = 0; i < initPoints.length; i++) {
double x = (double) i / initPoints.length;
double y = 1.0 - Math.exp(-1.0 * i * EXP_MULTIPLIER);
initPoints[i] = new Point2D.Double(x, y);
}
}
public void actionPerformed(ActionEvent e) {
if (count < maxPoints) {
// simply push data from initPoints into the list that will
// be used to draw the graph
showGraph.addPoint(initPoints[count]);
count++;
} else {
// unless we've run out of points. Then simply start over
count = 0;
showGraph.clearPointsList();
}
// repaint so that the GUI will show the points
showGraph.repaint();
}
}
#SuppressWarnings("serial")
class ShowGraph extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 600;
private static final int BORDER_GAP = 50;
private static final Color AXIS_COLOR = Color.blue;
private static final Color POINTS_COLOR = Color.red;
private static final Color BACKGRND_COLOR = Color.white;
private static final Stroke AXIS_STROKE = new BasicStroke(3f);
private static final Stroke POINTS_STROKE = new BasicStroke(2f);
private static final double X_SCALE = PREF_W - 2 * BORDER_GAP;
private static final double Y_SCALE = PREF_H - 2 * BORDER_GAP;
private static final int POINT_RADIUS = 3;
// list that the paintComponent method loops through to
// draw points
private List<Point2D> pointsList = new ArrayList<Point2D>();
public ShowGraph(int maxPoints) {
setBackground(BACKGRND_COLOR);
}
public void addPoint(Point2D point2d) {
pointsList.add(point2d);
}
public void clearPointsList() {
pointsList.clear();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
drawAxises(g2);
drawPoints(g2);
}
private void drawAxises(Graphics g2) {
// derive a Graphics2D object from the one provided by the
// JVM so we can change settings on it without effecting
// the Graphics object provided by the JVM
Graphics2D g2Axises = (Graphics2D) g2.create();
g2Axises.setStroke(AXIS_STROKE);
g2Axises.setColor(AXIS_COLOR);
int x1XAxis = BORDER_GAP;
int y1XAxis = PREF_H - BORDER_GAP;
int x2XAxis = PREF_W - BORDER_GAP;
int y2XAxis = PREF_H - BORDER_GAP;
g2Axises.drawLine(x1XAxis, y1XAxis, x2XAxis, y2XAxis);
int x1YAxis = BORDER_GAP;
int y1YAxis = BORDER_GAP;
int x2YAxis = BORDER_GAP;
int y2YAxis = PREF_H - BORDER_GAP;
g2Axises.drawLine(x1YAxis, y1YAxis, x2YAxis, y2YAxis);
g2Axises.dispose(); // because we derived this we must dispose it
}
private void drawPoints(Graphics2D g2) {
Graphics2D g2Points = (Graphics2D) g2.create();
g2Points.setStroke(POINTS_STROKE);
g2Points.setColor(POINTS_COLOR);
for (Point2D p : pointsList) {
// p points hold data between 0 and 1
// we must scale our points to fit the display
// before displaying them
int pX = (int)(X_SCALE * p.getX()) + BORDER_GAP;
int pY = PREF_H - (int)(Y_SCALE * p.getY()) - BORDER_GAP;
// displayed the scaled points
int radius = POINT_RADIUS;
g2Points.drawOval(pX - radius, pY - radius, 2 * radius, 2 * radius);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
Luck.