The code that I have here is using a MouseAdapter to listen for the user to "draw" a box around the area of an image that they would like to zoom in on and calculate the ratio of the box to the image. It then resizes the image to the calculated ratio. This part works.
The issue that I am having is making the JScrollPane view appear as if it is still at the same top left position after the image has been resized. I have tried several methods that seem to have gotten close to the result I want but not exactly.
This is the Listener that finds the scale ratio and sets the position:
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.Graphics;
import java.awt.Point;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.JComponent;
public class DynamicZoom extends MouseAdapter {
private Point start;
private Point end;
private double zoom = 1.0;
private JScrollPane pane;
private JViewport port;
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1) {
this.pane = (JScrollPane)e.getSource();
this.port = pane.getViewport();
start = e.getPoint();
}
}
public void mouseReleased(MouseEvent e) {
if(this.pane != null) {
Point curr = this.port.getViewPosition();
end = e.getPoint();
ImageComponent canvas = (ImageComponent)this.port.getView();
zoom = canvas.getScale();
double factor = 0.0;
double selectedWidth = Math.abs(end.getX() - start.getX());
double selectedHeight = Math.abs(end.getY() - start.getY());
if(selectedWidth > selectedHeight)
factor = this.port.getWidth() / selectedWidth;
else
factor = this.port.getHeight() / selectedHeight;
zoom *= factor;
int x = (int)((start.x+curr.x)*zoom);
int y = (int)((start.y+curr.y)*zoom);
Point point = new Point(x, y);
((ImageComponent)(this.port.getView())).setScale(zoom);
ResizeViewport.resize(pane);
this.port.setViewPosition(point);
}
}
public void mouseDragged(MouseEvent e) {
if(this.pane != null) {
Graphics g = this.port.getGraphics();
int width = this.start.x - e.getX();
int height = this.start.y - e.getY();
int w = Math.abs( width );
int h = Math.abs( height );
int x = width < 0 ? this.start.x : e.getX();
int y = height < 0 ? this.start.y : e.getY();
g.drawRect(x, y, w, h);
this.port.repaint();
}
}
}
This is the ImageComponent it resizes and displays the image:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
public class ImageComponent extends JComponent {
private static final long serialVersionUID = 1975488835382044371L;
private BufferedImage img = null;
private double scale = 0.0;
public ImageComponent() {}
public ImageComponent(BufferedImage img) {
this.displayPage(img);
}
#Override
public void paint(Graphics g) {
Graphics2D g2 = ((Graphics2D)g);
if(this.img != null) {
int width = (int)(this.img.getWidth() * this.scale);
int height = (int)(this.img.getHeight() * this.scale);
this.setPreferredSize(new Dimension(width, height));
g2.drawImage(this.img, 0, 0, width, height, null, null);
g2.dispose();
}
}
public void displayPage(BufferedImage pic) {
if(img != null) {
this.img = pic;
}
}
public BufferedImage getPage() {
return this.img;
}
public void setScale(double ratio) {
if(ratio > .04) {
this.scale = ratio;
this.repaint();
}
}
public double getScale() {
return this.scale;
}
}
The ResizeViewport forces the viewport to show the entire ImageComponent when it is scaled up because otherwise it will clip the image at the size that it previously was:
import java.awt.Dimension;
import javax.swing.JScrollPane;
public class ResizeViewport {
public static void resize(JScrollPane scroll) {
int vw = scroll.getWidth()-scroll.getVerticalScrollBar().getWidth();
int vh = scroll.getHeight()-scroll.getHorizontalScrollBar().getHeight();
scroll.getViewport().setViewSize(new Dimension(vw, vh));
}
}
It turns out there is nothing wrong with the math used to calculate the position or the way I designed the code. The problem was that the ImageComponent was still painting in another Thread when the position was being calculated; therefore returning "false" values for the getWidth() and getHeight() methods. I solved the issue using the following code:
EventQueue.invokeLater(new Runnable() {
public void run() {
port.setViewPosition(new Point(x,y));
}
});
This allows the painting to finish before trying to calculate the size of the image and setting the position in the JScrollPane. Problem solved. I would still like to throw a thanks out to the people that took the time to at least review this issue.
I had a similar issue but sometimes the panel got painted wrong the first time after resize, after hours of trying to find a workaround I found a solution!
After a resize of the panel in the scrollpane it is best is to destroy viewport by setting to null, and then adding panel back to scrollpane that will recreate a new viewport and everything is working!
// Old viewport has to be replaced
scrollPane.setViewport(null);
scrollPane.setViewportView(zoomablePanel);
/Anders
Related
I'm trying to do a simulation program using java swing gui. I have 2 images that are same but one is blurred all the way and other one is normal. And a 3rd image that is just a transparent rectangular frame in .png format. What I want to achieve is that, I will drag the transparent rectangular box over the blurred image and it will reveal the non-blurred image below. How can I achieve this?
P.S: Images are loaded into the program using JLabel onto JLayeredPane and JFrame. Also transparent rectangular box has a mouse listener.
A "overlay" illusion...
Basically this is going to generate a subImage of the un-blurred image based on the current mouse position and view port size, this is then blended with the "overlay effect" and rendered on top of the blurred image, creating the "illusion" of a cutout effect.
The following example will follow the mouse around, exposing a 200x200 pixel area of the image around the mouse cursor.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage blurredImage;
private BufferedImage normalImage;
private BufferedImage spyScopeImage;
private int viewPortSize = 200;
private Point currentLocation;
public TestPane() throws IOException {
blurredImage = ImageIO.read(getClass().getResource("/images/PosterBlurred.png"));
normalImage = ImageIO.read(getClass().getResource("/images/Poster.png"));
spyScopeImage = new BufferedImage(viewPortSize, viewPortSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D masked = spyScopeImage.createGraphics();
Color transparent = new Color(255, 0, 0, 0);
Color fill = Color.RED;
RadialGradientPaint rgp = new RadialGradientPaint(
new Point2D.Double(viewPortSize / 2d, viewPortSize / 2d),
viewPortSize,
new float[]{0f, 0.5f, 1f},
new Color[]{fill, transparent, transparent});
// masked.setComposite(AlphaComposite.DstAtop);
masked.setPaint(rgp);
masked.fill(new Rectangle(0, 0, viewPortSize, viewPortSize));
masked.dispose();
MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
currentLocation = e.getPoint();
repaint();
}
#Override
public void mouseEntered(MouseEvent e) {
currentLocation = e.getPoint();
repaint();
}
#Override
public void mouseExited(MouseEvent e) {
currentLocation = null;
repaint();
}
};
addMouseMotionListener(mouseAdapter);
}
#Override
public Dimension getPreferredSize() {
if (blurredImage != null) {
return new Dimension(blurredImage.getWidth(), blurredImage.getHeight());
}
return new Dimension(800, 600);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (blurredImage != null) {
int x = (getWidth() - blurredImage.getWidth()) / 2;
int y = (getHeight() - blurredImage.getHeight()) / 2;
g2d.drawImage(blurredImage, x, y, this);
if (currentLocation != null && normalImage != null) {
int mouseX = currentLocation.x - x;
int mouseY = currentLocation.y - y;
int viewPortOffset = viewPortSize / 2;
int minX = Math.max(0, mouseX - viewPortOffset);
int minY = Math.max(0, mouseY - viewPortOffset);
int maxX = Math.min(normalImage.getWidth(), mouseX + viewPortSize);
int maxY = Math.min(normalImage.getHeight(), mouseY + viewPortSize);
int viewX = minX - viewPortOffset;
int viewY = minY - viewPortOffset;
BufferedImage subimage = normalImage.getSubimage(minX, minY, maxX - minX, maxY - minY);
// Here we're going to "mask" the sub image and the "spy scope" effect together
BufferedImage masked = new BufferedImage(viewPortSize, viewPortSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D mg = masked.createGraphics();
mg.drawImage(subimage, 0, 0, this);
mg.setComposite(AlphaComposite.DstAtop);
mg.drawImage(spyScopeImage, 0, 0, this);
mg.dispose();
g2d.drawImage(masked, x + minX, y + minY, this);
}
}
g2d.dispose();
}
}
}
Alternatively...
You could create a "masked" version of the blurred image with the "scope effect" which exposes the image rendered below it, but agin, this will be expensive, as each time you're creating a new image the size of the original blurred image.
This concept is demonstrated in How to create a transparent shape in the image
I created a graphical component that allows you to view an image and allows you to make a selection of a part of the image: the selection of a portion of the image is accomplished by drawing a rectangle on this image (using drag-and-drop).
To this purpose, I used this example, which created a subclass of JLabel in order to draw the image and in order to deal with the drawing of the rectangle. Then I put an instance of this subclass within a JPanel, in order to have the image always positioned at the center of the panel.
FigurePanel.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
public class FigurePanel extends JPanel
{
private SelectionLabel imageLabel = null;
public FigurePanel()
{
this.setLayout(new GridBagLayout());
imageLabel = new SelectionLabel();
this.add(imageLabel, null);
}
public void setImage(Image image)
{
imageLabel.setImage(image);
}
private class SelectionLabel extends JLabel
{
private Rectangle currentRect = null;
private Rectangle rectToDraw = null;
private final Rectangle previousRectDrawn = new Rectangle();
public SelectionLabel()
{
super();
setOpaque(true);
SelectionListener listener = new SelectionListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
public void setImage(Image image)
{
currentRect = null;
rectToDraw = null;
previousRectDrawn.setBounds(0, 0, 0, 0);
setIcon(new ImageIcon(image));
}
private class SelectionListener extends MouseInputAdapter
{
#Override
public void mousePressed(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect = new Rectangle(x, y, 0, 0);
updateDrawableRect(getWidth(), getHeight());
repaint();
}
#Override
public void mouseDragged(MouseEvent e)
{
updateSize(e);
}
#Override
public void mouseReleased(MouseEvent e)
{
updateSize(e);
}
/*
* Update the size of the current rectangle
* and call repaint. Because currentRect
* always has the same origin, translate it
* if the width or height is negative.
*
* For efficiency (though
* that isn't an issue for this program),
* specify the painting region using arguments
* to the repaint() call.
*
*/
void updateSize(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect.setSize(x - currentRect.x,
y - currentRect.y);
updateDrawableRect(getWidth(), getHeight());
Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
repaint(totalRepaint.x, totalRepaint.y,
totalRepaint.width, totalRepaint.height);
}
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g); //paints the background and image
//If currentRect exists, paint a box on top.
if (currentRect != null) {
//Draw a rectangle on top of the image.
g.setXORMode(Color.white); //Color of line varies
//depending on image colors
g.drawRect(rectToDraw.x, rectToDraw.y,
rectToDraw.width - 1, rectToDraw.height - 1);
System.out.println("rectToDraw: " + rectToDraw);
}
}
private void updateDrawableRect(int compWidth, int compHeight)
{
int x = currentRect.x;
int y = currentRect.y;
int width = currentRect.width;
int height = currentRect.height;
//Make the width and height positive, if necessary.
if (width < 0) {
width = 0 - width;
x = x - width + 1;
if (x < 0) {
width += x;
x = 0;
}
}
if (height < 0) {
height = 0 - height;
y = y - height + 1;
if (y < 0) {
height += y;
y = 0;
}
}
//The rectangle shouldn't extend past the drawing area.
if ((x + width) > compWidth) {
width = compWidth - x;
}
if ((y + height) > compHeight) {
height = compHeight - y;
}
//Update rectToDraw after saving old value.
if (rectToDraw != null) {
previousRectDrawn.setBounds(
rectToDraw.x, rectToDraw.y,
rectToDraw.width, rectToDraw.height);
rectToDraw.setBounds(x, y, width, height);
} else {
rectToDraw = new Rectangle(x, y, width, height);
}
}
}
}
FigurePanelTest.java
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
public class FigurePanelTest extends JFrame
{
public FigurePanelTest()
{
FigurePanel imagePanel = new FigurePanel();
JScrollPane imageScrollPane = new JScrollPane();
imageScrollPane.setPreferredSize(new Dimension(420, 250));
imageScrollPane.setViewportView(imagePanel);
JButton imageButton = new JButton("Load Image");
imageButton.addActionListener(
new ActionListener()
{
#Override
public void actionPerformed(ActionEvent evt)
{
JFileChooser fc = new JFileChooser();
int returnValue = fc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fc.getSelectedFile();
System.out.println(selectedFile.getName());
try
{
Image image = ImageIO.read(selectedFile.getAbsoluteFile());
imagePanel.setImage(image);
imageScrollPane.getViewport().setViewPosition(new Point(0, 0));
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
);
Container container = getContentPane();
container.setLayout(new BorderLayout());
container.add(imageScrollPane, BorderLayout.CENTER);
container.add(imageButton, BorderLayout.NORTH);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
/**
* #param args the command line arguments
*/
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FigurePanelTest().setVisible(true);
}
});
}
}
The private class SelectionLabel is the class SelectionArea from this example.
When a new rectangle is drawn, a message is printed on the console. Now I would replace the printing of the message with the firing of a custom event, so that the position and size of the rectangle are accessible to the application business logic.
I read how to create a custom event in Java. Moreover, this article identifies two super types for creating events: EventObject and AWTEvent. This articles states:
Normally you extend AWTEvent for events generated by a graphical
component and EventObject any other time.
Since the event concerning the selection of a part of the image is generated by a graphical component (that is the FigurePanel panel), I could implement the ImageSelectionEvent class by extending AWTEvent, as the following code snippet.
public class ImageSelectionEvent extends AWTEvent
{
public ImageSelectionEvent(Object source, int id) {
super(source, id);
}
}
The documentation identifies the id as the event type. So, what value should be assigned to this parameter?
Moreover, why does the constructor of EventObject class be devoid of the id parameter?
When creating an event class, you must guarantee that the event is
immutable. The event generator will share the same event instance
among the listeners; so ensure any one listener cannot change the
event's state.
What about this?
I don't know what is needed to create a custom event.
However, since you are extending JLabel maybe you can just create a PropertyChangeEvent.
To generated the event you would just use something like:
firePropertyChange("selectionRectangle", oldRectangle, newRectangle);
Then you can use a PropertyChangeListener to listen for "selectionRectangle" changes.
The Javadoc for AWTEvent says:
Subclasses of this root AWTEvent class defined outside of the java.awt.event package should define event ID values greater than the value defined by RESERVED_ID_MAX.
This value is 1999. You can set it to whatever you want that's higher than that. This value is specified by all the different types of Swing events, and Swing uses values that are less than that. For example, the MouseEvent event types use values from 500-507.
The main thing is to use a consistent value for your events.
Finally, I would consider subclassing ComponentEvent over AWTEvent as the source of your event is a Component, not an Object.
I am trying to implement a zoom tool for my game editor. I am using a Color[] to store all the "Colors/tiles" and I want to be able to zoom inside my JPanel to view all the tiles upclose or from far away i.e zoomed in or zoomed out. These are some requirements that I would like to have(Any examples are appreciated and they don't need to include all or any of the requirements. They are there to help you understand what I am aiming for):
Zooming should be determined by a zoom variable that can be changed dynamicly(prefferably mouseWheel)
When zoomed(in or out) you should be able to navigate horisontaly and vertically with as little as possible tearing or other graphical glitches(using the JScrollBar s in the example)
The zooming should be based on where the mouse cursor is and zoom towards that point
The zooming can use anything inside normal Java which means no external libraries.
This is a very simple version of my editor. I added some test code for zooming but it doesn't work. I am giving you this code so you have something to start from if neccesary. See the PaintComponent method and the mouseWheel listener.
EDITED: Thanks to #trashgod you can now place colors at the right position. See MouseTest class!
The tearing is gone.
package stuff;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
public class ZoomPane extends JPanel implements MouseWheelListener, Scrollable {
private int width, height, screenWidth, screenHeight, tileSize;
private Color[] colorMap;
private double scale = 1.0;
// Zooming
AffineTransform at;
class MouseTest extends MouseAdapter {
public void mousePressed(MouseEvent e) {
try {
int newX = (int) at.inverseTransform(e.getPoint(), null).getX();
int newY = (int) at.inverseTransform(e.getPoint(), null).getY();
colorMap[(newX / tileSize) + ((newY / tileSize) * width)] = getRandomColor();
} catch (NoninvertibleTransformException e1) {
e1.printStackTrace();
}
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
super.mouseDragged(e);
try {
System.out.println("Dragging");
int newX = (int) at.inverseTransform(e.getPoint(), null).getX();
int newY = (int) at.inverseTransform(e.getPoint(), null).getY();
colorMap[(newX / tileSize) + ((newY / tileSize) * width)] = getRandomColor();
} catch (NoninvertibleTransformException e1) {
e1.printStackTrace();
}
repaint();
}
}
public ZoomPane(int width, int height, int tileSize) {
super();
this.width = width;
this.height = height;
this.screenWidth = width * tileSize;
this.screenHeight = height * tileSize;
this.tileSize = tileSize;
this.colorMap = new Color[width * height];
addMouseWheelListener(this);
addMouseListener(new MouseTest());
addMouseMotionListener(new MouseTest());
}
public void paintComponent(Graphics g) {
super.paintComponents(g);
final Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Scale
at = null;
at = g2d.getTransform();
// Translate code here?
at.scale(scale, scale);
g2d.setTransform(at);
final Rectangle clip = g2d.getClipBounds();
g2d.setColor(Color.DARK_GRAY);
g2d.fill(clip);
int topX = clip.x / tileSize;
int topY = clip.y / tileSize;
int bottomX = clip.x + clip.width / tileSize + 1 + (int) (tileSize * scale);
int bottomY = clip.y + clip.height / tileSize + 1;
// Draw colors
for (int y = topY; y < bottomY; y++) {
for (int x = topX; x < bottomX; x++) {
Rectangle r = new Rectangle(width, height);
if (r.contains(x, y) && colorMap[x + y * width] != null) {
g2d.setColor(colorMap[x + y * width]);
g2d.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
}
g2d.dispose();
}
private Color getRandomColor() {
Random rand = new Random();
return new Color(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255));
}
#Override
public Dimension getPreferredSize() {
return new Dimension((int) (screenWidth), (int) (screenHeight));
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
double delta = 0.05 * e.getPreciseWheelRotation();
if (scale + delta > 0)
scale += delta;
revalidate();
repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Zoom test");
// Frame settings
frame.setVisible(true);
frame.setPreferredSize(new Dimension(800, 600));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane pane = new JScrollPane(new ZoomPane(50, 50, 32));
frame.add(pane);
frame.pack();
}
});
}
#Override
public Dimension getPreferredScrollableViewportSize() {
repaint();
return null;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
repaint();
return 0;
}
#Override
public boolean getScrollableTracksViewportHeight() {
repaint();
return false;
}
#Override
public boolean getScrollableTracksViewportWidth() {
repaint();
return false;
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
repaint();
return 0;
}
}
If you try to run the program you will see these problems:
When you move the JScrollBars it seams that the mouse position gets an offset when you try to place a color.
If you zoom/scroll the mouse whell everything shifts up/down and left/right. I would like to zoom at the mouse cursor/point or atleast not shift everything sideways.
This is what works at the moment:
Placing of colors works correctly now.
Moving verticaly or horizontaly doesnt tear anymore.
As you see most of the requirements I mentioned at the top can be found in articles here on SO and most of them are really good and well described but I don't know/ can't implement them so that I can solve the problems above.
My goal with this zoom would be something like the example in here with the exception of drawing my "Colors/tiles" instead of the rectangles he draws and that I am using AffineTransform. Otherwise that example is really what I am aiming for.
It doesn't have to use any of the code he uses. I would preffer to use AffineTransform. If any of you knows how to implement what I am looking for in another way I am all ears. I thought that the example might make it easier for you guys to understand what I am aiming for.
I accept any changes or comments about the code or the implementaion and I will respond as quickly as I can and change the post.
Many thanks,
Towni0
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:
I have two classes. The first, JPanelImage, adds an Image to my JPanel. The second, myObjet, represents the object I want to add on my Image. The Image can move and can zoom.
The problem is that when I move the image, the object remains fixed.
Class JImagePanel:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Panel;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
class JImagePanel extends Panel{
private static final long serialVersionUID = 5375994938523354306L;
private MediaTracker tracker;
private Image img;
private Dimension imgSize,iniSize;
private int zoom = 0 ;
private int MouseX;
private int MouseY;
int transX=0;
int transY=0;
public JImagePanel(String file){
//setSize(100,200);
img=Toolkit.getDefaultToolkit().getImage(file);
setLayout(null);
tracker=new MediaTracker(this);
tracker.addImage(img,0);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
MouseX = e.getX();
MouseY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
transX += e.getX()-MouseX;
transY += e.getY()-MouseY;
MouseX = e.getX();
MouseY = e.getY();
repaint();
}
});
try{
tracker.waitForAll();
}
catch(Exception ie){}
imgSize=iniSize=new Dimension(img.getWidth(this),img.getHeight(this));
}
public Dimension getPreferredSize(){
return new Dimension(imgSize);
}
public void paint(Graphics g){
super.paint(g);
if(imgSize.width<=iniSize.width) {
imgSize=iniSize;
}
g.drawImage(this.img, (getWidth()-imgSize.width)/2+transX, (getHeight()-imgSize.height)/2+transY, imgSize.width,imgSize.height,this);
}
public void zoomIn(){
int x=10*imgSize.width/100;
int y=10*imgSize.height/100;
imgSize=new Dimension(imgSize.width+x,imgSize.height+y);
if(imgSize.width>iniSize.width){
setSize(imgSize);
getParent().doLayout();
}
repaint();
}
public void zoomOut(){
int x=10*imgSize.width/100;
int y=10*imgSize.height/100;
imgSize=new Dimension(imgSize.width-x,imgSize.height-y);
if(getWidth()>iniSize.width)
{
setSize(imgSize);
getParent().doLayout();
}
repaint();
}
public int getZoom() {
return zoom;
}
Class myObjet:
import java.awt.BorderLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class myObjet extends JPanel {
JLabel lblName,lblAct,lblSeuil ;
JPanel panelObjet;
public myObjet(String NameObjet ,double activite )
{
ImageIcon img = createImageIcon("images/Source.png");
lblName = new JLabel(img);
lblAct = new JLabel(String.valueOf(activite));
panelObjet = new JPanel();
panelObjet.setToolTipText(NameObjet);
panelObjet.setLayout(new BorderLayout());
panelObjet.add("North",lblName);
panelObjet.add("South",lblAct);
add(panelObjet);
isOpaque();
}
public ImageIcon createImageIcon(String path) {
java.net.URL imgURL = getClass().getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
JOptionPane.showMessageDialog(null, "Cette image n'existe pas : " + path, "Erreur", JOptionPane.ERROR_MESSAGE);
// System.err.println("L'image n'est pas dans : " + path);
return null;
}
}
public boolean isOpaque()
{
return true ;
}
}
MYObject is alone , i adds this Object in Panel of this Image .
Here is a concrete example of how I use these classes
public static void (String [] args )
{
imagePanel = new JImagePanel("/home/Image.png");
p = new JPanel();
p.setLayout(new FlowLayout());
// p.setBounds(0,0,0,0);
p.add(getImagePanel());
ple2.add("Center",p);
}
2/ in actionPerformed :
public void actionPerformed(ActionEvent ev) {
Object sourceEv = ev.getSource() ;
if(sourceEv == action.jpfI.btnFrame[4])
{
df = new DecimalFormat("0.00");
int x = Integer.valueOf(action.jpfI.lblTxt[4].getText());
int y =Integer.valueOf(action.jpfI.lblTxt[5].getText()) ;
x =(int)(x/0.26) ;
y =(int)(y/0.26):
objet = new myObjet("islem","0.002");
objet.setBounds(x,y , 50,50);
action.getImagePanel().addImage(objet);
action.repaint();
}
Okay, I've come across three issues.
Firstly: There's no layout manager on the image pane. No big deal, but if you're not going to use a layout manager, you become responsible for laying out any child components. I fixed this (and it's in the wrong place) by adding the following to the "myObjet" class.
Dimension size = getPreferredSize();
setBounds(0, 0, size.width, size.height);
This really should be taken care of by the JImagePanel - either add a layout manager or check the doLayout method.
Secondly: The JImagePanel is a heavy weight component. You should avoid mixing heavy and light weight components if you can (there are Z order issues amongst other things). I updated the JImagePanel to extend from a JPanel.
Thridly: You should only very rarely have to override the paint method. In your case I can understand why you did, but what you ended up doing was painting on the top of everything else (and mixed with the fact you were using a heavy weight component compounded the issue).
I changed the "paint" for "paintComponent" which paints the background and was able to get it to work nicely. I was able to move the image around and have the "myObjet" visible and static in place.
UPDATE
Okay...
public void mouseDragged(MouseEvent e) {
transX += e.getX() - MouseX;
transY += e.getY() - MouseY;
MouseX = e.getX();
MouseY = e.getY();
// Add this to your code
for (Component comp : getComponents()) {
comp.setLocation(transX, transY);
}
repaint();
}
In fact, a better solution would be to allow the parent container to handle the movement and set the image and objects statically with in the image pane (my pane was set to a static size). The basic idea you have running here just needs to be moved to the container.
The only other thing you would need to deal with is the Z order of the panes.