Display images of cards from a file - java

I need to code my program to randomly select three cards from a deck of cards. The images are in a folder on my desktop. I then display the images of the cards. My program runs fine as far as reading from the file and then randomly picking 3 cards out of the 52. I have the program printing the 3 random cards in the output. But I can not see the images of the cards when the java window opens up on my computer. It displays a small line that just continuously repeats. So I think it is trying to display the cards but I have something wrong somewhere that is keeping the whole card(s) from displaying.
Thanks in advance for your help!
import java.awt.Graphics;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class DisplayThreeCards {
public static void main(String[] args) {
// File location
File folder = new File("//Users//macuser//Desktop//DeckOfCards");
File[] fileList;
fileList = folder.listFiles();
// Generate random card. Uses the Math.rand() function.
// The '%' operator is used to make sure the random is between 0-52.
int randomCard = (int) (Math.random() * 1000);
System.out.println(fileList[randomCard % 52].getAbsolutePath());
// Print the filename.
// Card 1
System.out.println(fileList[randomCard % 52].getName());
randomCard = (int) (Math.random() * 1000);
// Card 2
System.out.println(fileList[randomCard % 52].getName());
randomCard = (int) (Math.random() * 1000);
// Card 3
System.out.println(fileList[randomCard % 52].getName());
randomCard = (int) (Math.random() * 1000);
try {
ImageFrame frame = new ImageFrame(fileList[randomCard % 52].getAbsolutePath());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
} catch (Exception E) {
}
// Create a window for the card images to be displayed
class ImageFrame extends JFrame {
public ImageFrame(String name) {
setTitle("ImageTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
ImageComponent component = new ImageComponent(name);
add(component);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 300;
}
class ImageComponent extends JComponent {
private static final long serialVersionUID = 1L;
private Image image;
public ImageComponent(String name) {
try {
File image2 = new File(name);
image = ImageIO.read(image2);
} catch (IOException e) {
}
}
#Override
public void paintComponent(Graphics g) {
if (image == null) {
}
int imageWidth = image.getWidth(this);
int imageHeight = image.getHeight(this);
g.drawImage(image, 50, 50, this);
for (int i = 0; i * imageWidth <= getWidth(); i++) {
for (int j = 0; j * imageHeight <= getHeight(); j++) {
if (i + j > 0) {
g.copyArea(0, 0, imageWidth, imageHeight, i * imageWidth, j * imageHeight);
}
}
}
}
}
}
}

Related

Animate image list by group Java Swing

Currently my code animates an image from the list from initial size to final size and continues with the next one until it completes animating all images and all have their final size.
package ui;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.Timer;
import utils.SudokuGen;
// TODO: Auto-generated Javadoc
/**
* The Class ButtonPanel.
*/
public class ButtonPanel extends JPanel implements ActionListener{
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The buttons. */
private JButton[] buttons;
private int[] imgsize;
private int index,count;
private Timer timer;
private ImageButton images;
private static final int COUNT = 10;
private static final int INITSIZE = 32;
private static final int COLUMNS = 1;
private static final int ROWS = 9;
/**
* Instantiates a new button panel.
*
* #param puzzle The Sudoku matrix object
* #param images The images object
* #param sPanel The Sudoku JPanel
*/
public ButtonPanel(SudokuGen puzzle, ImageButton images, SudokuPanel sPanel){
//Create an array of JButton with 9 elements
this.buttons = new JButton[puzzle.getMyboard().getNewnumbers().size()];
this.images = images;
this.imgsize = new int[COUNT];
for (int i=0;i<COUNT;i++){
imgsize[i] = INITSIZE;
}
//Sets the layout to a 9*1 GridLayout with padding 5
this.setLayout(new GridLayout(ROWS,COLUMNS,5,5));
//Load an image, create the MouseListener and add it at index i
for (int i=1;i<this.images.getImagelist().size();i++){
ImageIcon image = new ImageIcon(this.images.getImagelist()
.get(i).getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH));
buttons[i] = createMouseListener(image, i, puzzle, sPanel);
buttons[i].setPreferredSize(new Dimension(100, 100));
this.add(buttons[i]);
}
index = 1; //first button
count = 0; //times to resize the button
timer = new Timer(5,this);
timer.start();
}
public void set(int X){
this.imgsize[index] = X;
}
/**
* Creates the mouse listener.
*
* #param image The image at index i
* #param i The index for the button i
* #param puzzle The Sudoku matrix object
* #param sPanel The Sudoku JPanel
* #return the JButton to add at index i
*/
private JButton createMouseListener(ImageIcon image, int i, SudokuGen puzzle, SudokuPanel sPanel){
JButton button = new JButton();
button.setIcon(image);
button.setActionCommand(Integer.toString(puzzle.getMyboard().getNewnumbers().get(i)));
button.addActionListener(sPanel.new ButtonActionListener());
return button;
}
#Override
public void actionPerformed(ActionEvent e) {
count++;
set(this.imgsize[index]+1); //increase the size of the image
if (this.imgsize[index] < 64){
ImageIcon image = new ImageIcon(this.images.getImagelist()
.get(index).getImage().getScaledInstance(this.imgsize[index], this.imgsize[index], Image.SCALE_SMOOTH));
buttons[index].setIcon(image);
if (count > 24){ //reached final size
count = 0; //start counting again
index++; //move to the next image
}
};
if (index == 10) timer.stop();
repaint();
}
}
I'm not able to provide an SSCCE given the amount of resources needed for it to work so apologizes for that.
My goal is to start animating the next image when the first animation finished half its process. So when the second image reaches its half animation the first one completes its full animation. Kind of a wave effect.
If there something else I could improve, I'm glad to hear suggestions.
Update: Updated method used to achieve the effect:
public void actionPerformed(ActionEvent e) {
count++;
this.imgsize[index]++;
ImageIcon image = new ImageIcon(this.images.getImagelist()
.get(index).getImage().getScaledInstance(this.imgsize[index], this.imgsize[index], Image.SCALE_SMOOTH));
buttons[index].setIcon(image);
if (count > 24){
count = 0;
index++;
}
if (count > 12 && index < 9)
{
this.imgsize[index+1]++;
image = new ImageIcon(this.images.getImagelist()
.get(index+1).getImage().getScaledInstance(this.imgsize[index+1], this.imgsize[index+1], Image.SCALE_SMOOTH));
buttons[index+1].setIcon(image);
}
if (index == 10) timer.stop();
repaint();
}
This is a somewhat overly simplified idea...
Basically, it sets up a normalised timeline (0-1) where events can occur along it, starting and ending at specified points. During these "periods" some "action" can occur.
In your case, these actions are image scaling actions, the scale of which is a proportion of the amount of time that a given event has been running, which is a proportion of the overall time line.
This all sounds wonderfully confusing, but the benefit of which is, you can change the duration of the timeline and the rest of the code will scale automatically
For example, you could take...
timeline = new Timeline(events, 10000);
which generates a timeline to run over 10 seconds and change it to
timeline = new Timeline(events, 5000);
which will generate a timeline to run over 5 seconds and you've not had to change a single line of code to make it work.
I've also spend some time working out how to automatically generate timeline events from a series of images, so you can supply n number of images, and it will generate the required events so that they overlap each other accordingly
Now, you're probably thinking that this is all overly complicated, but the point is, it's all variable, the duration over which the images are scaled, the number of images, it's all variable and you don't need to do much to add/remove images or change the duration
Cavert- I've used getScaledInstance for this example, it's neither fast nor does it generate a quality image
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
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 Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
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);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private Timeline timeline;
private Map<Object, Image> mapImages = new HashMap<>(5);
private List<Object> imageOrder = new ArrayList<>(5);
public TestPane() throws IOException {
BufferedImage[] images = new BufferedImage[]{
ImageIO.read(new File("...")),
ImageIO.read(new File("...")),
ImageIO.read(new File("...")),
ImageIO.read(new File("..."))
};
List<TimelineEvent> events = generateEvents(images, new ImageScaledObserver() {
#Override
public void imageScaled(Object id, Image image) {
if (!imageOrder.contains(id)) {
imageOrder.add(id);
}
mapImages.put(id, image);
repaint();
}
});
timeline = new Timeline(events, 10000);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timeline.update()) {
((Timer) e.getSource()).stop();
}
}
});
timer.setInitialDelay(5000);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Object id : imageOrder) {
Image image = mapImages.get(id);
int x = (getWidth() - image.getWidth(this)) / 2;
int y = (getHeight() - image.getHeight(this)) / 2;
g2d.drawImage(image, x, y, this);
}
g2d.dispose();
}
}
protected List<TimelineEvent> generateEvents(BufferedImage[] images, ImageScaledObserver observer) {
double length = 1.0 / (double) (images.length);
double overlap = length * 0.5;
List<TimelineEvent> events = new ArrayList<>(images.length);
double startAt = 0.0;
for (BufferedImage image : images) {
double endAt = Math.min(startAt + length + (overlap / 2.0), 1.0);
events.add(new ScaleImageTimelineEvent(image, observer, startAt, endAt));
startAt = Math.min(endAt - (overlap / 2.0), 1.0);
}
return events;
}
public interface TimelineEvent {
public double[] range();
public boolean isWithin(double timelineProgression);
public void performAction(double timelineProgression);
}
public abstract class AbstractTimelineEvent implements TimelineEvent {
private double from, to;
public AbstractTimelineEvent(double from, double to) {
this.from = from;
this.to = to;
}
#Override
public double[] range() {
return new double[]{from, to};
}
#Override
public boolean isWithin(double timelineProgression) {
boolean within = timelineProgression >= from && timelineProgression <= to;
return within;
}
protected double localisedProgression(double timelineProgression) {
double max = from - to;
double value = timelineProgression - to;
double weight = value / max;
return 1d - weight;
}
}
public interface ImageScaledObserver {
public void imageScaled(Object id, Image img);
}
public class ScaleImageTimelineEvent extends AbstractTimelineEvent {
private BufferedImage original;
private ImageScaledObserver observer;
private UUID id;
public ScaleImageTimelineEvent(BufferedImage image, ImageScaledObserver observer, double from, double to) {
super(from, to);
this.original = image;
this.observer = observer;
this.id = UUID.randomUUID();
}
#Override
public void performAction(double timelineProgression) {
double progress = localisedProgression(timelineProgression);
Image image = null;
if (progress < 1.0) {
int width = (int) (original.getWidth() * progress);
if (width > 0) {
image = original.getScaledInstance((int) (original.getWidth() * progress), -1, Image.SCALE_FAST);
}
} else {
image = original;
}
if (image != null) {
observer.imageScaled(id, image);
}
}
}
public static class Timeline {
private List<TimelineEvent> events;
private Long startTime;
private long duration;
public Timeline(List<TimelineEvent> events, long duration) {
this.events = events;
this.duration = duration;
}
public List<TimelineEvent> getEvents() {
return events;
}
public Long getStartTime() {
return startTime;
}
public long getDuration() {
return duration;
}
public void start() {
if (startTime == null) {
startTime = System.nanoTime();
}
}
public void stop() {
startTime = null;
}
public boolean update() {
if (startTime == null) {
start();
}
boolean completed = false;
long currentTime = System.nanoTime();
long diff = currentTime - getStartTime();
long nanoDuration = TimeUnit.NANOSECONDS.convert(getDuration(), TimeUnit.MILLISECONDS);
double progress = diff / (double) nanoDuration;
if (progress > 1.0d) {
progress = 1.0d;
completed = true;
stop();
}
for (TimelineEvent evt : getEvents()) {
if (evt.isWithin(progress)) {
evt.performAction(progress);
}
}
return completed;
}
}
}
I'd also suggest having a look at some of the animation frameworks which are available, which provide additional functionality, like easement. Maybe do a search for "Timing Framework" as a suggestion
The example uses a UUID to generate a unique identifier for the imageScaled event. I wouldn't be hard to make ScaleImageTimelineEvent take an identifier of your own which is linked back to a specific image, allowing you to determine which image was scaled and perform more accurate updates

