I'm trying to improve my understanding of Java, particularly Java GUI, by making a puzzle program. Currently the user selects an image, which is cut up into a specified number of pieces. The pieces are drawn randomly to the screen but they seem to be covered by blank portions of other pieces, and not all of them show up, but I can print out all the coordinates. I am using absolute positioning because a LayoutManager didn't seem to work. I briefly tried layeredPanes but they confused me and didn't seem to solve the problem. I would really appreciate some help.
Here are the 2 relevant classes:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
public class PuzzlePieceDriver extends JFrame
{
private static Dimension SCREENSIZE = Toolkit.getDefaultToolkit().getScreenSize();
private static final int HEIGHT = SCREENSIZE.height;
private static final int WIDTH = SCREENSIZE.width;
public static int MY_WIDTH;
public static int MY_HEIGHT;
private static BufferedImage image;
private int xPieces = PuzzleMagicDriver.getXPieces();
private int yPieces = PuzzleMagicDriver.getYPieces();
private PuzzlePiece[] puzzle = new PuzzlePiece[xPieces*yPieces];
public Container pane = this.getContentPane();
private JLayeredPane layeredPane = new JLayeredPane();
public PuzzlePieceDriver(ImageIcon myPuzzleImage)
{
MY_WIDTH = myPuzzleImage.getIconWidth()+(int)myPuzzleImage.getIconHeight()/2;
MY_HEIGHT = myPuzzleImage.getIconHeight()+(int)myPuzzleImage.getIconHeight()/2;
setTitle("Hot Puzz");
setSize(MY_WIDTH,MY_HEIGHT);
setLocationByPlatform(true);
pane.setLayout(null);
image = iconToImage(myPuzzleImage); //pass image into bufferedImage form
puzzle = createClip(image);
//pane.add(layeredPane);
setVisible(true);
}//end constructor
public static BufferedImage iconToImage(ImageIcon icon)
{
Image img = icon.getImage();
int w = img.getWidth(null);
int h = img.getHeight(null);
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = image.createGraphics();
// Paint the image onto the buffered image
g.drawImage(img, 0, 0, null);
g.dispose();
return image;
}//end BufferedImage
protected int randomNumber(int min, int max)
{
int temp =
min + (int)(Math.random() * ((max - min) + 1));
return temp;
}//end randomNumber
private PuzzlePiece[] createClip(BufferedImage passedImage)
{
int cw, ch;
int w,h;
w = image.getWidth(null);
h = image.getHeight(null);
cw = w/xPieces;
ch = h/yPieces;
int[] cells=new int[xPieces*yPieces];
int dx, dy;
BufferedImage clip = passedImage;
//layeredPane.setPreferredSize(new Dimension(w,h));
for (int x=0; x<xPieces; x++)
{
int sx = x*cw;
for (int y=0; y<yPieces; y++)
{
int sy = y*ch;
int cell = cells[x*xPieces+y];
dx = (cell / xPieces) * cw;
dy = (cell % yPieces) * ch;
clip= passedImage.getSubimage(sx, sy, cw, ch);
int myX = randomNumber(0,(int)w);
int myY = randomNumber(0,(int)h);
PuzzlePiece piece=new PuzzlePiece(clip,myX,myY);
puzzle[x*xPieces+y]=piece;
piece.setBounds(myX,myY,w,h);
//layeredPane.setBounds(myX,myY,w,h);
//layeredPane.add(piece,new Integer(x*xPieces+y));
pane.add(piece);
piece.repaint();
}//end nested for
}//end for
return puzzle;
}//end createClip
}//end class
Sorry if the spacing is a little messed up!
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
public class PuzzlePiece extends JPanel
{
private Point imageCorner; //the image's top-left corner location
private Point prevPt; //mouse location for previous event
private Boolean insideImage =false;
private BufferedImage image;
public PuzzlePiece(BufferedImage clip, int x, int y)
{
image = clip;
imageCorner = new Point(x,y);
//repaint();
}//end constructor
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, (int)getImageCornerX(),(int)getImageCornerY(), this);
System.out.println("paint "+getImageCornerX()+" "+getImageCornerY());
//repaint();
//g.dispose();
}//end paintComponent
public Point getImageCorner()
{
return imageCorner;
}//end getImageCorner
public double getImageCornerY()
{
return imageCorner.getY();
}//end getImageCornerY
public double getImageCornerX()
{
return imageCorner.getX();
}//end getPoint
}//end class PuzzlePiece
Any help would be appreciated, I've gotten really stuck! Thanks!!
I was really intrigued by this idea, so I made another example, using a custom layout manager.
public class MyPuzzelBoard extends JPanel {
public static final int GRID_X = 4;
public static final int GRID_Y = 4;
private BufferedImage image;
public MyPuzzelBoard(BufferedImage image) {
setLayout(new VirtualLayoutManager());
setImage(image);
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
removeAll();
generatePuzzel();
} else {
Component comp = getComponentAt(e.getPoint());
if (comp != null && comp != MyPuzzelBoard.this) {
setComponentZOrder(comp, 0);
invalidate();
revalidate();
repaint();
}
}
}
});
}
public void setImage(BufferedImage value) {
if (value != image) {
image = value;
removeAll();
generatePuzzel();
}
}
public BufferedImage getImage() {
return image;
}
protected float generateRandomNumber() {
return (float) Math.random();
}
protected void generatePuzzel() {
BufferedImage image = getImage();
if (image != null) {
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
int clipWidth = imageWidth / GRID_X;
int clipHeight = imageHeight / GRID_Y;
for (int x = 0; x < GRID_X; x++) {
for (int y = 0; y < GRID_Y; y++) {
float xPos = generateRandomNumber();
float yPos = generateRandomNumber();
Rectangle bounds = new Rectangle((x * clipWidth), (y * clipHeight), clipWidth, clipHeight);
MyPiece piece = new MyPiece(image, bounds);
add(piece, new VirtualPoint(xPos, yPos));
}
}
}
invalidate();
revalidate();
repaint();
}
public class VirtualPoint {
private float x;
private float y;
public VirtualPoint(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public void setX(float x) {
this.x = x;
}
public void setY(float y) {
this.y = y;
}
}
public class VirtualLayoutManager implements LayoutManager2 {
private Map<Component, VirtualPoint> mapConstraints;
public VirtualLayoutManager() {
mapConstraints = new WeakHashMap<>(25);
}
#Override
public void addLayoutComponent(Component comp, Object constraints) {
if (constraints instanceof VirtualPoint) {
mapConstraints.put(comp, (VirtualPoint) constraints);
}
}
#Override
public Dimension maximumLayoutSize(Container target) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
#Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
#Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
#Override
public void invalidateLayout(Container target) {
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
mapConstraints.remove(comp);
}
#Override
public Dimension preferredLayoutSize(Container parent) {
return new Dimension(400, 400);
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public void layoutContainer(Container parent) {
int width = parent.getWidth();
int height = parent.getHeight();
for (Component comp : parent.getComponents()) {
VirtualPoint p = mapConstraints.get(comp);
if (p != null) {
int x = Math.round(width * p.getX());
int y = Math.round(height * p.getY());
Dimension size = comp.getPreferredSize();
x = Math.min(x, width - size.width);
y = Math.min(y, height - size.height);
comp.setBounds(x, y, size.width, size.height);
}
}
}
}
}
Basically, this uses a "virtual" coordinate system, where by rather then supply absolute x/y positions in pixels, you provide them as percentage of the parent container. Now, to be honest, it wouldn't take much to convert back to absolute positioning, just this way, you also get layout scaling.
The example also demonstrates Z-reording (just in case) and the double click simple re-randomizes the puzzel
Oh, I also made the piece transparent (opaque = false)
Oh, one thing I should mention, while going through this example, I found that it was possible to have pieces placed off screen (completely and partially).
You may want to check your positioning code to make sure that the images when they are laid out aren't been moved off screen ;)
Try using setBorder(new LineBorder(Color.RED)) in your puzzle piece constructor to see where the bounds of your puzzle pieces are. If they are where you'd expect them to be, it's likely that your positioning is wrong. Also make your puzzle pieces extend JComponent instead, or use setOpaque(false) if you're extending JPanel.
There are lots of suggestions I'd like to make, but first...
The way you choose a random position is off...
int myX = randomNumber(0,(int)w);
int myY = randomNumber(0,(int)h);
This allows duplicate position's to be generated (and overlaying cells)
UPDATES (using a layout manager)
Okay, so this is a slight shift in paradigm. Rather then producing a clip and passing it to the piece, I allowed the piece to make chooses about how it was going to render the the piece. Instead, I passed it the Rectangle it was responsible for.
This means, you could simply use something like setCell(Rectangle) to make a piece change (unless you're hell bent on drag'n'drop ;))
I ended up using Board panel due to some interesting behavior under Java 7, but that's another question ;)
package puzzel;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.*;
public class PuzzlePieceDriver extends JFrame {
public PuzzlePieceDriver(ImageIcon myPuzzleImage) {
setTitle("Hot Puzz");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
add(new Board(myPuzzleImage));
pack();
setVisible(true);
}//end constructor
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
ImageIcon image = new ImageIcon(PuzzlePieceDriver.class.getResource("/issue459.jpg"));
PuzzlePieceDriver driver = new PuzzlePieceDriver(image);
driver.setLocationRelativeTo(null);
driver.setVisible(true);
}
});
}
}//end class
A piece panel...
The panel overrides the preferred and minimum size methods...while it works for this example, it's probably better to use setPreferredSize and setMiniumumSize instead ;)
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package puzzel;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
public class PuzzlePiece extends JPanel {
private BufferedImage masterImage;
private Rectangle pieceBounds;
private BufferedImage clip;
public PuzzlePiece(BufferedImage image, Rectangle bounds) {
masterImage = image;
pieceBounds = bounds;
// Make sure the rectangle fits the image
int width = Math.min(pieceBounds.x + pieceBounds.width, image.getWidth() - pieceBounds.x);
int height = Math.min(pieceBounds.y + pieceBounds.height, image.getHeight() - pieceBounds.y);
clip = image.getSubimage(pieceBounds.x, pieceBounds.y, width, height);
}//end constructor
#Override
public Dimension getPreferredSize() {
return pieceBounds.getSize();
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int x = 0;
int y = 0;
g.drawImage(clip, x, y, this);
g.setColor(Color.RED);
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}//end paintComponent
}//end class PuzzlePiece
The board panel...used mostly because of some interesting issues I was having with Java 7...Implements a MouseListener, when you run the program, click the board, it's fun ;)
package puzzel;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
/**
*
* #author shane
*/
public class Board extends JPanel {
public static final int X_PIECES = 4;
public static final int Y_PIECES = 4;
private PuzzlePiece[] puzzle = new PuzzlePiece[X_PIECES * Y_PIECES];
private static BufferedImage image;
public Board(ImageIcon myPuzzleImage) {
image = iconToImage(myPuzzleImage); //pass image into bufferedImage form
puzzle = createClip();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
removeAll();
invalidate();
createClip();
// doLayout();
invalidate();
revalidate();
repaint();
}
});
}
public static BufferedImage iconToImage(ImageIcon icon) {
Image img = icon.getImage();
int w = img.getWidth(null);
int h = img.getHeight(null);
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = image.createGraphics();
// Paint the image onto the buffered image
g.drawImage(img, 0, 0, null);
g.dispose();
return image;
}//end BufferedImage
protected int randomNumber(int min, int max) {
int temp = min + (int) (Math.random() * ((max - min) + 1));
return temp;
}//end randomNumber
private PuzzlePiece[] createClip() {
int cw, ch;
int w, h;
w = image.getWidth(null);
h = image.getHeight(null);
cw = w / X_PIECES;
ch = h / Y_PIECES;
// Generate a list of cell bounds
List<Rectangle> lstBounds = new ArrayList<>(25);
for (int y = 0; y < h; y += ch) {
for (int x = 0; x < w; x += cw) {
lstBounds.add(new Rectangle(x, y, cw, ch));
}
}
BufferedImage clip = image;
setLayout(new GridBagLayout());
for (int x = 0; x < X_PIECES; x++) {
for (int y = 0; y < Y_PIECES; y++) {
// Get a random index
int index = randomNumber(0, lstBounds.size() - 1);
// Remove the bounds so we don't duplicate any positions
Rectangle bounds = lstBounds.remove(index);
PuzzlePiece piece = new PuzzlePiece(clip, bounds);
puzzle[x * X_PIECES + y] = piece;
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = x;
gbc.gridy = y;
gbc.fill = GridBagConstraints.BOTH;
add(piece, gbc);
piece.invalidate();
piece.repaint();
}//end nested for
}//end for
invalidate();
repaint();
return puzzle;
}//end createClip
}
Now I know you eventually going to ask about how to move a piece, GridBagLayout has this wonderful method called getConstraints which allows you to retrieve the constraints used to layout the component in question. You could then modify the gridx and gridy values and use setConstraints to update it (don't forget to call invalidate and repaint ;))
I'd recommend having a read of How to Use GridBagLayout for more information ;)
Eventually, you'll end up with something like:
Related
In this image you can see a default Java Swing color chooser, JColorChooser:
What I want is just the color square and vertical slider as outlined in red:
I was able to remove all the tabs (Swatches, HSL, etc) and the preview, but I was unable to remove all the sliders. How can I do this?
OK, just for grins, I decided to create your desired JPanel from scratch, wiring it up in a kind of View-Controller set up, since the model here is trivial -- a Color field within the controller. To use this component you create an instance of the component, add it to your GUI where desired and simply add a PropertyChangeListener to it that listens to its COLOR property:
hsvChooserPanel.addPropertyChangeListener(HsvChooserPanel.COLOR, pce -> {
Color c = (Color) pce.getNewValue();
// do what you want with Color value, c, here
});
The test class:
Version 2: this allows use of any 3 of the HSV panel/bar combinations, either the one with the bar focused on Hue, or Saturation, or Value (here Brightness). It uses an enum, ColorProperty, that has 3 values, ColorProperty.HUE, ColorProperty.SATURATION, and ColorProperty.BRIGHTNESS, and has the calculations within the enum items themselves on how to create the color bar and the color square BufferedImages. Comments welcome:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestHsvChooser2 {
// ***** choose which color panel / color bar you'd like to test:
// private static final ColorProperty COLOR_PROP = ColorProperty.HUE;
// private static final ColorProperty COLOR_PROP = ColorProperty.BRIGHTNESS;
private static final ColorProperty COLOR_PROP = ColorProperty.SATURATION;
private static void createAndShowGui() {
HsvChooserPanel2 hsvChooserPanel = new HsvChooserPanel2(COLOR_PROP);
JPanel testPanel = new JPanel();
JPanel outerPanel = new JPanel(new BorderLayout());
outerPanel.add(testPanel);
outerPanel.setBorder(BorderFactory.createTitledBorder("Test Panel"));
hsvChooserPanel.addPropertyChangeListener(HsvChooserPanel2.COLOR, pce -> {
Color c = (Color) pce.getNewValue();
testPanel.setBackground(c);
});
JPanel gridPanel = new JPanel(new GridLayout(0, 1, 10, 10));
gridPanel.add(hsvChooserPanel);
gridPanel.add(outerPanel);
JFrame frame = new JFrame("HSV Chooser");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(gridPanel);
frame.setResizable(false);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
The whole shebang:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.event.SwingPropertyChangeSupport;
#SuppressWarnings("serial")
public class HsvChooserPanel2 extends JPanel {
public static final String COLOR = "color";
private static final int PREF_W = 180;
private static final int PREF_H = PREF_W;
private static final int PREF_W_CB = 20;
private static final int GAP = 10;
private MyColorPanel2 colorPanel = null;
private MyColorBar2 colorBar = null;
private InnerControl2 innerControl = null;
public HsvChooserPanel2(ColorProperty colorProp) {
colorPanel = new MyColorPanel2(PREF_W, PREF_H, colorProp);
colorBar = new MyColorBar2(PREF_W_CB, PREF_H, colorProp);
innerControl = new InnerControl2(this, colorPanel, colorBar);
colorPanel.setColorPropertyValue(Color.RED); // !! magic value
setLayout(new BorderLayout(GAP, GAP));
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
add(colorPanel, BorderLayout.CENTER);
add(colorBar, BorderLayout.LINE_END);
// propagate COLOR changes in the inner controller
// to this component's property change support
innerControl.addPropertyChangeListener(COLOR, pce -> {
firePropertyChange(COLOR, pce.getOldValue(), pce.getNewValue());
});
}
}
// listens for changes to both the color bar and the color panel
class InnerControl2 {
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private HsvChooserPanel2 hsvChooser;
private MyColorPanel2 colorPanel;
private MyColorBar2 colorBar;
private Color color; // This is all there is to the model, a "bound"
// property
public InnerControl2(HsvChooserPanel2 hsvChooser, MyColorPanel2 colorPanel, MyColorBar2 colorBar) {
this.hsvChooser = hsvChooser;
this.colorPanel = colorPanel;
this.colorBar = colorBar;
// listen for changes
colorPanel.addPropertyChangeListener(ColorPanelParent.CURSOR, new ColorPanelListener());
colorBar.addPropertyChangeListener(ColorPanelParent.CURSOR, new ColorBarListener());
}
public Color getColor() {
return color;
}
// classic setter method for a "bound" property
public void setColor(Color color) {
Color oldValue = this.color;
Color newValue = color;
this.color = color;
// notify listeners of the change
pcSupport.firePropertyChange(HsvChooserPanel2.COLOR, oldValue, newValue);
}
// allow outside classes the ability to listen
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(name, listener);
}
public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(name, listener);
}
public HsvChooserPanel2 getHsvChooser() {
return hsvChooser;
}
// inner listeners
private class ColorPanelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
Point p = colorPanel.getCursorP();
Color c = colorPanel.getColor(p);
colorBar.setColorPropertyValue(c);
setColor(c); // this will fire the prop change
// support
}
}
private class ColorBarListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
// get hue from the color bar and use it to set the main hue
// of the color panel
Point p = colorBar.getCursorP();
Color c = colorBar.getColor(p);
colorPanel.setColorPropertyValue(c);
}
}
}
// parent of both color bar panel and color panel
#SuppressWarnings("serial")
abstract class ColorPanelParent extends JPanel {
public static final String CURSOR = "cursor";
private int prefW;
private int prefH;
private Point cursorP = new Point(0, 0);
private BufferedImage img = null;
private ColorProperty colorProperty;
private boolean panel;
public ColorPanelParent(int prefW, int prefH, ColorProperty colorProperty, boolean panel) {
this.prefW = prefW;
this.prefH = prefH;
this.colorProperty = colorProperty;
this.panel = panel;
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
setBorder(BorderFactory.createLineBorder(Color.BLACK));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(prefW, prefH);
}
public int getPrefH() {
return prefH;
}
public int getPrefW() {
return prefW;
}
public ColorProperty getColorProperty() {
return colorProperty;
}
public BufferedImage getImg() {
return img;
}
public Point getCursorP() {
return cursorP;
}
public void setImg(BufferedImage img) {
this.img = img;
repaint();
}
public Color getColor(Point p) {
Color c = null;
if (getImg() != null) {
int rgb = getImg().getRGB(p.x, p.y);
c = new Color(rgb);
}
return c;
}
// when the main hue is changed, we have to create a new
// background image to reflect the new main color
public void setColorPropertyValue(Color color) {
int w = getPrefW();
int h = getPrefH();
BufferedImage img = getColorProperty().createImage(color, w, h, panel);
setImg(img);
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, 0, 0, this);
}
}
// change the cursor point and then
// notify prop change support of changes
private class MyMouse extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
mouseResponse(e);
}
#Override
public void mouseDragged(MouseEvent e) {
mouseResponse(e);
}
private void mouseResponse(MouseEvent e) {
int x = e.getX();
int y = e.getY();
if (!contains(e.getPoint())) {
x = Math.max(0, x);
x = Math.min(prefW - 1, x);
y = Math.max(0, y);
y = Math.min(prefH - 1, y);
}
Point oldValue = cursorP;
cursorP = new Point(x, y);
firePropertyChange(CURSOR, oldValue, cursorP);
repaint();
}
}
}
// color bar on the right side of the HsvChooser JPanel.
// Controller action: Changing selection point on this JPanel
// will change the hue of the color panel
#SuppressWarnings("serial")
class MyColorBar2 extends ColorPanelParent {
public MyColorBar2(int prefW, int prefH, ColorProperty colorProperty) {
super(prefW, prefH, colorProperty, false);
// create and set the background image
setColorPropertyValue(Color.RED); // fix the magic number?
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // draws the background image
// this draws a line at the cursor's location
g.setXORMode(Color.WHITE);
int y = getCursorP().y;
g.drawLine(0, y, getPrefW(), y);
}
}
// color panel on the left side of the HsvChooser JPanel.
// Controller action: Changing selection point on this JPanel
// will notify listeners of a new COLOR selection
#SuppressWarnings("serial")
class MyColorPanel2 extends ColorPanelParent {
private static final int CURSOR_RADIUS = 8;
public MyColorPanel2(int prefW, int prefH, ColorProperty colorProperty) {
super(prefW, prefH, colorProperty, true);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // draws the background image
// draws the cross hatch indicating color selection point
g.setXORMode(Color.WHITE);
int x = getCursorP().x;
int y = getCursorP().y;
int x1 = x - CURSOR_RADIUS;
int y1 = y - CURSOR_RADIUS;
int x2 = x + CURSOR_RADIUS;
int y2 = y + CURSOR_RADIUS;
g.drawLine(x1, y, x2, y);
g.drawLine(x, y1, x, y2);
}
}
enum ColorProperty {
HUE {
#Override
public BufferedImage createImage(Color color, int w, int h, boolean panel) {
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
if (panel) {
int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();
// float array is HSB
float hue = Color.RGBtoHSB(red, green, blue, null)[0];
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
float s = ((float) j) / (float) w;
float b = (h - (float) i) / (float) h;
int rgb = Color.getHSBColor(hue, s, b).getRGB();
img.setRGB(j, i, rgb);
}
}
} else {
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
float hue = (h - (float) i) / (float) h;
int rgb = Color.getHSBColor(hue, 1f, 1f).getRGB();
img.setRGB(j, i, rgb);
}
}
}
return img;
}
},
SATURATION {
#Override
public BufferedImage createImage(Color color, int w, int h, boolean panel) {
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();
// float array is HSB
float[] hsb = Color.RGBtoHSB(red, green, blue, null);
return panel ? createPanelImg(w, h, img, hsb) : createBarImg(w, h, img, hsb);
}
private BufferedImage createBarImg(int w, int h, BufferedImage img, float[] hsb) {
float hue = hsb[0];
// float brightness = hsb[2];
float brightness = 1f;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
float saturation = (h - (float) i) / (float) h;
int rgb = Color.getHSBColor(hue, saturation, brightness).getRGB();
img.setRGB(j, i, rgb);
}
}
return img;
}
private BufferedImage createPanelImg(int w, int h, BufferedImage img, float[] hsb) {
float saturation = hsb[1];
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
float hue = ((float) j) / (float) w;
float b = (h - (float) i) / (float) h;
int rgb = Color.getHSBColor(hue, saturation, b).getRGB();
img.setRGB(j, i, rgb);
}
}
return img;
}
},
BRIGHTNESS {
#Override
public BufferedImage createImage(Color color, int w, int h, boolean panel) {
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();
// float array is HSB
float[] hsb = Color.RGBtoHSB(red, green, blue, null);
return panel ? createPanelImg(w, h, img, hsb) : createBarImg(w, h, img, hsb);
}
private BufferedImage createBarImg(int w, int h, BufferedImage img, float[] hsb) {
float hue = hsb[0];
// float saturation = hsb[1];
float saturation = 1f;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
float brightness = (h - (float) i) / (float) h;
int rgb = Color.getHSBColor(hue, saturation, brightness).getRGB();
img.setRGB(j, i, rgb);
}
}
return img;
}
private BufferedImage createPanelImg(int w, int h, BufferedImage img, float[] hsb) {
float brightness = hsb[2];
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
float hue = ((float) j) / (float) w;
float saturation = (h - (float) i) / (float) h;
int rgb = Color.getHSBColor(hue, saturation, brightness).getRGB();
img.setRGB(j, i, rgb);
}
}
return img;
}
};
public abstract BufferedImage createImage(Color color, int w, int h, boolean panel);
}
My solution:
colorChooser.setPreviewPanel(new JPanel());
AbstractColorChooserPanel[] panels = colorChooser.getChooserPanels();
for (AbstractColorChooserPanel accp : panels) {
if(!accp.getDisplayName().equals("HSV")) {
colorChooser.removeChooserPanel(accp);
}
}
JComponent current = (JComponent) colorChooser.getComponents()[0];
while( !current.getClass().toString().equals( "class javax.swing.colorchooser.ColorChooserPanel" ) ){
current = (JComponent) current.getComponents()[0];
}
for(Component jc : current.getComponents()){
if(!jc.getClass().toString().equals( "class javax.swing.colorchooser.DiagramComponent" )){
current.remove(jc);
}
}
This removes everything that I don't want, but I think making a custom one would be a better solution.
I am going to try trashgod's to see if it works better.
You can use the Swing Utils class to search for components of a given type on the panel.
In the example below you can find all the sliders and then make them invisible:
import java.awt.*;
import java.util.List;
import javax.swing.*;
import javax.swing.colorchooser.*;
public class ColorChooserPanel extends JPanel
{
ColorChooserPanel()
{
JColorChooser chooser = new JColorChooser();
AbstractColorChooserPanel[] panels = chooser.getChooserPanels();
for (AbstractColorChooserPanel panel: panels)
{
if ("HSL".equals(panel.getDisplayName()))
{
add( panel );
List<JSlider> sliders = SwingUtils.getDescendantsOfType(JSlider.class, panel, true);
for (JSlider slider: sliders)
{
slider.setVisible( false );
}
}
}
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("ColorChooserPanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ColorChooserPanel());
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
A look at the javax.swing.colorchooser internals reveals that ColorChooserComponentFactory creates subclasses of AbstractColorChooserPanel using ColorChooserPanel and a suitable subclass of ColorModel. ColorChooserPanel holds the parts you want as two instances of DiagramComponent: the left is named diagram and the right is named slider.
Instead of removing components from an existing ColorChooserPanel, consider Creating a Custom Chooser Panel that contains just the desired elements.
class MyChooserPanel extends AbstractColorChooserPanel implements PropertyChangeListener {
private final ColorModel model;
private final DiagramComponent diagram;
private final DiagramComponent slider;
MyChooserPanel(ColorModel model) {
this.model = model;
this.diagram = new DiagramComponent(this.panel, true);
this.slider = new DiagramComponent(this.panel, false);
}
…
}
Naturally, you'll have to recapitulate some of the (package-)private code, but the result will be less fragile.
I am trying to use some sort of draw method to draw a sprite image to my subclass of JPanel called AnimationPanel. I have created a Spritesheet class which can generate a BufferedImage[] that contains all of the sprites in the sheet. In my AnimationPanel class, which implements Runnable, I am getting that BufferedImage[] from the spritesheet instantiated in the AnimationPanel constructor. I want to be able to loop through this array and display each sprite to the screen. How would I do this? Here are my AnimationPanel and Spritesheet classes.
AnimationPanel
package com.kahl.animation;
import javax.swing.JPanel;
public class AnimationPanel extends JPanel implements Runnable {
//Instance Variables
private Spritesheet sheet;
private int currentFrame;
private Thread animationThread;
private BufferedImage image;
public AnimationPanel(Spritesheet aSheet) {
super();
sheet = aSheet;
setPreferredSize(new Dimension(128,128));
setFocusable(true);
requestFocus();
}
public void run() {
BufferedImage[] frames = sheet.getAllSprites();
currentFrame = 0;
while (true) {
frames[currentFrame].draw(); //some implementation still necessary here
currentFrame++;
if (currentFrame >= frames.length) {
currentFrame = 0;
}
}
}
public void addNotify() {
super.addNotify();
if (animationThread == null) {
animationThread = new Thread(this);
animationThread.start();
}
}
}
Spritesheet
package com.kahl.animation;
import java.awt.image.BufferedImage;
import java.imageio.ImageIO;
import java.io.IOException;
import java.io.File;
public class Spritesheet {
//Instance Variables
private String path;
private int frameWidth;
private int frameHeight;
private int framesPerRow;
private int frames;
private BufferedImage sheet = null;
//Constructors
public Spritesheet(String aPath,int width,int height,int fpr, int numOfFrames) {
path = aPath;
frameWidth = width;
frameHeight = height;
framesPerRow = fpr;
frames = numOfFrames;
try {
sheet = ImageIO.read(getClass().getResourceAsStream());
} catch (IOException e) {
e.printStackTrace();
}
}
//Methods
public int getHeight() {
return frameWidth;
}
public int getWidth() {
return frameWidth;
}
public int getFramesPerRow() {
return framesPerRow;
}
private BufferedImage getSprite(int x, int y, int h, int w) {
BufferedImage sprite = sheet.getSubimage(x,y,h,w);
}
public BufferedImage[] getAllSprites() {
BufferedImage[] sprites = new BufferedImage[frames];
int y = 0;
for (int i = 0; i < frames; i++) {
x = i * frameWidth;
currentSprite = sheet.getSprite(x,y,frameHeight,frameWidth);
sprites.add(currentSprite);
}
return sprites;
}
}
I'd encourage the use of a javax.swing.Timer to control the frame rate, rather than an uncontrolled loop
Once the timer "ticks", you need to increment the current frame, get the current image to be rendered and call repaint on the JPanel
Use Graphics#drawImage to render the image.
See...
Painting in AWT and Swing
Performing Custom Painting
How to use Swing Timers
Graphics#drawImage(Image, int, int, ImageObserver)
for more details
There is a cascading series of issues with your Spritesheet class, apart from the fact that it won't actually compile, there are issues with you returning the wrong values from some methods and relying on values which are better calculated...
I had to modify your code so much, I can't remember most of them
public int getHeight() {
return frameWidth;
}
and
public BufferedImage[] getAllSprites() {
BufferedImage[] sprites = new BufferedImage[frames];
int y = 0;
for (int i = 0; i < frames; i++) {
x = i * frameWidth;
currentSprite = sheet.getSprite(x,y,frameHeight,frameWidth);
sprites.add(currentSprite);
}
return sprites;
}
Stand out as two main examples...
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 java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestSpriteSheet {
public static void main(String[] args) {
new TestSpriteSheet();
}
public TestSpriteSheet() {
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 Spritesheet spritesheet;
private BufferedImage currentFrame;
private int frame;
public TestPane() {
spritesheet = new Spritesheet("/Sheet02.gif", 240, 220);
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
currentFrame = spritesheet.getSprite(frame % spritesheet.getFrameCount());
repaint();
frame++;
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(240, 220);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (currentFrame != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - currentFrame.getWidth()) / 2;
int y = (getHeight() - currentFrame.getHeight()) / 2;
g2d.drawImage(currentFrame, x, y, this);
g2d.dispose();
}
}
}
public class Spritesheet {
//Instance Variables
private String path;
private int frameWidth;
private int frameHeight;
private BufferedImage sheet = null;
private BufferedImage[] frameImages;
//Constructors
public Spritesheet(String aPath, int width, int height) {
path = aPath;
frameWidth = width;
frameHeight = height;
try {
sheet = ImageIO.read(getClass().getResourceAsStream(path));
frameImages = getAllSprites();
} catch (IOException e) {
e.printStackTrace();
}
}
public BufferedImage getSprite(int frame) {
return frameImages[frame];
}
//Methods
public int getHeight() {
return frameHeight;
}
public int getWidth() {
return frameWidth;
}
public int getColumnCount() {
return sheet.getWidth() / getWidth();
}
public int getRowCount() {
return sheet.getHeight() / getHeight();
}
public int getFrameCount() {
int cols = getColumnCount();
int rows = getRowCount();
return cols * rows;
}
private BufferedImage getSprite(int x, int y, int h, int w) {
BufferedImage sprite = sheet.getSubimage(x, y, h, w);
return sprite;
}
public BufferedImage[] getAllSprites() {
int cols = getColumnCount();
int rows = getRowCount();
int frameCount = getFrameCount();
BufferedImage[] sprites = new BufferedImage[frameCount];
int index = 0;
System.out.println("cols = " + cols);
System.out.println("rows = " + rows);
System.out.println("frameCount = " + frameCount);
for (int row = 0; row < getRowCount(); row++) {
for (int col = 0; col < getColumnCount(); col++) {
int x = col * getWidth();
int y = row * getHeight();
System.out.println(index + " " + x + "x" + y);
BufferedImage currentSprite = getSprite(x, y, getWidth(), getHeight());
sprites[index] = currentSprite;
index++;
}
}
return sprites;
}
}
}
Remember, animation is the illusion of change over time. You need to provide a delay between each frame of the animation, long enough for the user to recognise it, but short enough to make the animation look smooth.
In the above example, I've used 100 milliseconds, simply as an arbitrary value. It could be possible to use something more like 1000 / spritesheet.getFrameCount(), which will allow a full second for the entire animation (all the frames within one second).
You might need to use different values, for longer or short animations, depending on your needs
Here's some generic code for drawing an image to a JPanel. This method is called to paint your JPanel component.
public void paintComponent (Graphics g)
{
super.paintComponent(g);
//I would have image be a class variable that gets updated in your run() method
g.drawImage(image, 0, 0, this);
}
I may also modify run() to look something like this:
public void run() {
BufferedImage[] frames = sheet.getAllSprites();
currentFrame = 0;
while (true) {
image = frames[currentFrame];
this.repaint(); //explicitly added "this" for clarity, not necessary.
currentFrame++;
if (currentFrame >= frames.length) {
currentFrame = 0;
}
}
}
In regards to only repainting part of the component, it gets a little more complicated
public void run() {
BufferedImage[] frames = sheet.getAllSprites();
currentFrame = 0;
while (true) {
image = frames[currentFrame];
Rectangle r = this.getDirtyRect();
this.repaint(r);
currentFrame++;
if (currentFrame >= frames.length) {
currentFrame = 0;
}
}
}
public Rectangle getDirtyRect() {
int minX=0; //calculate smallest x value affected
int maxX=0; //calculate largest x value affected
int minY=0; //calculate smallest y value affected
int maxY=0; //calculate largest y value affected
return new Rectangle(minX,minY,maxX,maxY);
}
I believe the code will speak for itself, but in general the point of the code is the have a Map class that will take in an array of BufferedImages, x values, and y values, to compose a map of many layers (first layer being the BufferedImage array at 0, starting at the x value at 0 and the y value at 0, and so on). The main job of the map class, is to take each pixel of each image and convert them to Block Objects, which are just simply rectangles with a color (Includes a BufferedImage, because after it works, I will replace the color with the Image. Also includes an integer to specify which layer (1 being index 0) its allowed on with 0 meaning it can exist among all layers). In the end, when I call Render() on a Map object, the map object should do all the work in rendering the blocks into the correct positions. The largest problem with all of this is that I get no sytax or compiler errors, so my logic is what is messed up and I can not figure it out!
Thanks in advance, and if the question is confusing please tell me!
The Map Class:
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
public class Map {
private int width;
private int height;
public int getWidth() { return width; }
public int getHeight() { return height; }
private int xPos;
private int yPos;
public int getX(int i)
{
return xPos;
}
public int getY(int i)
{
return yPos;
}
public void setPosition(int x, int y) { xPos = x; yPos = y; }
private int[] xStarts;
private int[] yStarts;
private ArrayList<BufferedImage> layersList = new ArrayList<BufferedImage>();
public void addLayer(BufferedImage image) { layersList.add(image); }
public void setLayer(int i, BufferedImage image) { layersList.set(i, image); }
private Block[][][] blocksArray;
private boolean beenInitialized = false;
public Map(BufferedImage[] images, int[] x, int[] y){
for (BufferedImage image : images){
layersList.add(image);
xStarts = x;
yStarts = y;
}
}
public void initialize(){
int widthMax = 0;
int heightMax = 0;
for (BufferedImage image : layersList){
if (image.getHeight() > heightMax) { heightMax = image.getHeight(); }
if (image.getWidth() > widthMax) { widthMax = image.getWidth(); }
}
width = widthMax;
height = heightMax;
blocksArray = new Block[layersList.size()][width][height];
for (int i = 0; i < layersList.size(); i++){
int currentLayer = i;
for (int y = 0; y < layersList.get(i).getHeight(); y++){
for (int x = 0; x < layersList.get(i).getWidth(); x++){
int colorCode = layersList.get(i).getRGB(x, y);
boolean error = true;
Block b = null;
for (int c = 0; c < Block.BLOCKS.size(); c++){
if (Block.BLOCKS.get(i).getColorCode() == colorCode && (Block.BLOCKS.get(i).getLayerCode() == currentLayer || Block.BLOCKS.get(i).getLayerCode() == 0)){
b = Block.BLOCKS.get(c);
error = false;
}
}
if (!error){
blocksArray[currentLayer][x][y] = b;
} else {
Block bb = new Block(false, colorCode);
bb.initialize();
blocksArray[currentLayer][x][y] = bb;
}
}
}
}
beenInitialized = true;
}
public void render(Graphics2D g2d){
if (beenInitialized){
for (int i = 0; i < layersList.size(); i++){
for (int y = yStarts[i]; y < layersList.get(i).getHeight() + yStarts[i]; y += Block.SIZE){
int currentY = 0;
for (int x = xStarts[i]; x < layersList.get(i).getWidth() + xStarts[i]; x += Block.SIZE){
int currentX = 0;
blocksArray[i][currentX][currentY].setPosition(x, y);
blocksArray[i][currentX][currentY].render(g2d);
currentX ++;
}
currentY++;
}
}
}
}
public void updatePosition(int x, int y){
xPos += x;
yPos += y;
}
}
The Block Class:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
public class Block {
public static final int SIZE = 32;
public static final boolean DEBUG = true;
public static ArrayList<Block> BLOCKS = new ArrayList<Block>();
private Color debugColor;
public Color getColor() { return debugColor; }
public void setColor(Color color) { debugColor = color; }
private BufferedImage blockIcon;
public BufferedImage getIcon() { return blockIcon; }
public void setIcon(BufferedImage icon) { blockIcon = icon; }
private int xPos;
private int yPos;
public int getX() { return xPos; }
public int getY() { return yPos; }
public void setPosition(int x, int y) { xPos = x; yPos = y; }
private Rectangle blockShape;
public Rectangle getShape() { return blockShape; }
private int colorCode;
public int getColorCode() { return colorCode; }
private boolean colides;
public boolean doesColide() { return colides; }
private int layerCode;
public int getLayerCode() { return layerCode; }
private boolean beenInitialized = false;
public Block(boolean colides, int layerCode){
this.colides = colides;
this.layerCode = layerCode;
}
public void initialize(){
blockShape = new Rectangle(xPos, yPos, SIZE, SIZE);
int r = (colorCode >> 16) & 0x000000FF;
int g = (colorCode >> 8) & 0x000000FF;
int b = (colorCode) & 0x000000FF;
debugColor = new Color(r, g, b);
BLOCKS.add(this);
beenInitialized = true;
}
public void render(Graphics2D g2d){
if (beenInitialized){
if (DEBUG){
g2d.setColor(debugColor);
if (colides){
g2d.fill(blockShape);
} else {
g2d.draw(blockShape);
}
} else{
}
}
}
}
And finally the Game Class (I threw this together JUST to show a window for testing):
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game extends JFrame{
public Game(){
super("Test");
try{
layer1 = ImageIO.read(getClass().getResourceAsStream("/layer1.png"));
layer2 = ImageIO.read(getClass().getResourceAsStream("/layer2.png"));
} catch (Exception ex) { ex.printStackTrace(); }
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setLayout(new BorderLayout());
add(new panel(), BorderLayout.CENTER);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args){
new Game();
}
private int[] xStartPositions = {0, 0};
private int[] yStartPositions = {0, 0};
private BufferedImage layer1;
private BufferedImage layer2;
private BufferedImage[] imageArray = {layer1, layer2};
private Map map;
public class panel extends JPanel{
public panel(){
setMinimumSize( new Dimension(1200, 675));
setMaximumSize( new Dimension(1200, 675));
setPreferredSize( new Dimension(1200, 675));
setVisible(true);
map = new Map(imageArray, xStartPositions, yStartPositions);
}
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D) g;
map.render(g2d);
}
}
}
The initialize method of Map is never called, therefore Map will never render...
Some feedback...
Don't ever override paint, use paintComponent instead (it's very rare that you would need to override paint...
Make sure you are calling super.paintXxx - there's a lot of important working going on in the background that you don't want to miss or replicate...
Instead of extending from a top level container like JFrame, start by extending from JPanel and add this to a frame you create instead
Beware of static variables, this might cause you more problems ;)
You may also want to have a read through Initial Threads
I'm completely stuck on a problem i'm having with this program where I have to draw a city with swing. Basically what i'm trying to do is make it so that the windows don't change every frame. I've tried just about everything I can think of and nothing has worked yet.
Here is the main class that draws everything
import java.applet.Applet;
import java.awt.*;
import java.util.*;
import javax.swing.JFrame;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
public class Skyline extends JFrame implements MouseMotionListener
{
private int mX, mY; //Mouse cooddinates
private Image mImage; //Image buffer
//private Image mImage2; //Image buffer
private int num = 0;
private Building bldg1 = new Building(305, 110, 30);
private Building bldg2 = new Building(380, 125, 170);
private Building bldg3 = new Building(245, 200, 325);
private Building bldg4 = new Building(470, 170, 555);
private Building bldg5 = new Building(395, 200, 755);
private Background bg = new Background();
public void init ()
{
}
public static void main(String []args)
{
Skyline f = new Skyline();
f.setSize(1017, 661); //Sets size of window
f.setTitle("Skyline"); //Sets title of window
f.show();
}
public void paintOffscreen(Graphics page)
{
//Draws the background
bg.draw(page);
//Moving square
num++;
if (num > 1200)
num = 0;
page.setColor(Color.yellow);
page.fillRect(num,100,100,100);
//Draws the buildings
bldg1.draw(page);
bldg2.draw(page);
bldg3.draw(page);
bldg4.draw(page);
bldg5.draw(page);
//Mouse move square
int s = 100;
page.setColor(Color.yellow);
page.fillRect(mX - s / 2, mY - s / 2, s, s);
repaint();
}
//====================================BUFFER CODE========================================
public void paint(Graphics g)
{
//Clear the buffer
Dimension d = getSize();
checkOffscreenImage();
Graphics offG = mImage.getGraphics();
offG.setColor(getBackground());
offG.fillRect(0, 0, d.width, d.height);
//Save frame to buffer
paintOffscreen(mImage.getGraphics());
//Draw the buffer
g.drawImage(mImage, 0, 0, null);
}
private void checkOffscreenImage()
{
Dimension d = getSize();
if (mImage == null || mImage.getWidth(null) != d.width || mImage.getHeight(null) != d.height)
mImage = createImage(d.width, d.height);
}
//=======================================================================================
//==================================MOUSE MOVE CODE======================================
public Skyline()
{
addMouseMotionListener(this);
setVisible(true);
}
public void mouseMoved(MouseEvent me)
{
Graphics g = getGraphics();
mX = (int) me.getPoint().getX();
mY = (int) me.getPoint().getY();
update(g);
//repaint();
}
public void mouseDragged(MouseEvent me)
{
mouseMoved(me);
}
//=======================================================================================
}
And here is the window class that might be able to be fixed somehow.
import java.applet.Applet;
import java.awt.*;
import java.util.Random;
import javax.swing.JFrame;
public class Windows extends JFrame
{
private Random gen = new Random();
private int height, width, locX;
private int onOff = 0;
public Windows()
{
height = 305;
width = 110;
locX = 30;
}
public Windows(int height, int width, int locX)
{
this.height = height;
this.width= width;
this.locX = locX;
}
public void draw(Graphics page)
{
page.setColor (Color.darkGray);
page.fillRect (locX, 550 - height, width, height);
for (int i = 550 - height + 5; i < 550; i += 15)
{
for (int x = locX + 5; x < locX + width; x += 15)
{
onOff = gen.nextInt(2);
if(onOff == 0)
page.setColor(Color.black);
else
page.setColor(Color.yellow);
page.fillRect (x,i,10,10);
}
}
}
}
Heres the building class just in case.
import java.applet.Applet;
import java.awt.*;
import javax.swing.JFrame;
public class Building extends JFrame
{
private int height, width, locX;
private int onOff;
private Windows windows1;// = new Windows(height, width, locX);
public Building()
{
height = 305;
width = 110;
locX = 30;
windows1 = new Windows(height, width, locX);
}
public Building(int height, int width, int locX)
{
this.width = width;
this.height = height;
this.locX = locX;
windows1 = new Windows(height, width, locX);
}
public void draw(Graphics page)
{
page.setColor (Color.darkGray);
page.fillRect (locX, 550 - height, width, height);
windows1.draw(page);
}
}
And the bg class just to be safe
import java.applet.Applet;
import java.awt.*;
public class Background extends Applet
{
private int height, width;
public Background()
{
height = 400;
width = 2000;
}
public Background(int height, int width)
{
this.height = height;
this.width = width;
}
public void draw(Graphics page)
{
//Draws the sky
page.setColor(Color.cyan);
page.fillRect(0,0,2000,2000);
//Draws the grass
page.setColor (Color.green);
page.fillRect (0,500,width,height);
}
}
A number of things jump out at me immediately...
You're trying to use a off screen buffer, but you're recreating it each time you paint to the screen...
public void paintOffscreen(Graphics page)
{
//Draws the background
bg.draw(page);
//Moving square
num++;
if (num > 1200)
num = 0;
page.setColor(Color.yellow);
page.fillRect(num,100,100,100);
//Draws the buildings
bldg1.draw(page);
bldg2.draw(page);
bldg3.draw(page);
bldg4.draw(page);
bldg5.draw(page);
//Mouse move square
int s = 100;
page.setColor(Color.yellow);
page.fillRect(mX - s / 2, mY - s / 2, s, s);
repaint();
}
Additionally, the last call in the method is to repaint. This is a bad idea. This could cause you paint method to be recalled, again and again and again...
You would be better of rendering the backing buffer only when it needs to change...
public void paint(Graphics g)
{
super.paint(g); // YOU MUST CALL super.paint!!!!
//Clear the buffer
Dimension d = getSize();
checkOffscreenImage();
//Draw the buffer
g.drawImage(mImage, 0, 0, null);
}
private void checkOffscreenImage()
{
Dimension d = getSize();
if (mImage == null || mImage.getWidth(null) != d.width || mImage.getHeight(null) != d.height) {
mImage = createImage(d.width, d.height);
Graphics offG = mImage.getGraphics();
offG.setColor(getBackground());
offG.fillRect(0, 0, d.width, d.height);
//Save frame to buffer
paintOffscreen(offG);
offG.dispose(); // If you create it, you must dispose of it...
}
}
Now, this is going to raise some issues with invalidating the buffer. This can be achieved by overriding invalidate and setting the mImage to null
public void invalidate() {
mImage = null;
super.invalidate();
}
You're extending most of your components from JFrame???
Building, Window and Background do no painting of there own (from the content of Swing), you are simply calling the draw method. There is no need to extend from JFrame or JApplet, they're adding no benefit to your program and are simply confusing the issues.
You should, only very rarely, need to override paint on a top level container like JFrame. You are better off using something like JPanel and override the paintComponent method, if for no other reason, they (top level containers) aren't double buffered.
I would move the logic for Skyline into a JPanel and then add it to a JFrame for displaying - IMHO
UPDATED
I've gone through the code and updated it to work the (basic) way I think it should and found a couple of other things along the way...
This this is a bad idea...
public void mouseMoved(MouseEvent me) {
Graphics g = getGraphics();
mX = (int) me.getPoint().getX();
mY = (int) me.getPoint().getY();
update(g);
//repaint();
}
There should never be any need for you to call update(Graphics), besides, the Graphics context you got is simply a snap shot of the last repaint. This will drastically slow you painting process any way, as it is repeatedly calling paint.
So, this is my take...
public class Skyline extends JFrame {
private int num = 0;
private Building bldg1 = new Building(305, 110, 30);
private Building bldg2 = new Building(380, 125, 170);
private Building bldg3 = new Building(245, 200, 325);
private Building bldg4 = new Building(470, 170, 555);
private Building bldg5 = new Building(395, 200, 755);
private Background bg = new Background();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
Skyline f = new Skyline();
f.setSize(1017, 661); //Sets size of window
f.setTitle("Skyline"); //Sets title of window
f.setVisible(true);
}
});
}
public Skyline() {
setLayout(new BorderLayout());
add(new SkyLinePane());
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public class SkyLinePane extends JPanel {
private Image mImage; //Image buffer
private boolean painting = false;
private int mX, mY; //Mouse cooddinates
public SkyLinePane() {
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent me) {
mX = (int) me.getPoint().getX();
mY = (int) me.getPoint().getY();
repaint();
}
});
}
protected void updateBuffer() {
if (!painting && mImage == null) {
painting = true;
new BackgroundPainter(this).execute();
}
}
//====================================BUFFER CODE========================================
#Override
public void paintComponent(Graphics g) {
Dimension d = getSize();
if (mImage != null) {
g.drawImage(mImage, 0, 0, null);
} else {
updateBuffer();
}
g.setColor(Color.RED);
g.drawOval(mX - 5, mY - 5, 10, 10);
}
//=======================================================================================
protected void setBackground(Image image) {
mImage = image;
painting = false;
repaint();
}
}
public class BackgroundPainter extends SwingWorker<Image, Image> {
private SkyLinePane skyLinePane;
public BackgroundPainter(SkyLinePane skyLinePane) {
this.skyLinePane = skyLinePane;
}
#Override
protected Image doInBackground() throws Exception {
Dimension d = skyLinePane.getSize();
Image backgroundBuffer = null;
if (d.width > 0 && d.height > 0) {
System.out.println("Paint offscreen...");
backgroundBuffer = createImage(d.width, d.height);
Graphics offG = backgroundBuffer.getGraphics();
offG.setColor(getBackground());
offG.fillRect(0, 0, d.width, d.height);
//Save frame to buffer
paintOffscreen(offG);
offG.dispose();
System.out.println("Done Paint offscreen...");
}
return backgroundBuffer;
}
#Override
protected void done() {
try {
skyLinePane.setBackground(get());
} catch (ExecutionException exp) {
exp.printStackTrace();
} catch (InterruptedException exp) {
exp.printStackTrace();
}
}
public void paintOffscreen(Graphics page) {
//Draws the background
bg.draw(page);
//Moving square
num++;
if (num > 1200) {
num = 0;
}
page.setColor(Color.yellow);
page.fillRect(num, 100, 100, 100);
//Draws the buildings
bldg1.draw(page);
bldg2.draw(page);
bldg3.draw(page);
bldg4.draw(page);
bldg5.draw(page);
}
}
//=======================================================================================
public class Windows {
private Random gen = new Random();
private int height, width, locX;
private int onOff = 0;
public Windows() {
height = 305;
width = 110;
locX = 30;
}
public Windows(int height, int width, int locX) {
this.height = height;
this.width = width;
this.locX = locX;
}
public void draw(Graphics page) {
page.setColor(Color.darkGray);
page.fillRect(locX, 550 - height, width, height);
for (int i = 550 - height + 5; i < 550; i += 15) {
for (int x = locX + 5; x < locX + width; x += 15) {
onOff = gen.nextInt(2);
if (onOff == 0) {
page.setColor(Color.black);
} else {
page.setColor(Color.yellow);
}
page.fillRect(x, i, 10, 10);
}
}
}
}
public class Building {
private int height, width, locX;
private int onOff;
private Windows windows1;// = new Windows(height, width, locX);
public Building() {
height = 305;
width = 110;
locX = 30;
windows1 = new Windows(height, width, locX);
}
public Building(int height, int width, int locX) {
this.width = width;
this.height = height;
this.locX = locX;
windows1 = new Windows(height, width, locX);
}
public void draw(Graphics page) {
page.setColor(Color.darkGray);
page.fillRect(locX, 550 - height, width, height);
windows1.draw(page);
}
}
public class Background {
private int height, width;
public Background() {
height = 400;
width = 2000;
}
public Background(int height, int width) {
this.height = height;
this.width = width;
}
public void draw(Graphics page) {
//Draws the sky
page.setColor(Color.cyan);
page.fillRect(0, 0, 2000, 2000);
//Draws the grass
page.setColor(Color.green);
page.fillRect(0, 500, width, height);
}
}
}
Basically, I moved the core rendering of the skyling to it's own panel and used JComponent#paintComponent to render the skyline.
I employed a SwingWorker to off load the rendering of the backing buffer to another thread, allowing the UI to remain responsive while the backing buffer was rendered.
First off, please accept my apologies if this question is basic, I mainly have knowledge of C# but am forced to use Java for this particular project!
I'm trying to implement a GUI to display an occupancy grid based on robot sensor data. The occupancy grid will be quite large, perhaps up to 1500x1500 grid squares representing real-world area of around 10cm2 per grid cell.
Each grid square will simply store an Enumerable status, for example:
Unknown
Unoccupied
Occupied
Robot
I would simply like to find the best way to render this as a grid, using different colour squares to depict different grid cell status'.
I have implemented a naive, basic algorithm to draw squares and grid lines, however it performs VERY badly on larger occupancy grids. Other code in the class redraws the window every 0.5s as new sensor data is collected, I suspect the reason for the very poor performance is the fact that i am rendering EVERY cell EVERY time. Is there an easy way i can selectively render these cells, should I wrap each cell in an observable class?
My current implementation:
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
int width = getSize().width;
int height = getSize().height;
int rowHeight = height / (rows);
int colWidth = width / (columns);
//Draw Squares
for (int row = 0; row < rows; row++) {
for (int col = 0; col < columns; col++) {
switch (this.grid[row][col]) {
case Unexplored:
g.setColor(Color.LIGHT_GRAY);
break;
case Empty:
g.setColor(Color.WHITE);
break;
case Occupied:
g.setColor(Color.BLACK);
break;
case Robot:
g.setColor(Color.RED);
break;
}
g.drawRect(col * colWidth, height - ((row + 1) * rowHeight), colWidth, rowHeight);
g.fillRect(col * colWidth, height - ((row + 1) * rowHeight), colWidth, rowHeight);
}
}
int k;
if (outline) {
g.setColor(Color.black);
for (k = 0; k < rows; k++) {
g.drawLine(0, k * rowHeight, width, k * rowHeight);
}
for (k = 0; k < columns; k++) {
g.drawLine(k * colWidth, 0, k * colWidth, height);
}
}
}
private void setRefresh() {
Action updateUI = new AbstractAction() {
boolean shouldDraw = false;
public void actionPerformed(ActionEvent e) {
repaint();
}
};
new Timer(updateRate, updateUI).start();
}
Please help! Thanks in advance.
ROS - robot operating system from willowgarage has an occupancygrid implementation in java: http://code.google.com/p/rosjava/source/browse/android_honeycomb_mr2/src/org/ros/android/views/map/OccupancyGrid.java?spec=svn.android.88c9f4af5d62b5115bfee9e4719472c4f6898665&repo=android&name=88c9f4af5d&r=88c9f4af5d62b5115bfee9e4719472c4f6898665
You may use it or get ideas from it.
You need to respect the clip rectangle when painting (assuming your grid is in a JScrollPane) and use JComponent#repaint(Rectangle) appropriately.
See this sample program (although it related to loading the value of a cell lazily, it also has the clip bounds painting):
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.*;
public class TilePainter extends JPanel implements Scrollable {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Tiles");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(new TilePainter()));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private final int TILE_SIZE = 50;
private final int TILE_COUNT = 1000;
private final int visibleTiles = 10;
private final boolean[][] loaded;
private final boolean[][] loading;
private final Random random;
public TilePainter() {
setPreferredSize(new Dimension(
TILE_SIZE * TILE_COUNT, TILE_SIZE * TILE_COUNT));
loaded = new boolean[TILE_COUNT][TILE_COUNT];
loading = new boolean[TILE_COUNT][TILE_COUNT];
random = new Random();
}
public boolean getTile(final int x, final int y) {
boolean canPaint = loaded[x][y];
if(!canPaint && !loading[x][y]) {
loading[x][y] = true;
Timer timer = new Timer(random.nextInt(500),
new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
loaded[x][y] = true;
repaint(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
});
timer.setRepeats(false);
timer.start();
}
return canPaint;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle clip = g.getClipBounds();
int startX = clip.x - (clip.x % TILE_SIZE);
int startY = clip.y - (clip.y % TILE_SIZE);
for(int x = startX; x < clip.x + clip.width; x += TILE_SIZE) {
for(int y = startY; y < clip.y + clip.height; y += TILE_SIZE) {
if(getTile(x / TILE_SIZE, y / TILE_SIZE)) {
g.setColor(Color.GREEN);
}
else {
g.setColor(Color.RED);
}
g.fillRect(x, y, TILE_SIZE, TILE_SIZE);
}
}
}
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(visibleTiles * TILE_SIZE, visibleTiles * TILE_SIZE);
}
#Override
public int getScrollableBlockIncrement(
Rectangle visibleRect, int orientation, int direction) {
return TILE_SIZE * Math.max(1, visibleTiles - 1);
}
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
#Override
public int getScrollableUnitIncrement(
Rectangle visibleRect, int orientation, int direction) {
return TILE_SIZE;
}
}
Creating rectangles is probably too slow. Instead, why don't you create a bitmap image, each pixel being a cell of the grid, you can then scale it to whatever size you want.
The following class takes a matrix of integers, and saves it to a bitmap file.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class BMP {
private final static int BMP_CODE = 19778;
byte [] bytes;
public void saveBMP(String filename, int [][] rgbValues){
try {
FileOutputStream fos = new FileOutputStream(new File(filename));
bytes = new byte[54 + 3*rgbValues.length*rgbValues[0].length + getPadding(rgbValues[0].length)*rgbValues.length];
saveFileHeader();
saveInfoHeader(rgbValues.length, rgbValues[0].length);
saveRgbQuad();
saveBitmapData(rgbValues);
fos.write(bytes);
fos.close();
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
private void saveFileHeader() {
byte[]a=intToByteCouple(BMP_CODE);
bytes[0]=a[1];
bytes[1]=a[0];
a=intToFourBytes(bytes.length);
bytes[5]=a[0];
bytes[4]=a[1];
bytes[3]=a[2];
bytes[2]=a[3];
//data offset
bytes[10]=54;
}
private void saveInfoHeader(int height, int width) {
bytes[14]=40;
byte[]a=intToFourBytes(width);
bytes[22]=a[3];
bytes[23]=a[2];
bytes[24]=a[1];
bytes[25]=a[0];
a=intToFourBytes(height);
bytes[18]=a[3];
bytes[19]=a[2];
bytes[20]=a[1];
bytes[21]=a[0];
bytes[26]=1;
bytes[28]=24;
}
private void saveRgbQuad() {
}
private void saveBitmapData(int[][]rgbValues) {
int i;
for(i=0;i<rgbValues.length;i++){
writeLine(i, rgbValues);
}
}
private void writeLine(int row, int [][] rgbValues) {
final int offset=54;
final int rowLength=rgbValues[row].length;
final int padding = getPadding(rgbValues[0].length);
int i;
for(i=0;i<rowLength;i++){
int rgb=rgbValues[row][i];
int temp=offset + 3*(i+rowLength*row) + row*padding;
bytes[temp] = (byte) (rgb>>16);
bytes[temp +1] = (byte) (rgb>>8);
bytes[temp +2] = (byte) rgb;
}
i--;
int temp=offset + 3*(i+rowLength*row) + row*padding+3;
for(int j=0;j<padding;j++)
bytes[temp +j]=0;
}
private byte[] intToByteCouple(int x){
byte [] array = new byte[2];
array[1]=(byte) x;
array[0]=(byte) (x>>8);
return array;
}
private byte[] intToFourBytes(int x){
byte [] array = new byte[4];
array[3]=(byte) x;
array[2]=(byte) (x>>8);
array[1]=(byte) (x>>16);
array[0]=(byte) (x>>24);
return array;
}
private int getPadding(int rowLength){
int padding = (3*rowLength)%4;
if(padding!=0)
padding=4-padding;
return padding;
}
}
With that class, you can simply do:
new BMP().saveBMP(fieName, myOccupancyMatrix);
Generating the matrix of integers (myOccupancyMatrix) is easy. A simple trick to avoid the Switch statement is assigning the color values to your Occupancy enum:
public enum Occupancy {
Unexplored(0x333333), Empty(0xFFFFFF), Occupied(0x000000), Robot(0xFF0000);
}
Once you save it do disk, the BMP can be shown in an applet and scaled easily:
public class Form1 extends JApplet {
public void paint(Graphics g) {
Image i = ImageIO.read(new URL(getCodeBase(), "fileName.bmp"));
g.drawImage(i,0,0,WIDTH,HEIGHT,Color.White,null);
}
}
Hope this helps!
Rendering even a subset of 2,250,000 cells is not a trivial undertaking. Two patterns you'll need are Model-View-Controller, discussed here, and flyweight, for which JTable may be useful.