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.
Related
I have to draw n + 1 amount of circles horizontally and vertically in a GUI. Which I have successfully done as shown below. With this, a 2D array of strings will be printed between them, centralised.
How it currently stands
Now I want to draw numbers in a "square" of the dots.
How I want the final result
for (int i = 0; i < width; i++) {
for (int j = 0; j < width; j++) {
canvas.drawCircle( (j + 1) * 125, (i + 1) * 125, 15, Color.white);
}
}
for (int r = 0; r < size; r++) {
for (int c = 0; c < size; c++) {
canvas.drawString(events[r][c], (r + 1) * 150, (c + 1) * 150, Color.green );
}
}
Width in this case is 4, so basically (n-1) dots/circles in the picture.
Size is 3 which is just the length of the 2d array, as there are 4 circles in this case there will be 3 numbers between each one
Events is the 2D array with the data containing the numbers
The drawCircle method's signature is
(x, y, radius, color)
The drawString method's signature is
(text, x, y color)
I believe part of the problem is also the drawing of circles. Basically I think it has to do with the rubbish formula I have for determining the x, y coords for both circles and the text. Any help is appreciated, thank you.
Provided is some code which I believe does what you want. Some of the constants may be tweaked to fit your final requirements. I used a 2D array of numbers and converted to strings during paint. It also allows for the following:
Changing the ball diameter or number of balls will still provide a
correct graph (although it will change its location within the
panel).
The String numbers track along with the size of the balls
Anti-aliasing was turned on to smooth out the graphics.
FontMetrics was used to fine tune the location of the numbers.
One final note: Because this is not resource intensive, the coordinates are calculated within the paintComponent method. A more optimum solution would be to adopt the Flyweight design pattern and precalculate as much as possible before entering paintComponent.
import java.awt.*;
import javax.swing.*;
public class SwingMain extends JPanel {
final static int WIDTH = 700;
final static int HEIGHT = 700;
final static int SEPARATION = 100;
final static int DIAMETER = 25;
final static int NBALLS = 4;
final static int XSTART = WIDTH / (NBALLS + 2);
final static int YSTART = HEIGHT / (NBALLS + 2);
JFrame frame = new JFrame();
int[][] numbers = new int[NBALLS - 1][NBALLS - 1];
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new SwingMain().start());
}
public void start() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame.add(this);
setBackground(Color.RED);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// populate numbers in 2D array.
for (int r = 0; r < NBALLS - 1; r++) {
for (int c = 0; c < NBALLS - 1; c++) {
numbers[r][c] = r * (NBALLS - 1) + c + 1;
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// Allow smoothing of the graphics.
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.white);
// Iterating by the number of balls is more consistent than
// by x and y due to round of errors.
int y = YSTART;
for (int r = 0; r < NBALLS; r++) {
int x = XSTART;
for (int c = 0; c < NBALLS; c++) {
g2d.fillOval(x, y, DIAMETER, DIAMETER);
x += SEPARATION;
}
y += SEPARATION;
}
// This is the same as above except that the start begins
// halfway between the first row and column
// have the size of the font track with the diameter of the balls
g2d.setFont(new Font("ARIAL", Font.BOLD, DIAMETER));
FontMetrics fm = g2d.getFontMetrics();
y = YSTART + SEPARATION / 2;
for (int r = 0; r < NBALLS - 1; r++) {
int x = XSTART + SEPARATION / 2;
for (int c = 0; c < NBALLS - 1; c++) {
String number = Integer.toString(numbers[r][c]);
// Do some final position adjustment with the font metrics to
// center the number
int strWidth = fm.stringWidth(number);
int strHeight = fm.getAscent();
g2d.drawString(number,
x - strWidth / 2 + DIAMETER / 2,
y + strHeight);
x += SEPARATION;
}
y += SEPARATION;
}
}
}
You could store the coordinates of the circles in a 2D array as well, and use that to find the location of the strings. One thing to note is that the drawCircle method for some reason wont draw the circle with the given center (the coordinates you give will be the top left corner actually).
Point[][] circleCoords = new Point[width][width]; //suppose Point class has x and y coords
for (int i = 0; i < width; i++) {
for (int j = 0; j < width; j++) {
//the -15 actually centers the circle to the coordianates
circleCoords[i][j] = new Point((j + 1) * 125 - 15, (i + 1) * 125 -15);
canvas.drawCircle(circleCoords[i][j].x , circleCoords[i][j].y, 15, Color.white);
}
}
for (int r = 0; r < width-1; r++) {
for (int c = 0; c < width-1; c++) {
//calculate coords from circleCoords array: halfway between them
int xCoord = (circleCoords[r][c].x + circleCoords[r+1][c].x)/2;
int yCoord = (circleCoords[r][c].y + circleCoords[r][c+1].y)/2;
//wont be out of bounds, becouse this runs to width-1
canvas.drawString(events[r][c], xCoord, yCoord, Color.green );
}
}
This still wont actually be perfectly centered, because the drawString will use the coordinates for top left point as well, and not centerpoint. Maybe I miscalculated something, but this should give you the idea: instead of calculating the coordinates independently, re-use the circle coordinates.
Consider a class that represents a single square. The value is represented by a JLabel which is placed at the bottom using straight-forward layout manager (BorderLayout in this example). You can change or manipulate the layout manager to change to position of the JLabel.
class Square extends JPanel{
private static int WIDTH = 100, HEIGHT = 100, DAIMETER = 30, GAP = 5;
private final JLabel label;
Square(int value) {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setLayout(new BorderLayout());
setBackground(Color.red);
setOpaque(true);
label = new JLabel(String.valueOf(value), JLabel.RIGHT);
label.setForeground (Color.white);
Border margin = new EmptyBorder(GAP,GAP,GAP,2*GAP); //top left bottom right
label.setBorder(margin);
add(label, BorderLayout.PAGE_END);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.white);
g2d.fillOval(WIDTH/2 - DAIMETER/2, HEIGHT/2-DAIMETER/2, DAIMETER, DAIMETER);
}
}
Now add 16 instances of this Square to panel representing a board. Its layout manager is set to GridLayout:
class Board extends JPanel{
private static int ROWS = 4, COLS = 4;
Board(){
setLayout(new GridLayout(ROWS, COLS));
for(int index = 0; index < ROWS * COLS; index ++){
add(new Square(index)); //index is used as value for demonstration purposes
}
}
}
Putting it all together (this is a one-file mcve. Copy paste the entire code into SwingMain.java and run )
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
public class SwingMain {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(400,250);
frame.add(new Board());
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
}
}
class Board extends JPanel{
private static int ROWS = 4, COLS = 4;
Board(){
setLayout(new GridLayout(ROWS, COLS));
for(int index = 0; index < ROWS * COLS; index ++){
add(new Square(index)); //index is used as value for demonstration purposes
}
}
}
class Square extends JPanel{
private static int WIDTH = 100, HEIGHT = 100, DAIMETER = 30, GAP = 5;
private final JLabel label;
Square(int value) {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setLayout(new BorderLayout());
setBackground(Color.red);
setOpaque(true);
label = new JLabel(String.valueOf(value), JLabel.RIGHT);
label.setForeground (Color.white);
Border margin = new EmptyBorder(GAP,GAP,GAP,2*GAP); //top left bottom right
label.setBorder(margin);
add(label, BorderLayout.PAGE_END);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.white);
g2d.fillOval(WIDTH/2 - DAIMETER/2, HEIGHT/2-DAIMETER/2, DAIMETER, DAIMETER);
}
}
I've been trying to generate a grid of randomly colored boxes for a brick-breaker game. However given this code, the colors keep changing. I'd like them to be randomly set and stay that way.
for(int i = 0; i < map.length; i++) {
for(int j = 0; j < map [0].length; j++) {
if(map[i][j] > 0) { //make brick if greater than 0, else don't
int color = (int) (Math.random() * 256);
g.setColor(new Color(color, color, color));
g.fillRect(j * brickWidth + 80, i * brickHeight + 50, brickWidth, brickHeight);
g.setStroke(new BasicStroke(3));
g.setColor(Color.black);
g.drawRect(j * brickWidth + 80, i * brickHeight + 50, brickWidth, brickHeight);
}
}
}
Each time your component need to be resized or moved, repaint() method is being called to update it's state. So if you are generating colors in paintComponent like in following example:
public class MyComponent extends JComponent {
#Override
protected void paintComponent(Graphics g) {
// generate colors and draw grid
}
}
then colors will change on resize event or other event that lead to repaint call, since repaint invokes paintComponent method. If you want to display the same colors, just move generate-colors code out of this method:
public class MyComponent extends JComponent {
private final Color[][] gridColors = randomGridColors(5, 5);
private Color[][] randomGridColors(int rows, int columns) {
Color[][] gridColors = new Color[rows][columns];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
gridColors [i][j] = randomColor();
}
}
}
private Color randomColor() {
int rgbValue = (int) (Math.random() * 256);
return new Color(rgbValue, rgbValue, rgbValue);
}
#Override
protected void paintComponent(Graphics g) {
// draw grid
}
}
You currently create the random color every time you draw your bricks, which can be many times per second.
Create some brick class and generate the color for each brick only once.
Something like this:
public class TryThis {
private static final Logger LOG = Logger.getLogger(TryThis.class.getName());
public static void main(String[] args) {
SwingComponent panel = new SwingComponent();
JFrame frame = new JFrame("try me");
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.setVisible(true);
}
static class SwingComponent extends JPanel {
int W = 10;
int H = 10;
int recwidth = 20;
int recheight = 10;
Brick[][] bricks = new Brick[H][W];
public SwingComponent() {
super();
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
bricks[y][x] = new Brick(createRandomColor(), new Rectangle(x * recwidth, y * recheight, recwidth,
recheight));
}
}
}
#Override
public void paint(Graphics g) {
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
bricks[y][x].draw(g);
}
}
}
Color createRandomColor() {
return new Color((int) (Math.random() * 256), (int) (Math.random() * 256), (int) (Math.random() * 256));
}
}
static class Brick {
Color col;
Rectangle rec;
public Brick(Color col, Rectangle rec) {
this.col = col;
this.rec = rec;
}
public void draw(Graphics g) {
g.setColor(col);
g.fillRect(rec.x, rec.y, rec.width, rec.height);
}
}
}
I've been tasked to make a java replica of Candy Crush Saga.
Right now im quite stuck on the GUI part.
I've decided that each candy will be represented by a JLabel holding the candy icon, and a mouselistener to control the functionality.
What happens is, after i finish running the screen shows, the mouse listeners respond but the image doesn't show, meaning i can press the labels get a response but cannot see the icons. I take this as the labels are on the panel but somehow not visible or the icon is not loaded correctly - although when checking the ImageIcon.toString it shows the path to the file.
Any ideas?
Here is the code:
public class Board extends JPanel {
Candy[][] board;
static final int TILE_SIZE = 55;
static final int TILES_MARGIN = 8;
public Board() {
setFocusable(true);
board = new Candy[13][13];
Candy c;
for (int i = 0; i < 13; i++)
for (int j = 0; j < 13; j++) {
if (i != 0 && i != 1 && j != 0 && j != 1 && i != 11 && i != 12 && j != 11 && j != 12) {
Random rand = new Random();
int randomNum = rand.nextInt((6 - 1) + 1) + 1;
c = new Basic(randomNum, this);
} else {
c = new Basic(0, this);
}
setAt(i, j, c);
}
repaint();
}
public void drawCandy(Graphics g2, Candy candy, int x, int y) {
Graphics2D g = ((Graphics2D) g2);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
int value = candy.getClr();
int xOffset = offsetCoors(x);
int yOffset = offsetCoors(y);
ImageIcon myImg = candy.switchIcon();
JLabel toAdd = new JLabel(myImg);
toAdd.setIcon(myImg);
toAdd.setLocation(xOffset,yOffset);
toAdd.setSize(TILE_SIZE,TILE_SIZE);
toAdd.addMouseListener(new ButtonPressed(x,y,candy));
toAdd.setVisible(true);
if (value != 0)
add(toAdd);
}
private static int offsetCoors(int arg) {
return (arg-2) * (TILES_MARGIN + TILE_SIZE) + TILES_MARGIN;
}
public void paint(Graphics g) {
super.paint(g);
removeAll();
requestFocusInWindow();
g.setColor(Color.black);
g.fillRect(0, 0, this.getSize().width, this.getSize().height);
for (int x = 2; x < 11; x++) {
for (int y = 2; y < 11; y++) {
drawCandy(g, board[x][y], x, y);
}
}
validate();
}
and the JFrame :
public Game() {
super("Candy Crush Game");
setDefaultLookAndFeelDecorated(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
setSize(600, 600);
setResizable(false);
setLocationRelativeTo(null);
this.board = new Board();
this.score = 0;
this.moves = 20;
this.getContentPane().add(board, BorderLayout.CENTER);
setVisible(true);
board.checkSquare(2, 2, 10, 10);
}
I'm quite frustrated, any help will be great!
Instead of overriding paint() method use paintComponent() method for JPanel.
#Overrie
public void paintComponent(Graphics g) {
super.paintComponent(g);
//your custom painting here
}
Read more
Painting in AWT and Swing
paintComponent() vs paint() and JPanel vs Canvas in a paintbrush-type GUI
There might be some issue in reading image icon. My another post might help you.
ImageIcon does not work with me
Instead of creating new JLabel simply change it's icon.
I have a strange problem, I can figure it out.
here is my program running:
the clock on the right shouldn't even be there, it should just be string that displays the time. I create two different objects and add them to a content panel, then add the content panel to the JFrame. I also have a menu bar that I add to the JFrame and it shows up on the right side of the content pane. Here is my main class:
class DigitalClock3DialsView extends DigitalClockView {
private static int caps[] = { BasicStroke.CAP_BUTT,
BasicStroke.CAP_SQUARE, BasicStroke.CAP_ROUND};
private static int joins[] = { BasicStroke.JOIN_MITER,
BasicStroke.JOIN_BEVEL, BasicStroke.JOIN_ROUND};
private static Color colors[] = {Color.gray, Color.pink, Color.lightGray};
private static BasicStroke bs1 = new BasicStroke(1.0f);
// three arms of clock
private Line2D lines[] = new Line2D[3];
private int rAmt[] = new int[lines.length];
private int speed[] = new int[lines.length];
private BasicStroke strokes[] = new BasicStroke[lines.length];
private GeneralPath path;
private Point2D[] pts;
private float size;
private Ellipse2D ellipse = new Ellipse2D.Double();
private BufferedImage bimg;
//variables to keep track if minutes or hours increased
private int oldMinute;
private int oldHour;
/**
* init background
*/
public void init() {
setBackground(Color.white);
}
/**
* reset view: the shape of arms, etc
*/
public void reset(int w, int h)
{
oldMinute = 0;
oldHour = 0;
size = (w > h) ? h/6f : w/6f;
for (int i = 0; i < lines.length; i++) {
lines[i] = new Line2D.Float(0,0,size,0);
strokes[i] = new BasicStroke(size/3, caps[i], joins[i]);
rAmt[i] = 270; // vertical
}
//speed of the 3 arms
speed[0] = 6;
speed[1] = 6;
speed[2] = 6;
path = new GeneralPath();
path.moveTo(size, -size/2);
path.lineTo(size+size/2, 0);
path.lineTo(size, +size/2);
// YW: do not know how to show the regular clock view
// with inc of 5 mins on the contour
// can you help to fix this?
ellipse.setFrame(w/2-size*2-4.5f,h/2-size*2-4.5f,size*4,size*4);
double linApproxLen = 0.75 * size * 0.258819; // sin(15 degree)
PathIterator pi = ellipse.getPathIterator(null, linApproxLen);
Point2D[] points = new Point2D[100];
int num_pts = 0;
while ( !pi.isDone() )
{
float[] pt = new float[6];
switch ( pi.currentSegment(pt) ) {
case FlatteningPathIterator.SEG_MOVETO:
case FlatteningPathIterator.SEG_LINETO:
points[num_pts] = new Point2D.Float(pt[0], pt[1]);
num_pts++;
}
pi.next();
}
pts = new Point2D[num_pts];
System.arraycopy(points, 0, pts, 0, num_pts);
}
private Point2D[] computePoints(double w, double h, int n)
{
Point2D points[] = new Point2D[n];
double angleDeltaRad = Math.PI * 2 / n;
for (int i=0; i<n; i++)
{
double angleRad = i * angleDeltaRad;
double ca = Math.cos(angleRad);
double sa = Math.sin(angleRad);
double x = sa * w/2;
double y = ca * h/2;
points[i] = new Point2D.Double(x,y);
}
return points;
}
public void paint(Graphics g)
{
System.out.printf("seconds: %s, minutes: %d \n", second, minute);
Dimension d = getSize();
updateSecond(d.width, d.height);
if(oldMinute < minute || (oldMinute==59 && minute==0))
{
oldMinute = minute;
updateMinute(d.width, d.height);
}
if(oldHour < hour || (oldHour==12 && hour == 1) )
{
oldHour = hour;
updateHour(d.width, d.height);
}
Graphics2D g2 = createGraphics2D(d.width, d.height);
drawClockArms(d.width, d.height, g2);
g2.dispose();
g.drawImage(bimg, 0, 0, this);
}
public Graphics2D createGraphics2D(int w, int h) {
// standard Java 2D code
Graphics2D g2 = null;
if (bimg == null || bimg.getWidth() != w || bimg.getHeight() != h) {
bimg = (BufferedImage) createImage(w, h);
reset(w, h);
}
g2 = bimg.createGraphics();
g2.setBackground(getBackground());
g2.clearRect(0, 0, w, h);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
return g2;
}
private void drawClockArms(int w, int h, Graphics2D g2) {
ellipse.setFrame(w/2-size,h/2-size,size*2,size*2);
g2.setColor(Color.black);
g2.draw(ellipse);
for (int i = 0; i < lines.length; i++) {
AffineTransform at = AffineTransform.getTranslateInstance(w/2,h/2);
at.rotate(Math.toRadians(rAmt[i]));
g2.setStroke(strokes[i]);
g2.setColor(colors[i]);
g2.draw(at.createTransformedShape(lines[i]));
g2.draw(at.createTransformedShape(path));
}
g2.setStroke(bs1);
g2.setColor(Color.black);
for (int i = 0; i < pts.length; i++) {
ellipse.setFrame(pts[i].getX(), pts[i].getY(), 9, 9);
g2.draw(ellipse);
}
}
/**
* step forward on the display: move arm forward
*/
public void step(int w, int h) {
for (int i = 0; i < lines.length; i++)
{
rAmt[i] += speed[i];
System.out.println(rAmt[i]);
if (rAmt[i] == 360) {
rAmt[i] = 0;
}
}
}
public void updateSecond(int w, int h) {
rAmt[0] += speed[0];
if (rAmt[0] == 360) {
rAmt[0] = 0;
}
}
public void updateMinute(int w, int h) {
rAmt[1] += speed[1];
if (rAmt[1] == 360) {
rAmt[1] = 0;
}
}
public void updateHour(int w, int h) {
rAmt[2] += speed[2];
if (rAmt[2] == 360) {
rAmt[2] = 0;
}
}
}
and here is the parent class that extends JPanel:
/**
* Digital Clock view base classes
*/
abstract class DigitalClockView extends JPanel
{
protected int second = 0;
protected int minute = 0;
protected int hour = 0;
public void draw()
{
this.repaint();
}
public void updateTime(int second, int minute, int hour)
{
this.second = second;
this.minute = minute;
this.hour = hour;
}
public abstract void paint(Graphics g);
}
my menu buttons are showing on the right side when I use the menu bar. It's almost as if it is just cloning the view on the left side and showing it on the right side again. Why is this?
super.repaint()
Don't do that, as calling repaint from within a paint method has potential for danger. Call the super.paint(g) method inside of a paint(Graphics g) override.
Or even better yet, don't override paint but instead override your JPanel's paintComponent(Graphics g) method and call super.paintComponent(g). For graphics you must call the same super method as the overridden method, and you're not doing that.
Also and again, you should avoid use of null layout as this makes for very inflexible GUI's that while they might look good on one platform look terrible on most other platforms or screen resolutions and that are very difficult to update and maintain.
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
}
}