Illegal Argument Exception thrown when trying to load a sprite sheet

I have linked the file I want to load and when I debug, the SpriteSheet constructor shows that the path variable is storing the path I specify. When it trying to run the image = ImageIO.read(SpriteSheet.class.getResourceAsStream(path)); line of code, it crashes with an Illegal Argument Exception. The only thing I can think is wrong is that the file I specified will not load, but I have no idea why.
Game class:
package com.swainchris.twodgame;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import com.swainchris.twodgame.gfx.SpriteSheet;
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
public static final int WIDTH = 160;
public static final int HEIGHT = WIDTH / 12 * 9;
public static final int SCALE = 3;
public static final String NAME = "2D Game";
public static boolean running = false;
private JFrame frame;
public int tickCount = 0;
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
private SpriteSheet spriteSheet = new SpriteSheet("/SS.png");
public Game() {
setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
frame = new JFrame(NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(this, BorderLayout.CENTER);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public synchronized void start() {
running = true;
new Thread(this).start();
}
public synchronized void stop() {
running = false;
}
public void tick(){
tickCount++;
for(int i = 0; i < pixels.length; i++){
pixels[i] = i - tickCount;
}
}
public void render(){
BufferStrategy bs = getBufferStrategy();
if(bs==null){
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.drawImage(image,0,0,getWidth(),getHeight(),null);
g.setColor(Color.BLACK);
g.fillOval(50,50,50,50);
g.dispose();
bs.show();
}
public void run() {
long lastTime = System.nanoTime();
double nsPerTick = 1000000000D/60D;
int ticks = 0;
int frames = 0;
long lastTimer = System.currentTimeMillis();
double delta = 0;
while(running){
long now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
boolean shouldRender = true;
while(delta >= 1){
ticks++;
tick();
delta--;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(shouldRender){
frames++;
render();
}
if(System.currentTimeMillis() - lastTimer >= 1000){
lastTimer += 1000;
frame.setTitle("2D Game! FPS: " + frames + " UPS: " + ticks);
frames = 0;
ticks = 0;
}
}
}
public static void main(String[] args) {
new Game().start();
}
}
SpriteSheet class:
package com.swainchris.twodgame.gfx;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public class SpriteSheet {
public String path;
public int width;
public int height;
public int[] pixels;
public SpriteSheet(String path){
BufferedImage image = null;
try {
image = ImageIO.read(SpriteSheet.class.getResourceAsStream(path));
} catch (IOException e) {
e.printStackTrace();
}
if(image == null){
return;
}
this.path = path;
this.width = image.getWidth();
this.height = image.getHeight();
pixels = image.getRGB(0,0,width,height,null,0,width);
for(int i = 0; i < pixels.length; i++){
pixels[i] = (pixels[i] & 0xff)/64;
}
for(int i = 0; i<8; i++){
System.out.println(pixels[i]);
}
}
}
Error:
Exception in thread "main" java.lang.IllegalArgumentException: input == null!
at javax.imageio.ImageIO.read(Unknown Source)
at com.swainchris.twodgame.gfx.SpriteSheet.<init>(SpriteSheet.java:20)
at com.swainchris.twodgame.Game.<init>(Game.java:30)
at com.swainchris.twodgame.Game.main(Game.java:137)
Works now, all i did was copy all the code to a new project file. Didn't change anything at all besides the name of the project. No idea why this worked but it did.

Paint in very high resolution

I am currently trying to paint a Waveform in very high resolution (due to zooming). The Waveform is drawn in a JScrollPane therefore. I want to be able to paint about 50.000-60.000 pixels width with it.
Unfortunately it stops painting properly at about 34000 pixel width. The issue is that it doesn't paint ca. the first screen size anymore, but the rest is drawn properly. As I only have very little experience in graphical stuff I thought you might be able to help me to decide how to fix this as best as possible.
I thought about dealing with it via repainting the first screen size (e.g. with repaint(Rectangle)) or maybe with parting the picture into 3 or more frames. If I choose the 2nd option I don't know if I should just part it an paint it all together or only paint it when its visible on the Viewport. Or maybe there is another better solution I cant figure out?
I hope you can help me with this and save me loads of hours to try out everything. Thanks in advance.
So here is the requested executable. You can see improper drawing at about 34.000 pixels width. Current pixels can be read in System.out . Drawing doesnt work with mp3, as not supported. I suggest tryouts with .wav.
Main Class:
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
public class Main {
private JFrame mainFrame;
private JPanel upperPanel;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Main window = new Main();
window.mainFrame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Initialize Application
*/
public Main() {
initializeMainFrame();
initializePanels();
}
private void initializePanels() {
upperPanel = new MainPanel();
upperPanel.setPreferredSize(new Dimension(1000, 500));
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.NORTH;
c.weightx = 1.0;
c.weighty = 1.0;
c.fill = GridBagConstraints.BOTH;
c.gridy = 0;
c.gridwidth = GridBagConstraints.REMAINDER;
mainFrame.add(upperPanel, c);
}
private void initializeMainFrame() {
mainFrame = new JFrame("Waveform Example");
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicsEnvironment ge = GraphicsEnvironment
.getLocalGraphicsEnvironment();
Rectangle gebounds = ge.getMaximumWindowBounds();
mainFrame.setSize(gebounds.getSize());
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
mainFrame.setLayout(new GridBagLayout());
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem importAudio = new JMenuItem("Import Audio");
menuBar.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.WEST;
c.weightx = 0.3;
c.weighty = 0.3;
c.gridx = 0;
c.gridy = 0;
mainFrame.setJMenuBar(menuBar);
menuBar.add(fileMenu, c);
c.gridx = 1;
c.gridy = 0;
fileMenu.add(importAudio);
importAudio.addActionListener(new importAudioActionListener());
}
private class importAudioActionListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
File f = getFile();
if (f != null) {
AudioInfo audioInfo = createAudioInfo(f);
mainFrame.remove(upperPanel);
upperPanel = new MainPanel(audioInfo);
upperPanel.setPreferredSize(new Dimension(1000, 500));
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.NORTH;
c.gridy = 0;
c.weightx = 1.0;
c.weighty = 1.0;
c.fill = GridBagConstraints.BOTH;
c.gridwidth = GridBagConstraints.REMAINDER;
mainFrame.add(upperPanel, c);
mainFrame.pack();
}
}
private AudioInfo createAudioInfo(File f) {
AudioInputStream audioInputStream = null;
try {
audioInputStream = AudioSystem.getAudioInputStream(f);
} catch (UnsupportedAudioFileException e1) {
System.out.println("Invalid Audio Format");
} catch (IOException e1) {
System.out.println("Invalid Input File");
}
AudioInfo retInfo = new AudioInfo(audioInputStream,
(int) f.length());
return retInfo;
}
private File getFile() {
// New file chooser only shows and accepts MP3 files.
JFileChooser fc = new JFileChooser();
fc.setAcceptAllFileFilterUsed(false);
fc.showOpenDialog(null);
File f = null;
try {
f = fc.getSelectedFile();
} catch (Exception fnfe) {
f = null;
System.out.println("File not found!");
}
return f;
}
}
}
Panel that contains JPanel:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class MainPanel extends JPanel implements MouseWheelListener,
ComponentListener {
private boolean finishedZoom = true;
private int mouseX;
private static final long serialVersionUID = 1L;
private AudioInfo audioInfo;
private int scale = 1;
private Dimension panelSize;
private int mouseXScaled;
private int mouseYScaled;
private JScrollPane scrollPane;
private int sizeNormalizer = 150;
private JPanel thisPanel = this;
private JPanel content;
public MainPanel() {
}
public MainPanel(AudioInfo audioInfo) {
this.audioInfo = audioInfo;
this.setLayout(new BorderLayout());
panelSize = new Dimension(1000, 500);
content = getContent();
scrollPane = new JScrollPane(content);
scrollPane.setPreferredSize(panelSize);
scrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
this.add(scrollPane, BorderLayout.CENTER);
this.setPreferredSize(panelSize);
content.addMouseWheelListener(this);
content.addComponentListener(this);
}
private JPanel getContent() {
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
c.weighty = 1.0;
JPanel retContent = new JPanel(false);
retContent.setPreferredSize(panelSize);
retContent.setLayout(new GridBagLayout());
WaveformPanel waveformPanel = new WaveformPanel(audioInfo);
waveformPanel.setPreferredSize(panelSize);
retContent.setBackground(Color.green);
c.gridwidth = GridBagConstraints.REMAINDER; // end row
retContent.add(waveformPanel, c);
return retContent;
}
public void mouseWheelMoved(MouseWheelEvent e) {
boolean changed = false;
double notches = e.getWheelRotation();
if (e.isControlDown() && finishedZoom) {
int newScale = (int) (scale + notches * (-1) * 2);
int newWidth = (int) ((thisPanel.getPreferredSize().getWidth()) * newScale);
if (newWidth > content.getPreferredSize().getWidth()) {
System.out.println("new width original: " + newWidth);
content.setVisible(false);
content.setPreferredSize(new Dimension(
newWidth,
(int) ((thisPanel.getPreferredSize().getHeight() - sizeNormalizer) / 3 * 2)));
content.setVisible(true);
mouseXScaled = e.getX() / scale * newScale;
mouseYScaled = e.getY() / scale * newScale;
scale = newScale;
changed = true;
} else if (newWidth < content.getPreferredSize().getWidth()
&& newWidth > thisPanel.getWidth()) {
content.setVisible(false);
content.setPreferredSize(new Dimension(
newWidth,
(int) ((thisPanel.getPreferredSize().getHeight() - sizeNormalizer) / 3 * 2)));
content.setVisible(true);
mouseXScaled = e.getX() / scale * newScale;
mouseYScaled = e.getY() / scale * newScale;
scale = newScale;
changed = true;
} else if (newWidth <= thisPanel.getWidth()) {
newWidth = (int) (thisPanel.getPreferredSize().getWidth());
newScale = 1;
content.setVisible(false);
content.setPreferredSize(new Dimension(
newWidth,
(int) ((thisPanel.getPreferredSize().getHeight() - sizeNormalizer) / 3 * 2)));
content.setVisible(true);
mouseXScaled = e.getX() / scale * newScale;
mouseYScaled = e.getY() / scale * newScale;
scale = newScale;
}
if (changed) {
finishedZoom = false;
}
mouseX = e.getX();
} else if (!e.isControlDown()) {
int scrollBarValue = scrollPane.getHorizontalScrollBar().getValue();
Rectangle viewRect = scrollPane.getViewport().getViewRect();
scrollPane
.getHorizontalScrollBar()
.setValue(
(int) ((int) scrollBarValue + ((viewRect.width - 100) * notches)));
}
}
public int getHorizontalScroll() {
return scrollPane.getHorizontalScrollBar().getValue();
}
#Override
public void componentHidden(ComponentEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void componentMoved(ComponentEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void componentResized(ComponentEvent arg0) {
if (mouseXScaled != 0 && mouseYScaled != 0) {
int scrollBarVal = scrollPane.getHorizontalScrollBar().getValue();
int newX = (int) (scrollBarVal + mouseXScaled - mouseX);
scrollPane.getHorizontalScrollBar().setValue(newX);
finishedZoom = true;
}
}
#Override
public void componentShown(ComponentEvent arg0) {
// TODO Auto-generated method stub
}
}
AudioInfo Class:
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
public class AudioInfo {
private static final int NUM_BITS_PER_BYTE = 8;
private AudioInputStream encodedInputSream;
private int[][] encodedSamplesContainer;
private byte[] encodedBuffer;
// cached values
private int sampleMax = 0;
private int sampleMin = 0;
private double biggestSample;
public AudioInfo(AudioInputStream encodedInputStream, int fileSize) {
encodedBuffer = new byte[fileSize];
this.encodedInputSream = encodedInputStream;
encodedBuffer = createSampleArrayCollection(encodedInputStream,
encodedBuffer);
encodedSamplesContainer = getSampleArray(encodedBuffer);
if (sampleMax > sampleMin) {
biggestSample = sampleMax;
} else {
biggestSample = Math.abs(((double) sampleMin));
}
}
protected int getNumberOfChannels() {
return 2;
}
/**
* Reads the audio input stream into a tmp array and then inserts the tmp
* array into a buffer array. Resets the mark of the audio input stream
* after finish buffering. Then cuts the array from fileSize*10 to the final
* size.
*/
private byte[] createSampleArrayCollection(AudioInputStream inputStream,
byte[] inBuffer) {
byte[] buffer = new byte[inBuffer.length];
int sumReadBytes = 0;
try {
// inputStream.mark(Integer.MAX_VALUE);
//inputStream.reset();
boolean end = false;
while (!end) {
int available = inputStream.available();
if (available <= 0) {
end = true;
}
if (!end) {
byte[] tmp = new byte[available];
int readBytes = inputStream.read(tmp);
tmp = cutArray(tmp, readBytes);
insertArray(buffer, tmp, sumReadBytes);
sumReadBytes += readBytes;
}
}
//inputStream.reset();
} catch (IOException e) {
e.printStackTrace();
}
buffer = cutArray(buffer, sumReadBytes);
return buffer;
}
/**
*
* #param cutThis
* array that has to be cut
* #param cutPoint
* index at which the array will be cut off
* #return the buffer array cut off at the point of cutpoint
*/
private byte[] cutArray(byte[] cutThis, int cutPoint) {
byte[] tmp = new byte[cutPoint];
for (int i = 0; i < tmp.length; i++) {
tmp[i] = cutThis[i];
}
return tmp;
}
/**
*
* #param insertIntoThis
* the array you want to insert in the other
* #param tmp
* the array that is going to be inserted
*/
private byte[] insertArray(byte[] insertIntoThis, byte[] tmp,
int nextEmptyField) {
for (int i = 0; i < tmp.length; i++) {
insertIntoThis[nextEmptyField] = tmp[i];
nextEmptyField++;
}
return insertIntoThis;
}
/**
*
* #param eightBitByteArray
* Array of an eight bit byte array.
* #return int audio information array for every channel.
*/
private int[][] getSampleArray(byte[] eightBitByteArray) {
int[][] toReturn = new int[getNumberOfChannels()][eightBitByteArray.length
/ (2 * getNumberOfChannels()) + 1];
int index = 0;
// loop through the byte[]
for (int t = 0; t + 4 < eightBitByteArray.length;) {
// for each iteration, loop through the channels
for (int a = 0; a < getNumberOfChannels(); a++) {
// do the byte to sample conversion
// see AmplitudeEditor for more info
int low = (int) eightBitByteArray[t];
t++;
int high = (int) eightBitByteArray[t];
t++;
int sample = (high << 8) + (low & 0x00ff);
if (sample < sampleMin) {
sampleMin = sample;
} else if (sample > sampleMax) {
sampleMax = sample;
}
// set the value.
toReturn[a][index] = sample;
}
index++;
}
return toReturn;
}
/**
*
* #param panelHeight
* #return calculated yScaleFactor
*/
public double getYScaleFactor(int panelHeight) {
return (panelHeight / (biggestSample * 2 * 1.5));
}
/**
*
* #param channel
* number of the channel you want the audio information of
* #return int array of the audio information of the given channel.
*/
protected int[] getAudio(int channel) {
return encodedSamplesContainer[channel];
}
/**
*
* #param xScale
* #return calculates the increment for given xScale
*/
protected int getIncrement(double xScale) {
try {
int increment = (int) (encodedSamplesContainer[0].length / (encodedSamplesContainer[0].length * xScale));
return increment;
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
}
Waveform Panel Class:
import javax.swing.*;
import java.awt.*;
public class WaveformPanel extends JPanel {
private static final long serialVersionUID = 1L;
private static final Color BACKGROUND_COLOR = Color.black;
private static final Color REFERENCE_LINE_COLOR = Color.blue;
private static final Color WAVEFORM_COLOR = Color.blue;
private AudioInfo helper;
private int[] samples;
public WaveformPanel(AudioInfo helper) {
super();
this.helper = helper;
setBackground(BACKGROUND_COLOR);
samples = helper.getAudio(0);
}
/**
* Paints the component of the melted channel audio data.
*/
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int lineHeight = getHeight() / 2;
g.setColor(REFERENCE_LINE_COLOR);
g.drawLine(0, lineHeight, (int) getWidth(), lineHeight);
drawWaveform(g, samples);
}
protected double getXScaleFactor(int panelWidth) {
double width = (double) panelWidth;
return (width / ((double) samples.length));
}
private double getIncrement(double xScale) {
try {
double increment = (samples.length / (samples.length * xScale));
return increment;
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
/**
* #param g
* graphic of this panel
* #param samples
* audio samples of a channel
*
* Draws a waveform with given input on a graphic.
*/
protected void drawWaveform(Graphics g, int[] samples) {
int buffer = 30;
if (samples == null) {
return;
}
double oldX = 0;
double xIndex = 0;
double increment = getIncrement(getXScaleFactor(getWidth() - buffer * 2));
g.setColor(WAVEFORM_COLOR);
System.out.println("width: " + this.getWidth());
double t = 0;
int drawLength = samples.length;
for (; t < drawLength; t += increment) {
double scaleFactor = helper.getYScaleFactor(getHeight());
double scaledSample = samples[(int) t] * scaleFactor;
double y = ((getHeight() / 2) - (scaledSample));
double yMirror = ((getHeight() / 2) + scaledSample);
g.drawLine((int) (oldX + buffer), (int) yMirror,
(int) (xIndex + buffer), (int) y);
xIndex++;
oldX = xIndex;
}
}
}
As an alternative, see this MCTaRE that successfully renders an image that is twice that width. Scroll it to half width (or any width for that matter) to see ..the image with no artifacts.
Note that I called setPreferredSize in that example to save a few code lines, but see Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? (Yes.)
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class BigImageWaveform {
public static void main(String[] args) {
final BufferedImage bi = new BufferedImage(
2*34000, 500, BufferedImage.TYPE_INT_RGB);
draw(bi);
Runnable r = new Runnable() {
#Override
public void run() {
JScrollPane jsp = new JScrollPane(
new JLabel(new ImageIcon(bi)),
JScrollPane.VERTICAL_SCROLLBAR_NEVER,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
Dimension d = jsp.getPreferredSize();
jsp.setPreferredSize(new Dimension(1000, (int)d.getHeight()));
JOptionPane.showMessageDialog(null, jsp);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
public static void draw(BufferedImage bi) {
Graphics2D g = bi.createGraphics();
int w = bi.getWidth();
int h = bi.getHeight();
GradientPaint gp = new GradientPaint(
0f,0f,Color.RED,
101f,0f,Color.GREEN,true);
g.setPaint(gp);
g.fillRect(0,0,w,h);
gp = new GradientPaint(
0f,0f,new Color(0,0,255,128),
97f,103f,new Color(220,0,220,164), true);
g.setPaint(gp);
g.fillRect(0,0,w,h);
gp = new GradientPaint(
0f,0f,new Color(0,0,0,0),
(float)w,0f,new Color(0,0,0,128), true);
g.setPaint(gp);
g.fillRect(0,0,w,h);
g.dispose();
}
}
After testing this on two more potent Windows Systems I came to the opinion that its either a Linux problem or a performance problem, as my laptop is about 2 years old and was pretty cheap.
If anybody could test it out on a Linux System it would be great. Otherwise I am going to mark this issue as answered.

Move objects in Java Swing Window

I have code here that displays a swing window with four jacks on it. I would like to be able to click and move the jacks around the window. I know they need to be redrawn but I'm not sure how to go about that.
How can I make it so a user could click and move the cards?
package p2test;
import javax.swing.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.awt.*;
public class main
{
public static final int PERIMETER_BEVEL = 20; //space between panel border and perimeter cards
public static final int LEFT_PERIMETER_BEVEL = 98;
public static final int INTERIOR_BEVEL = 5; //space between cards
public static final int CARD_HEIGHT = 97;
public static final int CARD_WIDTH = 73;
public static final int PANEL_HEIGHT = (2*PERIMETER_BEVEL) + (4*CARD_HEIGHT) + (3*INTERIOR_BEVEL);
public static final int PANEL_WIDTH = (2*PERIMETER_BEVEL) + (14*CARD_WIDTH) + (13*INTERIOR_BEVEL);
public static final String BACKGROUND_COLOR = "#64C866"; //window background color [hex]
public static final String CARD_FOLDER = "cardImages"; //default folder containing images
public static final String [] RANKS = { "jack"};
public static final ArrayList<String> ranks = new ArrayList<String>(Arrays.asList(RANKS));
public static void main(String[] args)
{
JFrame window = new JFrame("deck");
JPanel panel = new JPanel() {
public void paintComponent(Graphics g) { //find each rank of card in increasing
super.paintComponent(g); //order as specified in the array. All
File[] files = new File(CARD_FOLDER).listFiles(); //ranks appear in the same suit order in
//the filesystem so suits will automatically
//be in order when printing in groups of four
//cards.
int counter = 0;
for(String rank : ranks) {
for(File filename : files) {
if(filename.getName().contains(rank)) {
new ImageIcon(filename.getPath()).paintIcon(this, g,
LEFT_PERIMETER_BEVEL + (counter/4) * (CARD_WIDTH + INTERIOR_BEVEL),
PERIMETER_BEVEL + (3-(counter%4)) * (CARD_HEIGHT + INTERIOR_BEVEL));
counter++;
//counter/4 keeps track of the correct column
//3-(counter%4) keeps track of the correct row
//in which to print the card image
}
}
}
}
};
panel.setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.setBackground(Color.decode(BACKGROUND_COLOR));
window.add(panel);
window.setVisible(true);
window.pack();
}
}
I was able to get the cards to move by adding this.
A much better way of implementing this would be to follow what MadProgrammer and Andrew Thompson suggested.
public static MouseInputAdapter mouseHandler = new MouseInputAdapter(){
//internal JLabel displacement on mouse press for smooth dragging
public int labelDisX;
//internal JLabel displacement on mouse press for smooth dragging
public int labelDisY;
public void mousePressed(MouseEvent e) {
labelDisX = e.getX();
labelDisY = e.getY();
//move the card above all others
e.getComponent().getParent().setComponentZOrder(e.getComponent(), 0);
//repaint so card moves above others
e.getComponent().getParent().repaint();
}
public void mouseDragged (MouseEvent e) {
JPanel panel = (JPanel) e.getComponent().getParent();
//get preliminary new X coordinate
int newX = e.getComponent().getX() + e.getX() - labelDisX;
//get preliminary new Y coordinate
int newY = e.getComponent().getY() + e.getY() - labelDisY;
//here we check that the card is not
//being moved off the panel. If the
//user does try and do this then
//make the new coordinates such that
//the card is bounded by the panel's
//edges [extra credit]
if(newX > panel.getWidth() - CARD_WIDTH) {
newX = panel.getWidth() - CARD_WIDTH;
}
if(newY > panel.getHeight() - CARD_HEIGHT) {
newY = panel.getHeight() - CARD_HEIGHT;
}
if(newX < 0) { newX = 0; }
if(newY < 0) { newY = 0; }
e.getComponent().setLocation(newX, newY);
}
};

Absolute Positioning Graphic JPanel Inside JFrame Blocked by Blank Sections

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:

Categories

Resources