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
}
}
Related
I am trying to create a simple game using Swing with Java have one question about drawing shapes using the built-in objects. Should the objects be created in the constructor and just draw in the paint method or should they be both created and drawn in the paint method?
See below for an example:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
public class Window extends JFrame{//inheriting JFrame
JFrame f;
private Dimension windowSize;
private final int cols, rows; //The number of circles in each col/row
private Ellipse2D[][] circles;
public Window ()
{
cols = 5;
rows = 5;
//Set window initial size
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
windowSize = new Dimension();
windowSize.width = (int)(screenSize.width * .4);
windowSize.height = windowSize.width;
setSize(windowSize);
circles = new Ellipse2D[rows][cols];
initShapes();
setLayout(null);
setVisible(true);
}
#Override
public void paint (Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
RenderingHints rh = new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHints(rh);
for(int i = 0; i < circles.length; i++)
{
for(int j = 0; j < circles[0].length; j++)
{
g2d.fill(circles[i][j]);
g2d.draw(circles[i][j]);
}
}
}
private void initShapes()
{
int r = (int)(windowSize.width * .015);
//The frame is the region of the screen that will contain the game
//(excluding a border/margin of 10% all around)
int frameX = (int)(windowSize.width * .7);
int frameY = (int)(windowSize.height * .7);
for(int i = 0; i < circles.length; i++)
{
for(int j = 0; j < circles[0].length; j++)
{
int x = (int)(windowSize.width*.15) + (frameX/(cols-1)) * j;
int y = (int)(windowSize.height*.15) + (frameY/(rows-1)) * i;
circles[i][j] = new Ellipse2D.Double(x-r, y-r, 2*r, 2*r);
}
}
}
public static void main(String[] args)
{
Window window = new Window();
}
}
Here I am creating the Ellipse2D objects in the constructor function and storing them in an array. Then, in the paint method, I iterate through the array and paint each one. The other way that I have seen it is more like the following:
//...
#Override
public void paint (Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
RenderingHints rh = new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHints(rh);
int r = (int)(windowSize.width * .015);
for(int i = 0; i < circles.length; i++)
{
for(int j = 0; j < circles[0].length; j++)
{
int x = (int)(windowSize.width*.15) + (frameX/(cols-1)) * j;
int y = (int)(windowSize.height*.15) + (frameY/(rows-1)) * i;
Ellipse2D ellipse = new Ellipse2D.Double(x-r, y-r, 2*r, 2*r);
g2d.fill(ellipse );
g2d.draw(ellipse );
}
}
}
}
//...
This feels wrong to have the objects be created in the paint method which is recalled repeatedly, but I have seen this. Essentially, my question is just which organization is either more efficient or just best practice.
I want to make the displayed image repainted for everytime i change the slider position.
I've already made the Every change of the Variable from JSlider is added to the pixel. But i just don't know how to repaint it.
package training;
import javax.swing.*;
import java.io.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
import javax.swing.plaf.SliderUI;
import java.awt.Color;
import java.util.Arrays;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Training extends JPanel {
public BufferedImage image;
double maxw, maxh;
double w, h, ratio;
int warna, red, green, blue, abu, value;
int forT1, forT2;
int[][] bmpR;
int[][] bmpG;
int[][] bmpB;
int[][] alpha;
public Training () {
super();
try {
image = ImageIO.read(new File("src/training/V.jpg"));
}
catch (IOException e) {
//Not handled.
}
maxw = 750;
maxh = 600;
w = image.getWidth();
h = image.getHeight();
bmpR = new int[(int)w][(int)h];
bmpG = new int[(int)w][(int)h];
bmpB = new int[(int)w][(int)h];
if (w > h) {
if (w > maxw) {
ratio = maxw / w;
h = h * ratio; // Reset height to match scaled image
w = w * ratio;
}
}
if (w <= h) {
if (h > maxh) {
ratio = maxh / h;
w = w * ratio; // Reset height to match scaled image
h = h * ratio;
}
}
try {
for( int i = 0; i < w; i++ ) {
for( int j = 0; j < h; j++ ) {
Color c = new Color(image.getRGB(i, j));
bmpR [i][j] = c.getRed();
bmpG [i][j] = c.getGreen();
bmpB [i][j] = c.getBlue();
// alpha = c.getAlpha();
}
}
System.out.println(bmpB[40][40]);
}
catch (Exception e) {
System.out.println("Terjadi kesalahan saat mengambil data pixel");
e.printStackTrace();
return;
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Image i = image.getScaledInstance((int)w, (int)h,Image.SCALE_SMOOTH);
g.drawImage(i, 20, 20, null);
}
public static void main(String[] args) {
final Training ns = new Training();
System.out.println("User dir: " + System.getProperty("user.dir"));
JFrame f = new JFrame("Window");
JPanel p = new Training();
f.setSize(1100, 600);
p.setSize(750, 600);
f.add(p);
JSlider Temp = new JSlider(-50, 50, 0);
Temp.setMajorTickSpacing(10);
Temp.setMinorTickSpacing(1);
Temp.addChangeListener(new ChangeListener () {
public void stateChanged(ChangeEvent evt) {
JSlider Temp = (JSlider) evt.getSource();
if (Temp.getValueIsAdjusting()) {
ns.value = Temp.getValue();
for(ns.forT1 = 0; ns.forT1 < ns.w; ns.forT1++ ) {
for(ns.forT2 = 0; ns.forT2 < ns.h; ns.forT2++ ) {
ns.bmpB[ns.forT1][ns.forT2] = ns.bmpB[ns.forT1][ns.forT2] - ns.value;
if (ns.bmpB[ns.forT1][ns.forT2] > 255) {
ns.bmpB[ns.forT1][ns.forT2] = 255;
}
if (ns.bmpB[ns.forT1][ns.forT2] < 0) {
ns.bmpB[ns.forT1][ns.forT2] = 0;
}
ns.bmpR[ns.forT1][ns.forT2] = ns.bmpR[ns.forT1][ns.forT2] + ns.value;
if (ns.bmpR[ns.forT1][ns.forT2] > 255) {
ns.bmpR[ns.forT1][ns.forT2] = 255;
}
if (ns.bmpR[ns.forT1][ns.forT2] < 0) {
ns.bmpR[ns.forT1][ns.forT2] = 0;
}
}
}
}
ns.repaint();
}
});
f.add(Temp, BorderLayout.EAST);
f.setVisible(true);
}
}
Did i misplaced the ChangeListener or should i put paintComponent method after the change listener happens?
The random indentation of your code is making it hard to figure out exactly what's going on, but I don't see repaint() anywhere inside your ChangeListener. You need to use repaint() to trigger the repainting of your component.
I had a task to draw checkers board. Here is my frame class
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JTabbedPane;
public class AppFrame extends JFrame {
public AppFrame() {
setTitle("Kółko i kwadracik");
setSize(1000, 1500);
setLocationRelativeTo(null);
initGUI();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void initGUI() {
setLayout(new BorderLayout());
JTabbedPane tabPane = new JTabbedPane();
tabPane.addTab("Plansza", new PlanszaGry());
tabPane.addTab("Obrazek", new PanelZObrazkiem());
tabPane.addTab("Wykres", new Wykres());
tabPane.addTab("Warcaby", new Warcaby());
tabPane.addTab("4 Warcaby", new Warcaby4());
add(tabPane, BorderLayout.CENTER);
}
}
Than a class to create single checkers board, it's the one new Warcaby()
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import javax.swing.JPanel;
public class Warcaby extends JPanel {
public void paint(Graphics g) {
super.paint(g);
setBackground(Color.WHITE);
Graphics2D g2 = (Graphics2D) g;
Stroke defaultStroke = g2.getStroke();
int y = 9; // tu ustawiamy liczbę linii (czyli w sumie wilekość planszy)
// linie planszy do gry
for (int i = 0; i <= y; i++) {
float dash[] = { 10.0f };
Stroke lineStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
g2.setStroke(lineStroke);
g2.setColor(Color.BLACK);
int x = i * 100;
g2.draw(new Line2D.Double(100 + x, 100, 100 + x, 100 * y));// linie
// pionowe
g2.draw(new Line2D.Double(100, 100 + x, 100 * y, 100 + x)); // linie
// poziome
}
// Plansza do gry (czarne/białe pola)
for (int a = 1; a < y; a++) {
if (a % 2 != 0) {
for (int b = 1; b < y; b++) {
if (b % 2 == 0) {
g.setColor(Color.black);
g.fillRect(b * 100, a * 100, 100, 100);
}
}
} else {
for (int b = 1; b < y; b++) {
if (b % 2 != 0) {
g.setColor(Color.black);
g.fillRect(b * 100, a * 100, 100, 100);
}
}
}
}
}
}
My next task is to draw 4 boards next to eachother, teacher gave me a hint to create method drawing one board with information about it's position. I can't figure it out how to even start. I've start with this:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import javax.swing.JPanel;
public class Warcaby4 extends JPanel {
public void Warcaby(Graphics g, int x, int y) {
super.paint(g);
setBackground(Color.WHITE);
Graphics2D g2 = (Graphics2D) g;
Stroke defaultStroke = g2.getStroke();
float dash[] = { 10.0f };
Stroke lineStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
g2.setStroke(lineStroke);
g2.setColor(Color.BLACK);
for (int i = 0; i <= y; i++) {
x = i * 100;
g2.draw(new Line2D.Double(100 + x, 100, 100 + x, 100 * y));// linie
// pionowe
g2.draw(new Line2D.Double(100, 100 + x, 100 * y, 100 + y)); // linie
// poziome
}
// Plansza do gry (czarne/białe pola)
for (int a = 1; a < y; a++) {
if (a % 2 != 0) {
for (int b = 1; b < y; b++) {
if (b % 2 == 0) {
g.setColor(Color.black);
g.fillRect(b * 100, a * 100, 100, 100);
}
}
} else {
for (int b = 1; b < y; b++) {
if (b % 2 != 0) {
g.setColor(Color.black);
g.fillRect(b * 100, a * 100, 100, 100);
}
}
}
}
}
}
Now I don't know where and how call out it 4 times, am I even doing it right? Please give me some suggestions. :)
My next task is to draw 4 boards next to eachother, teacher gave me a hint to create method drawing one board with information about it's position. I can't figure it out how to even start. I've start with this:
Why not just re-use the component you already have? For example, using a GridLayout you could just create as many components as you need...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class AppFrame {
public static void main(String[] args) {
new AppFrame();
}
public AppFrame() {
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("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(2, 2));
frame.add(new Warcaby());
frame.add(new Warcaby());
frame.add(new Warcaby());
frame.add(new Warcaby());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Warcaby extends JPanel {
public Warcaby() {
setBackground(Color.WHITE);
setBorder(new EmptyBorder(5, 5, 5, 5));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// setBackground(Color.WHITE);
Graphics2D g2 = (Graphics2D) g;
Insets insets = getInsets();
int horontialPadding = insets.left + insets.right;
int verticalPadding = insets.top + insets.bottom;
int width = getWidth() - horontialPadding;
int height = getHeight() - verticalPadding;
int size = Math.min(width, height) / 10;
int xOffset = insets.left + ((width - (size * 10)) / 2);
int yOffset = insets.top + ((height - (size * 10)) / 2);
for (int vertical = 0; vertical < 10; vertical++) {
for (int horzontial = 0; horzontial < 10; horzontial++) {
int x = horzontial * size;
int y = vertical * size;
g2.setColor(Color.WHITE);
if (vertical % 2 == 0) {
if (horzontial % 2 == 0) {
g2.setColor(Color.BLACK);
}
} else if (horzontial % 2 != 0) {
g2.setColor(Color.BLACK);
}
g2.fillRect(xOffset + x, yOffset + y, size, size);
}
}
g2.setColor(Color.BLACK);
g2.drawRect(xOffset, yOffset, size * 10, size * 10);
}
}
}
Sorry, I optimised your drawing code, now it can resize based on the available space.
Also, you should NEVER update the state of the UI from within any paint method (like calling setBackground), this is a very bad idea which could lead into an infinite paint loop which would consume your CPU cycles and render your system unusable (yes, I've done this)
As a general rule of thumb, you should prefer paintComponent over paint. See Painting in AWT and Swing and Performing Custom Painting for more details
I've managed to finish this task on my own. Here's the code in case someone will have similar problem.
public void paint(Graphics g) {
super.paint(g);
rysujWarcaby((Graphics) g, 50, 50);
rysujWarcaby((Graphics) g, 50, 500);
rysujWarcaby((Graphics) g, 500, 50);
rysujWarcaby((Graphics) g, 500, 500);
}
public void rysujWarcaby(Graphics g, int x, int y) {
int xx = 1;
int nr = 9;
setBackground(Color.YELLOW);
Graphics2D g2 = (Graphics2D) g;
Stroke lineStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
g2.setStroke(lineStroke);
g2.setColor(Color.BLACK);
for (int i = 0; i < nr; i++) {
xx = i * 50;
g2.draw(new Line2D.Double(x + xx, y, xx + x, y + 50 * 8));
g2.draw(new Line2D.Double(x, y + xx, x + 50 * 8, y + xx));
}
}
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
}
}
}
I'm a beginner whit Java experience so I hope you can help me.
I want to read the number of steps from a JTextfield. But how can you make the stairs variable?
Graphics g = textP.getGraphics();
int x = 5;
int y = 20;
int width = 10;
int height = 10;
For loop 1 - whil give a drawRect of 6 pieces
for (int i = 0; i < 6; i++) {
g.drawRect(x, y * i, width, height);
}
For loop 2 - whil give a drawRect of 5 pieces
for (int i = 1; i < 6; i++) {
g.drawRect(x + 15, y * i, width, height);
}
For loop 3 - whil give a drawRect of 4 pieces
for (int i = 2; i < 6; i++) {
g.drawRect(x + 30, y * i, width, height);
}
For loop 4 - whil give a drawRect of 3 pieces
for (int i = 3; i < 6; i++) {
g.drawRect(x + 45, y * i, width, height);
}
For loop 5 - whil give a drawRect of 2 pieces
for (int i = 4; i < 6; i++) {
g.drawRect(x + 60, y * i, width, height);
}
For loop 6 - whil give a drawRect of 1 pieces
for (int i = 5; i < 6; i++) {
g.drawRect(x + 75, y * i, width, height);
}
Output (must by an rect ipv *):
*
**
***
****
*****
******
You can do it this way.
JTextField stairs = new JTextField("Enter no. of Stairs");
String noOfStairsStr = stairs.getText();
int noOfStairs = Integer.parseInt(noOfStairsStr);
...
for (int i = 0; i < noOfStairs; i++) { // Use that in the for loop.
Are you expecting something like this
for (int i = 0,k=15; i < 6; i++) {
g.drawRect( ( x+(i*k) ), y * i, width, height);
}
A Graphic environment is complex thing. No longer do you have the safety of fixed character width and heights, now you need to start dealing with a wider range of environmental factors (such as the width and height of the area you have to paint in...)
To get start I would suggest you take a look at
Creating a GUI with Swing
Performing custom painting
2D Graphics
To cover the basics.
In order to paint steps, we need to know (at least) three things.
The number of steps to be painted...
The width of each step
The height of each step.
This example calculates the width and height of the steps as a factor of the width and height of the container it's painting on.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class SimpleSteps {
public static void main(String[] args) {
new SimpleSteps();
}
public SimpleSteps() {
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("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTextField steps;
private StairPane stairPane;
public TestPane() {
setLayout(new BorderLayout());
JPanel options = new JPanel();
steps = new JTextField(10);
JButton create = new JButton("Create");
stairPane = new StairPane();
options.add(new JLabel("Step count: "));
options.add(steps);
options.add(create);
add(stairPane);
add(options, BorderLayout.SOUTH);
create.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String value = steps.getText();
try {
int stepCount = Integer.parseInt(value);
stairPane.setStepCount(stepCount);
} catch (NumberFormatException exp) {
JOptionPane.showMessageDialog(TestPane.this, value + " is not a valid step count", "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
}
}
public class StairPane extends JPanel {
private int stepCount = 0;
public StairPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (stepCount > 0) {
Graphics g2d = (Graphics2D) g.create();
int stepHeight = getHeight() / stepCount;
int stepWidth = getWidth() / stepCount;
for (int step = stepCount; step != 0; step--) {
int width = stepWidth * step;
int y = (stepHeight * step) - stepHeight;
g2d.fillRect(0, y, width, stepHeight);
}
g2d.dispose();
}
}
private void setStepCount(int stepCount) {
this.stepCount = stepCount;
repaint();
}
}
}