I'm trying to code a zoom-able image in a JScrollPane.
When the image is fully zoomed out it should be centered horizontally and vertically. When both scroll bars have appeared the zooming should always happen relative to the mouse coordinate, i.e. the same point of the image should be under the mouse before and after the zoom event.
I have almost achieves my goal. Unfortunately the "scrollPane.getViewport().setViewPosition()" method sometimes fails to update the view position correctly. Calling the method twice (hack!) overcomes the issue in most cases, but the view still flickers.
I have no explanation as to why this is happening. However I'm confident that it's not a math problem.
Below is a MWE. To see what my problem is in particular you can do the following:
Zoom in until you have some scroll bars (200% zoom or so)
Scroll into the bottom right corner by clicking the scroll bars
Place the mouse in the corner and zoom in twice. The second time you'll see how the scroll position jumps towards the center.
I would really appreciate if someone could tell me where the problem lies. Thank you!
package com.vitco;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* Zoom-able scroll panel test case
*/
public class ZoomScrollPanel {
// the size of our image
private final static int IMAGE_SIZE = 600;
// create an image to display
private BufferedImage getImage() {
BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
// draw the small pixel first
Random rand = new Random();
for (int x = 0; x < IMAGE_SIZE; x += 10) {
for (int y = 0; y < IMAGE_SIZE; y += 10) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255)));
g.fillRect(x, y, 10, 10);
}
}
// draw the larger transparent pixel second
for (int x = 0; x < IMAGE_SIZE; x += 100) {
for (int y = 0; y < IMAGE_SIZE; y += 100) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), 180));
g.fillRect(x, y, 100, 100);
}
}
return image;
}
// the image panel that resizes according to zoom level
private class ImagePanel extends JPanel {
private final BufferedImage image = getImage();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.scale(scale, scale);
g2.drawImage(image, 0, 0, null);
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension((int)Math.round(IMAGE_SIZE * scale), (int)Math.round(IMAGE_SIZE * scale));
}
}
// the current zoom level (100 means the image is shown in original size)
private double zoom = 100;
// the current scale (scale = zoom/100)
private double scale = 1;
// the last seen scale
private double lastScale = 1;
public void alignViewPort(Point mousePosition) {
// if the scale didn't change there is nothing we should do
if (scale != lastScale) {
// compute the factor by that the image zoom has changed
double scaleChange = scale / lastScale;
// compute the scaled mouse position
Point scaledMousePosition = new Point(
(int)Math.round(mousePosition.x * scaleChange),
(int)Math.round(mousePosition.y * scaleChange)
);
// retrieve the current viewport position
Point viewportPosition = scrollPane.getViewport().getViewPosition();
// compute the new viewport position
Point newViewportPosition = new Point(
viewportPosition.x + scaledMousePosition.x - mousePosition.x,
viewportPosition.y + scaledMousePosition.y - mousePosition.y
);
// update the viewport position
// IMPORTANT: This call doesn't always update the viewport position. If the call is made twice
// it works correctly. However the screen still "flickers".
scrollPane.getViewport().setViewPosition(newViewportPosition);
// debug
if (!newViewportPosition.equals(scrollPane.getViewport().getViewPosition())) {
System.out.println("Error: " + newViewportPosition + " != " + scrollPane.getViewport().getViewPosition());
}
// remember the last scale
lastScale = scale;
}
}
// reference to the scroll pane container
private final JScrollPane scrollPane;
// constructor
public ZoomScrollPanel() {
// initialize the frame
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(600, 600);
// initialize the components
final ImagePanel imagePanel = new ImagePanel();
final JPanel centerPanel = new JPanel();
centerPanel.setLayout(new GridBagLayout());
centerPanel.add(imagePanel);
scrollPane = new JScrollPane(centerPanel);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
frame.add(scrollPane);
// add mouse wheel listener
imagePanel.addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
super.mouseWheelMoved(e);
// check the rotation of the mousewheel
int rotation = e.getWheelRotation();
boolean zoomed = false;
if (rotation > 0) {
// only zoom out until no scrollbars are visible
if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() ||
scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) {
zoom = zoom / 1.3;
zoomed = true;
}
} else {
// zoom in until maximum zoom size is reached
double newCurrentZoom = zoom * 1.3;
if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom
zoom = newCurrentZoom;
zoomed = true;
}
}
// check if a zoom happened
if (zoomed) {
// compute the scale
scale = (float) (zoom / 100f);
// align our viewport
alignViewPort(e.getPoint());
// invalidate and repaint to update components
imagePanel.revalidate();
scrollPane.repaint();
}
}
});
// display our frame
frame.setVisible(true);
}
// the main method
public static void main(String[] args) {
new ZoomScrollPanel();
}
}
Note: I have also looked at the question here JScrollPane setViewPosition After "Zoom" but unfortunately the problem and solution are slightly different and do not apply.
Edit
I have solved the issue by using a hack, however I'm still no closer to understanding as to what the underlying problem is. What is happening is that when the setViewPosition is called some internal state changes trigger additional calls to setViewPosition. These additional calls only happen occasionally. When I'm blocking them everything works perfectly.
To fix the problem I simply introduced a new boolean variable "blocked = false;" and replaced the lines
scrollPane = new JScrollPane(centerPanel);
and
scrollPane.getViewport().setViewPosition(newViewportPosition);
with
scrollPane = new JScrollPane();
scrollPane.setViewport(new JViewport() {
private boolean inCall = false;
#Override
public void setViewPosition(Point pos) {
if (!inCall || !blocked) {
inCall = true;
super.setViewPosition(pos);
inCall = false;
}
}
});
scrollPane.getViewport().add(centerPanel);
and
blocked = true;
scrollPane.getViewport().setViewPosition(newViewportPosition);
blocked = false;
I would still really appreciate if someone could make sense of this!
Why does this hack work? Is there a cleaner way to achieve the same functionality?
Here is the completed, fully functional Code. I still don't understand why the hack is necessary, but at least it now works as expected:
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* Zoom-able scroll panel
*/
public class ZoomScrollPanel {
// the size of our image
private final static int IMAGE_SIZE = 600;
// create an image to display
private BufferedImage getImage() {
BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
// draw the small pixel first
Random rand = new Random();
for (int x = 0; x < IMAGE_SIZE; x += 10) {
for (int y = 0; y < IMAGE_SIZE; y += 10) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255)));
g.fillRect(x, y, 10, 10);
}
}
// draw the larger transparent pixel second
for (int x = 0; x < IMAGE_SIZE; x += 100) {
for (int y = 0; y < IMAGE_SIZE; y += 100) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), 180));
g.fillRect(x, y, 100, 100);
}
}
return image;
}
// the image panel that resizes according to zoom level
private class ImagePanel extends JPanel {
private final BufferedImage image = getImage();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.scale(scale, scale);
g2.drawImage(image, 0, 0, null);
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension((int)Math.round(IMAGE_SIZE * scale), (int)Math.round(IMAGE_SIZE * scale));
}
}
// the current zoom level (100 means the image is shown in original size)
private double zoom = 100;
// the current scale (scale = zoom/100)
private double scale = 1;
// the last seen scale
private double lastScale = 1;
// true if currently executing setViewPosition
private boolean blocked = false;
public void alignViewPort(Point mousePosition) {
// if the scale didn't change there is nothing we should do
if (scale != lastScale) {
// compute the factor by that the image zoom has changed
double scaleChange = scale / lastScale;
// compute the scaled mouse position
Point scaledMousePosition = new Point(
(int)Math.round(mousePosition.x * scaleChange),
(int)Math.round(mousePosition.y * scaleChange)
);
// retrieve the current viewport position
Point viewportPosition = scrollPane.getViewport().getViewPosition();
// compute the new viewport position
Point newViewportPosition = new Point(
viewportPosition.x + scaledMousePosition.x - mousePosition.x,
viewportPosition.y + scaledMousePosition.y - mousePosition.y
);
// update the viewport position
blocked = true;
scrollPane.getViewport().setViewPosition(newViewportPosition);
blocked = false;
// remember the last scale
lastScale = scale;
}
}
// reference to the scroll pane container
private final JScrollPane scrollPane;
// constructor
public ZoomScrollPanel() {
// initialize the frame
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(600, 600);
// initialize the components
final ImagePanel imagePanel = new ImagePanel();
final JPanel centerPanel = new JPanel();
centerPanel.setLayout(new GridBagLayout());
centerPanel.add(imagePanel);
scrollPane = new JScrollPane();
scrollPane.setViewport(new JViewport() {
private boolean inCall = false;
#Override
public void setViewPosition(Point pos) {
if (!inCall || !blocked) {
inCall = true;
super.setViewPosition(pos);
inCall = false;
}
}
});
scrollPane.getViewport().add(centerPanel);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
frame.add(scrollPane);
// add mouse wheel listener
imagePanel.addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
super.mouseWheelMoved(e);
// check the rotation of the mousewheel
int rotation = e.getWheelRotation();
boolean zoomed = false;
if (rotation > 0) {
// only zoom out until no scrollbars are visible
if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() ||
scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) {
zoom = zoom / 1.3;
zoomed = true;
}
} else {
// zoom in until maximum zoom size is reached
double newCurrentZoom = zoom * 1.3;
if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom
zoom = newCurrentZoom;
zoomed = true;
}
}
// check if a zoom happened
if (zoomed) {
// compute the scale
scale = (float) (zoom / 100f);
// align our viewport
alignViewPort(e.getPoint());
// invalidate and repaint to update components
imagePanel.revalidate();
scrollPane.repaint();
}
}
});
// display our frame
frame.setVisible(true);
}
// the main method
public static void main(String[] args) {
new ZoomScrollPanel();
}
}
Some time ago I was facing the same issue. I had some scalable/zoomable content (SWT widgets) stored in Viewport in JScrollPane and some features implemented to enable panning and zooming the content. I didn't look into your code if it's basically the same, but the issue that I was observing was completely the same. When zooming outside from the right/bottom side, sometimes, the view position jumped a little bit into the center (from my point-of-view that definitely points to a scale factor). Using doubled "setViewPosition" somehow enhanced the behavior but still not usable.
After some investigation, I've found out that the issue on my side was between the moment when I changed the scale factor of the content inside the scroll panel and the moment when view position was set in scroll panel. The thing is that scroll panel doesn't know about the content size updates until layout is done. So basically, it's updating the position based on old content size, extent size and view position.
So, at my side, this helped a lot.
// updating scroll panel content scale goes here
viewport.doLayout();
// setting view position in viewport goes here
Checking method BasicScrollPaneUI#syncScrollPaneWithViewport() was very useful on my side.
very useful example, excellent zoom at mouse pointer, here is the same code slightly modified to include mouse panning:
original code added taken from --> Scroll JScrollPane by dragging mouse (Java swing)
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* Zoom-able scroll panel
*/
// https://stackoverflow.com/questions/22649636/zoomable-jscrollpane-setviewposition-fails-to-update
public class ZoomPanScrollPanel {
// the size of our image
private final static int IMAGE_SIZE = 1600;
// create an image to display
private BufferedImage getImage() {
BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
// draw the small pixel first
Random rand = new Random();
for (int x = 0; x < IMAGE_SIZE; x += 10) {
for (int y = 0; y < IMAGE_SIZE; y += 10) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), rand.nextInt(255)));
g.fillRect(x, y, 10, 10);
}
}
// draw the larger transparent pixel second
for (int x = 0; x < IMAGE_SIZE; x += 100) {
for (int y = 0; y < IMAGE_SIZE; y += 100) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), 180));
g.fillRect(x, y, 100, 100);
}
}
return image;
}
// the image panel that resizes according to zoom level
private class ImagePanel extends JPanel {
private final BufferedImage image = getImage();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.scale(scale, scale);
g2.drawImage(image, 0, 0, null);
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension((int)Math.round(IMAGE_SIZE * scale), (int)Math.round(IMAGE_SIZE * scale));
}
}
// the current zoom level (100 means the image is shown in original size)
private double zoom = 100;
// the current scale (scale = zoom/100)
private double scale = 1;
// the last seen scale
private double lastScale = 1;
// true if currently executing setViewPosition
private boolean blocked = false;
public void alignViewPort(Point mousePosition) {
// if the scale didn't change there is nothing we should do
if (scale != lastScale) {
// compute the factor by that the image zoom has changed
double scaleChange = scale / lastScale;
// compute the scaled mouse position
Point scaledMousePosition = new Point(
(int)Math.round(mousePosition.x * scaleChange),
(int)Math.round(mousePosition.y * scaleChange)
);
// retrieve the current viewport position
Point viewportPosition = scrollPane.getViewport().getViewPosition();
// compute the new viewport position
Point newViewportPosition = new Point(
viewportPosition.x + scaledMousePosition.x - mousePosition.x,
viewportPosition.y + scaledMousePosition.y - mousePosition.y
);
// update the viewport position
blocked = true;
scrollPane.getViewport().setViewPosition(newViewportPosition);
blocked = false;
// remember the last scale
lastScale = scale;
}
}
// reference to the scroll pane container
private final JScrollPane scrollPane;
// constructor
public ZoomPanScrollPanel() {
// initialize the frame
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(600, 600);
// initialize the components
final ImagePanel imagePanel = new ImagePanel();
final JPanel centerPanel = new JPanel();
centerPanel.setLayout(new GridBagLayout());
centerPanel.add(imagePanel);
scrollPane = new JScrollPane();
scrollPane.setViewport(new JViewport() {
private boolean inCall = false;
#Override
public void setViewPosition(Point pos) {
if (!inCall || !blocked) {
inCall = true;
super.setViewPosition(pos);
inCall = false;
}
}
});
scrollPane.getViewport().add(centerPanel);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
frame.add(scrollPane);
// add mouse wheel listener
imagePanel.addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
super.mouseWheelMoved(e);
// check the rotation of the mousewheel
int rotation = e.getWheelRotation();
boolean zoomed = false;
if (rotation > 0) {
// only zoom out until no scrollbars are visible
if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() ||
scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) {
zoom = zoom / 1.3;
zoomed = true;
}
} else {
// zoom in until maximum zoom size is reached
double newCurrentZoom = zoom * 1.3;
if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom
zoom = newCurrentZoom;
zoomed = true;
}
}
// check if a zoom happened
if (zoomed) {
// compute the scale
scale = (float) (zoom / 100f);
// align our viewport
alignViewPort(e.getPoint());
// invalidate and repaint to update components
imagePanel.revalidate();
scrollPane.repaint();
}
}
});
//mouse panning
//original code: https://stackoverflow.com/questions/31171502/scroll-jscrollpane-by-dragging-mouse-java-swing
MouseAdapter ma = new MouseAdapter() {
private Point origin;
#Override
public void mousePressed(MouseEvent e) {
origin = new Point(e.getPoint());
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseDragged(MouseEvent e) {
if (origin != null) {
JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, imagePanel);
if (viewPort != null) {
int deltaX = origin.x - e.getX();
int deltaY = origin.y - e.getY();
System.out.println("X pan = "+ deltaX);
System.out.println("Y pan = "+ deltaY);
Rectangle view = viewPort.getViewRect();
view.x += deltaX;
view.y += deltaY;
imagePanel.scrollRectToVisible(view);
}
}
}
};
imagePanel.addMouseListener(ma);
imagePanel.addMouseMotionListener(ma);
imagePanel.setAutoscrolls(true);
// display our frame
frame.setVisible(true);
}
// the main method
public static void main(String[] args) {
new ZoomPanScrollPanel();
}
}
Related
I'm trying to draw a rotated shape at a given point. To give an example, in the following image, the red rectangle is a non-rotated rectangle drawn at a point and then the blue rectangle is rotated and drawn at the same position. The blue rectangle is the outcome I'm aiming for.
I've been experimenting and trying different methods. Currently, here is what I used for the image:
Point point = new Point(300, 300);
Dimension dim = new Dimension(200, 100);
double radians = Math.toRadians(30);
g.setColor(new java.awt.Color(1f, 0f, 0f, .5f));
g.fillRect(point.x, point.y, dim.width, dim.height);
translate(g, dim, radians);
g.rotate(radians, point.getX(), point.getY());
g.setColor(new java.awt.Color(0f, 0f, 1f, .5f));
g.fillRect(point.x, point.y, dim.width, dim.height);
private static void translate(Graphics2D g, Dimension dim, double radians) {
if (radians > Math.toRadians(360)) {
radians %= Math.toRadians(360);
}
int xOffsetX = 0;
int xOffsetY = 0;
int yOffsetX = 0;
int yOffsetY = 0;
if (radians > 0 && radians <= Math.toRadians(90)) {
xOffsetY -= dim.getHeight();
} else if (radians > Math.toRadians(90) && radians <= Math.toRadians(180)) {
xOffsetX -= dim.getWidth();
xOffsetY -= dim.getHeight();
yOffsetY -= dim.getHeight();
} else if (radians > Math.toRadians(180) && radians <= Math.toRadians(270)) {
xOffsetX -= dim.getWidth();
yOffsetX -= dim.getWidth();
yOffsetY -= dim.getHeight();
} else {
yOffsetX -= dim.getWidth();
}
int x = rotateX(xOffsetX, xOffsetY, radians);
int y = rotateY(yOffsetX, yOffsetY, radians);
g.translate(x, y);
}
private static int rotateX(int x, int y, double radians) {
if (x == 0 && y == 0) {
return 0;
}
return (int) Math.round(x * Math.cos(radians) - y * Math.sin(radians));
}
private static int rotateY(int x, int y, double radians) {
if (x == 0 && y == 0) {
return 0;
}
return (int) Math.round(x * Math.sin(radians) + y * Math.cos(radians));
}
This works for rectangles but doesn't work for other types of shapes. I'm trying to figure out if there is a way to accomplish this for every type of shape. Also note that the code is just for testing purposes and there are a lot of bad practices in it, like calling Math.toRadians so much.
Something like this?
It can be achieved using a rotate transform first, then using the bounds of the rotated shape as a basis, the translate transform can be used to shift it back to meet the top most y and leftmost x values of the original rectangle.
See the getImage() method for one implementation of that.
int a = angleModel.getNumber().intValue();
AffineTransform rotateTransform = AffineTransform.getRotateInstance((a*2*Math.PI)/360d);
// rotate the original shape with no regard to the final bounds
Shape rotatedShape = rotateTransform.createTransformedShape(rectangle);
// get the bounds of the rotated shape
Rectangle2D rotatedRect = rotatedShape.getBounds2D();
// calculate the x,y offset needed to shift it to top/left bounds of original rectangle
double xOff = rectangle.getX()-rotatedRect.getX();
double yOff = rectangle.getY()-rotatedRect.getY();
AffineTransform translateTransform = AffineTransform.getTranslateInstance(xOff, yOff);
// shift the new shape to the top left of original rectangle
Shape rotateAndTranslateShape = translateTransform.createTransformedShape(rotatedShape);
Here is the complete source code:
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.EmptyBorder;
public class TransformedShape {
private JComponent ui = null;
JLabel output = new JLabel();
JToolBar tools = new JToolBar("Tools");
ChangeListener changeListener = (ChangeEvent e) -> {
refresh();
};
int pad = 5;
Rectangle2D.Double rectangle = new Rectangle2D.Double(pad,pad,200,100);
SpinnerNumberModel angleModel = new SpinnerNumberModel(30, 0, 90, 1);
public TransformedShape() {
initUI();
}
private BufferedImage getImage() {
int a = angleModel.getNumber().intValue();
AffineTransform rotateTransform = AffineTransform.getRotateInstance((a*2*Math.PI)/360d);
Shape rotatedShape = rotateTransform.createTransformedShape(rectangle);
Rectangle2D rotatedRect = rotatedShape.getBounds2D();
double xOff = rectangle.getX()-rotatedRect.getX();
double yOff = rectangle.getY()-rotatedRect.getY();
AffineTransform translateTransform = AffineTransform.getTranslateInstance(xOff, yOff);
Shape rotateAndTranslateShape = translateTransform.createTransformedShape(rotatedShape);
Area combinedShape = new Area(rotateAndTranslateShape);
combinedShape.add(new Area(rectangle));
Rectangle2D r = combinedShape.getBounds2D();
BufferedImage bi = new BufferedImage((int)(r.getWidth()+(2*pad)), (int)(r.getHeight()+(2*pad)), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(new Color(255,0,0,127));
g.fill(rectangle);
g.setColor(new Color(0,0,255,127));
g.fill(rotateAndTranslateShape);
g.dispose();
return bi;
}
private void addModelToToolbar(String label, SpinnerNumberModel model) {
tools.add(new JLabel(label));
JSpinner spinner = new JSpinner(model);
spinner.addChangeListener(changeListener);
tools.add(spinner);
}
public final void initUI() {
if (ui!=null) return;
ui = new JPanel(new BorderLayout(4,4));
ui.setBorder(new EmptyBorder(4,4,4,4));
ui.add(output);
ui.add(tools,BorderLayout.PAGE_START);
addModelToToolbar("Angle", angleModel);
refresh();
}
private void refresh() {
output.setIcon(new ImageIcon(getImage()));
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
ex.printStackTrace();
}
TransformedShape o = new TransformedShape();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}
You have a shape, any shape.
You have a point (px,py) and you want to rotate the shape around this point and angle ag measured counter-clokwise.
For each point of the shape the proccess has three steps:
Translate to (px,py)
Rotate
Translate back to (0,0)
The translation is fully simple
xNew = xOld - px
yNew = yOld - py
The rotation is a bit less simple
xRot = xNew * cos(ag) - yNew * sin(ag)
yRot = xNew * sin(ag) + yNew * cos(ag)
Finally the translation back:
xDef = xRot + px
yDef = yRot + py
A bit of explanation: Any transformation can be seen in two ways: 1) I move the shape 2) I move the axis-system. If you think about it, you'll find that the trasnsformation is relative: seen from the axis point of view or seen from the shape point of view.
So, you can say "I want coordinates in the translated system", or you can also say "I want the coordinates of the translated shape".
It doesn't matter what point of view you chose, the equations are the same.
I'm explaining this so much, just to achieve you realize which is the positive direction of the angle: clockwise or counter-clockwise.
I'm trying to make a level editor for my platformer game, I want my levels to be 100 by 100 squares.
So far the editor works, but I can't scroll through the JPanel. I've been playing around and I've made a small test class to fiddle with which I'll post. If you run it, all it does it show the grid. However if I swap out two variables (I'll comment where) it can show an image and scroll according to the size of that image.
I want that scrolling ability only for the JPanel, so that I can scroll through my 100 x 100 square level.
import java.awt.BorderLayout;
public class ScrollPaneJ extends JFrame {
// setting the panels
private JPanel contentPane;
private JScrollPane scrollPane;
// dimensions/ variables of the grid
int size = 16;
int startX = 112;
int startY = 48;
int width = 30;
int height = 30;
// this is the grid
String[][] grid = new String[width][height];
// this is from the full editor class
String currentImage = new String("platform");
ImageIcon currentBackIcon = new ImageIcon("Resources/backdirttile.jpg");
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
// adding the scrollpane
ScrollPaneJ frame = new ScrollPaneJ();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public ScrollPaneJ() {
setTitle("Scrolling Pane Application");
setSize(new Dimension(300, 200));
setBackground(Color.gray);
setDefaultCloseOperation(EXIT_ON_CLOSE);
// defining the top and bottom panels, bottom is what I think I'm
// drawing on, top is where the scrollpanel goes, I copied this code
// from the internet and I'm not too sure how it works
JPanel topPanel = new JPanel();
JPanel bottomPanel = new JPanel(new GridLayout());
bottomPanel.setLayout(new BorderLayout());
getContentPane().add(bottomPanel);
topPanel.setLayout(new BorderLayout());
getContentPane().add(topPanel);
// this is the label I was talking about
Icon image = new ImageIcon("src/MenuDesign.jpg");
JLabel label = new JLabel(image);
// Create a tabbed pane
// if you set it to say label instead of bottomPanel, you can scroll
// through the size of the label
scrollPane = new JScrollPane(bottomPanel);
scrollPane.setBounds(40, 40, 100, 100);
// set it label here as well.
scrollPane.getViewport().add(bottomPanel);
// I was hoping this would force the scrollbar in but it does nothing
scrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane
.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setBounds(50, 30, 300, 50);
JPanel contentPane = new JPanel(null);
contentPane.setPreferredSize(new Dimension(500, 400));
contentPane.add(scrollPane);
topPanel.add(scrollPane, BorderLayout.CENTER);
init();
}
public void init() {
// this sets the grid to empty
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
grid[x][y] = "";
}
}
}
#Override
public void paint(Graphics g) {
// this paints the grid
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.black);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
g2d.drawRect(x * size + startX, y * size + startY, size, size);
if (grid[x][y].equals("")) {
g2d.drawImage(currentBackIcon.getImage(),
x * size + startX, y * size + startY, null);
}
g2d.setColor(Color.black);
g2d.drawRect((x * size) + 1 + startX, (y * size) + 1 + startY,
size, size);
}
}
}
public void drawTile() {
// this isn't enabled which is why you can't paint the grid, however it
// would change the tile of the square you're mouse is on, to the
// current tile, it works and isn't really important for what i need
// help with
PointerInfo a = MouseInfo.getPointerInfo();
Point b = a.getLocation();
int mouseX = (int) b.getX();
int mouseY = (int) b.getY();
int gMX = ((mouseX - 48) / 16) - 4;
int gMY = ((mouseY - 48) / 16) - 3;
grid[gMX][gMY] = currentImage;
repaint();
}
}
scrollPane.getViewport().add(bottomPanel); should be more like scrollPane.getViewportView(bottomPanel);
You shouldn't be painting directly to the frame, as child components can be painted without the notification to the parents, meaning that what ever you've painted could be partially wiped out. Instead, this kind of painting should be done within a custom component which acts as the JScrollPane's, JViewport's view.
A JScrollPane needs two things, first, the size that the component would like to be (the preferredSize) and the size of the viewport view. If the component doesn't implement the Scrollable interface, then the component's preferredSize is used to determine that as well. This is why a JLabel will work.
A JScrollPane has a JViewport as it's primary child component. The JViewport should only have a single component, typically assigned either via JScrollPane#setViewportView or JViewport#setView methods
See How to Use Scroll Panes for more details
Create a custom component that extends JPanel and override it's getPreferredSize method to return the size of the component you want. Override it's paintComponent method and perform you custom painting their.
Overlaying custom painting ontop of other components is more difficult
You can also add JScrollPane in your panel like this
JPanel p = new JPanel();
add(new JScrollPane(p));
I am trying to make a program that generates 25 random ovals then draw a ball and make it bounce, I got it somewhat done, I generated the ovals and I got the ball to move but when I added the thread it kept repeating the draw oval loop, I get somewhat why this is happening but I have no idea how to fix it.
Basically my program should:
draw 25 random sized ovals on random locations within the border - completed
draw a ball and make it move - completed
make the ball bounce - not done but I know how to do it
but it keeps repeating step one.
this is my code that I have written, oh and I have to use applets right now its part of the course please don't suggest I use swing or something else:
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
public class As4B extends Applet implements Runnable
{
public int x, y;
public int width = 854;
public int height = 480;
public int border = 20;
public Image offscreen;
public Graphics d;
public void init()
{
setSize(width,height);
Thread th = new Thread(this);
th.start();
offscreen = createImage(width,height);
d = offscreen.getGraphics();
}
public void run()
{
x = 100;
y = 100;
while(true)
{
x ++;
y ++;
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void paint(Graphics gfx)
{
d.setColor(java.awt.Color.black);
d.fillRect(0, 0, width, height);
d.setColor(java.awt.Color.green);
d.fillRect(0 + border, 0 + border, (width - (border * 2)), (height - (border* 2)));
genOval(25, d);
d.setColor(Color.gray);
d.fillOval(x, y, 50, 50);
gfx.drawImage(offscreen, 0, 0, this);
}
public int random(int low, int high)
{
int answer =(int)((Math.random()*(high-low))+ low);
return answer;
}
public void genOval(int amount, Graphics f)
{
int ranWidth, ranHeight, ranX, ranY, red, blue, green;
int i = 0;
while(i < 25)
{
green = random(0,255);
blue = random(0,255);
red = random(0,255);
f.setColor(new Color(red,green,blue));
ranWidth = random(30,400);
ranHeight = random(30,200);
ranX = random(0 + border, ((width - border)- (ranWidth)));
ranY = random(0 + border , ((height - border)- (ranHeight)));
f.fillOval(ranX, ranY, ranWidth, ranHeight);
i++;
}
}
public void update(Graphics gfx) {
paint(gfx);
}
}
Your genOval() method has no persistent backing. Every time repaint() is called (by your thread), the paint() method is called, and this generates new locations for the random ovals. You need to create a persistent source for that information, like so:
List<Rectangle> rectangles = new ArrayList<Rectangle>();
List<Color> colors = new ArrayList<Color>();
public void init() {
...
for (int i = 0; i < 25; i++) {
int green = random(0,255);
int blue = random(0,255);
int red = random(0,255);
colors.add(new Color(red,green,blue));
ranWidth = random(30,400);
ranHeight = random(30,200);
ranX = random(0 + border, ((width - border)- (ranWidth)));
ranY = random(0 + border , ((height - border)- (ranHeight)));
rectangles.add(new Rectangle(ranX, ranY, ranWidth, ranHeight));
}
}
public void genOval(Graphics g) {
for (int i = 0; i < 25; i++) {
Color color = colors.get(i);
Rectangle rectangle = rectangle.get(i);
// Draw using color & rectangle
}
}
i just finish making a Minesweeper game, everything functions perfectly except one thing, the speed of loading the images into he game. I noticed if i have a large number of cells in the game images loads really slow after the mouse click on the cell and it gets faster if i have a smaller number of cells. is there any other way that would make loading images much faster than the one i used? here is the method i used in order to load the images into the game :
private void draw(Graphics g) {
BufferedImage gRec =null, flag=null, mine=null, aCount0=null,
aCount1=null,aCount2 =null,aCount3 =null,aCount4 =null,aCount5 =null,
aCount6 =null,aCount7 =null,aCount8 = null;
try {
gRec = ImageIO.read(new File("/Users/msa_666/Desktop/blank.gif"));
flag = ImageIO.read(new File("/Users/msa_666/Desktop/bombflagged.gif"));
mine = ImageIO.read(new File("/Users/msa_666/Desktop/bombdeath.gif"));
flag = ImageIO.read(new File("/Users/msa_666/Desktop/bombflagged.gif"));
aCount0 = ImageIO.read(new File("/Users/msa_666/Desktop/open0.gif"));
aCount1 = ImageIO.read(new File("/Users/msa_666/Desktop/open1.gif"));
aCount2 = ImageIO.read(new File("/Users/msa_666/Desktop/open2.gif"));
aCount3 = ImageIO.read(new File("/Users/msa_666/Desktop/open3.gif"));
aCount4 = ImageIO.read(new File("/Users/msa_666/Desktop/open4.gif"));
aCount5 = ImageIO.read(new File("/Users/msa_666/Desktop/open5.gif"));
aCount6 = ImageIO.read(new File("/Users/msa_666/Desktop/open6.gif"));
aCount7 = ImageIO.read(new File("/Users/msa_666/Desktop/open7.gif"));
aCount8 = ImageIO.read(new File("/Users/msa_666/Desktop/open8.gif"));
}
catch (IOException e) {
e.printStackTrace();
}
if (getCovered() == true && getMarked () == false) { // gray rectangle
g.drawImage(gRec,getX(),getY(),w,h,null);
}
else if (getCovered()==true && getMarked() == true) { //flag
g.drawImage(flag,getX(),getY(),w,h,null);
}
else if (getCovered()== false && getMined()== true){ //mine
g.drawImage(mine,getX(),getY(),w,h,null);
}
else if ( getCovered() == false && getMined() == false) { // adjacency count image
switch (getAdjCount()){
case 0:
g.drawImage(aCount0,getX(),getY(),w,h,null);
break;
case 1:
g.drawImage(aCount1,getX(),getY(),w,h,null);
break;
case 2:
g.drawImage(aCount2,getX(),getY(),w,h,null);
break;
case 3:
g.drawImage(aCount3,getX(),getY(),w,h,null);
break;
case 4:
g.drawImage(aCount4,getX(),getY(),w,h,null);
break;
case 5:
g.drawImage(aCount5,getX(),getY(),w,h,null);
break;
case 6:
g.drawImage(aCount6,getX(),getY(),w,h,null);
break;
case 7:
g.drawImage(aCount7,getX(),getY(),w,h,null);
break;
case 8:
g.drawImage(aCount8,getX(),getY(),w,h,null);
break;
}
}
}
here is the mouse listener to repaint each cell after clicking on it :
public void mouseClicked(MouseEvent e) {
int sRow, sCol;
sRow= e.getX() / cellHeight;
sCol = e.getY()/ cellWidth;
System.out.println(e.getX() +"," +sCol);
System.out.println(e.getY()+","+sRow);
if (e.getButton() == MouseEvent.BUTTON1) {
if( cells[sRow][sCol].getMarked() == false)
uncoverCell(cells[sRow][sCol]);
// cells[sRow][sCol].setCovered(false);
System.out.println(cells[sRow][sCol].getMined());
repaint();
}
else if (e.getButton() == MouseEvent.BUTTON2) {
}
else if (e.getButton() == MouseEvent.BUTTON3) {
if (cells[sRow][sCol].getMarked() == false){
cells[sRow][sCol].setMarked(true);
repaint();
}
else {
cells[sRow][sCol].setMarked(false);
repaint();
}
}
if (allMinesMarked() && allNonMinesUncovered()){
System.out.println("You Win");
}
}
public void paintComponent(Graphics g) {
for ( int i=0 ; i <rowCount; i++ ) {
for (int j=0; j<columnCount; j++) {
cells[i][j].draw(g);
}
}
}
You need to tell us:
Just where is draw(...) called?
How do you obtain the Graphics object, g, that is passed into the draw method's parameter?
I'm guessing here since we don't have all of the relevant code, but it looks as if you're re-reading in your images each time you want to display one. If so, don't do this. Read the images in only once at the start of the program, and then use the Images or perhaps better, ImageIcons, obtained when you need them.
Edit
Thanks for posting more code, and this in fact confirms my suspicion: you're re-reading in the image files with every repaint of your GUI. This is highly inefficient and will slow your program down to a crawl. Again, you should read the images into your program once and then use them multiple times.
Myself I'd create ImageIcons from the images, and then display them in a JLabel. When there is need to swap images, simply call setIcon(...) on the JLabel. This way there's no need to even mess with paintComponent(...).
Edit 2
For example (compile and run this):
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
public class SwapIcons {
private static final int CELL_SIDE_COUNT = 3;
private ImageCell[] imageCells = new ImageCell[CELL_SIDE_COUNT * CELL_SIDE_COUNT];
private JPanel mainPanel = new JPanel();
public SwapIcons(final GetImages getImages) {
mainPanel.setLayout(new GridLayout(CELL_SIDE_COUNT, CELL_SIDE_COUNT));
mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
for (int i = 0; i < imageCells.length; i++) {
imageCells[i] = new ImageCell(getImages);
mainPanel.add(imageCells[i].getImgLabel());
}
}
public JComponent getMainComponent() {
return mainPanel;
}
private static void createAndShowGui(GetImages getImages) {
SwapIcons swapIcons = new SwapIcons(getImages);
JFrame frame = new JFrame("Click on Icons to Change");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(swapIcons.getMainComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
try {
final GetImages getImages = new GetImages();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui(getImages);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ImageCell {
private JLabel imgLabel = new JLabel();
private GetImages getImages;
private int iconIndex = 0;
public ImageCell(final GetImages getImages) {
this.getImages = getImages;
imgLabel.setIcon(getImages.getIcon(0));
imgLabel.addMouseListener(new MyMouseListener());
}
public JLabel getImgLabel() {
return imgLabel;
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
iconIndex++;
iconIndex %= getImages.getIconListSize();
imgLabel.setIcon(getImages.getIcon(iconIndex));
}
}
}
// Simply gets a SpriteSheet and subdivides it into a List of ImageIcons
class GetImages {
private static final String SPRITE_PATH = "http://th02.deviantart.net/"
+ "fs70/PRE/i/2011/169/0/8/blue_player_sprite_sheet_by_resetado-d3j7zba.png";
public static final int SPRITE_ROWS = 6;
public static final int SPRITE_COLS = 6;
public static final int SPRITE_CELLS = 35;
private List<ImageIcon> iconList = new ArrayList<ImageIcon>();
public GetImages() throws IOException {
URL imgUrl = new URL(SPRITE_PATH);
BufferedImage mainImage = ImageIO.read(imgUrl);
for (int i = 0; i < SPRITE_CELLS; i++) {
int row = i / SPRITE_COLS;
int col = i % SPRITE_COLS;
int x = (int) (((double) mainImage.getWidth() * col) / SPRITE_COLS);
int y = (int) ((double) (mainImage.getHeight() * row) / SPRITE_ROWS);
int w = (int) ((double) mainImage.getWidth() / SPRITE_COLS);
int h = (int) ((double) mainImage.getHeight() / SPRITE_ROWS);
BufferedImage img = mainImage.getSubimage(x, y, w, h);
ImageIcon icon = new ImageIcon(img);
iconList.add(icon);
}
}
// get the Icon from the List at index position
public ImageIcon getIcon(int index) {
if (index < 0 || index >= iconList.size()) {
throw new ArrayIndexOutOfBoundsException(index);
}
return iconList.get(index);
}
public int getIconListSize() {
return iconList.size();
}
}
Hovercraft Full Of Eels answer is good and will work.
And is fine for a standalone app, but for an applet or web start app can further optimize by having one large image and then copying parts of it to the graphics object that is visible, think grids and use function in java.awt.Graphics object (from javadoc):
public abstract boolean drawImage(Image img,
int dx1,
int dy1,
int dx2,
int dy2,
int sx1,
int sy1,
int sx2,
int sy2,
ImageObserver observer)
Draws as much of the specified area of the specified image as is currently available, scaling it on the fly to fit inside the specified area of the destination drawable surface. Transparent pixels do not affect whatever pixels are already there.
This method returns immediately in all cases, even if the image area to be drawn has not yet been scaled, dithered, and converted for the current output device. If the current output representation is not yet complete then drawImage returns false. As more of the image becomes available, the process that loads the image notifies the specified image observer.
This method always uses the unscaled version of the image to render the scaled rectangle and performs the required scaling on the fly. It does not use a cached, scaled version of the image for this operation. Scaling of the image from source to destination is performed such that the first coordinate of the source rectangle is mapped to the first coordinate of the destination rectangle, and the second source coordinate is mapped to the second destination coordinate. The subimage is scaled and flipped as needed to preserve those mappings.
Parameters:
img - the specified image to be drawn. This method does nothing if img is null.
dx1 - the x coordinate of the first corner of the destination rectangle.
dy1 - the y coordinate of the first corner of the destination rectangle.
dx2 - the x coordinate of the second corner of the destination rectangle.
dy2 - the y coordinate of the second corner of the destination rectangle.
sx1 - the x coordinate of the first corner of the source rectangle.
sy1 - the y coordinate of the first corner of the source rectangle.
sx2 - the x coordinate of the second corner of the source rectangle.
sy2 - the y coordinate of the second corner of the source rectangle.
observer - object to be notified as more of the image is scaled and converted.
Returns:
false if the image pixels are still changing; true otherwise.
This is better as it takes a few seconds to make a new connection and download image over internet, so if you have one main image that has all the sub images in a big table then total time to download, load and render will be less. extra logic to copy from an area is trivial maybe .1KB of jar file space :)
So I have a black image that acts as darkness (In my game). I want to show a small portion around the character only. Like so
The red square is the player.
The green bit is the ground in the game (grass).
The black is the shadow/darkness.
What I want to do is cut a Ellipse/hole out of the blank, black image. I want this Ellipse to be centered around the players (The red square) x and y position.
Currently I am using this to get the effect:
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
//draw mask
g.setColor(Color.black);
if(!nearPlayer(x, y)) {
g.drawLine(x, y, x, y);
}
}
}
But, this processes extremely slow and laggs the players movement drastically.
Is this possible?
..the Player is the red square. Basically what i want to do is cut a circle out of the blank, black image around the coordinates of the player relative to said black image.
What DYM by 'black image' - exactly? To me that just looks like the BG is painted black, which would make more sense for any solid color. In that case, just create the red thing using an Area, fill it, then for the border set a stroke & the color to black, and draw it. This example shows how.
The relevant part of that short code is..
public void paintDaisyPart(Graphics2D g, Area daisyArea) {
g.setClip(daisyArea);
g.setColor(Color.YELLOW);
g.fillRect(0, 0, 200, 200);
g.setColor(Color.YELLOW.darker());
g.setClip(null);
g.setStroke(new BasicStroke(3));
g.draw(daisyArea);
}
I must be bored. This is an animated SSCCE version of the code that drew the image above. It is typically showing >130 FPS. And that is on a clunky machine for which I told the guy my spec. was 'cheap' & reminded him twice that I don't play (heavy rendering) games.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class DaisyDisplay {
DaisyDisplay() {
JPanel gui = new JPanel(new BorderLayout(2,2));
final BufferedImage daisy = new BufferedImage(
200,200,BufferedImage.TYPE_INT_RGB);
final JLabel daisyLabel = new JLabel(new ImageIcon(daisy));
gui.add(daisyLabel,BorderLayout.CENTER);
final Daisy daisyPainter = new Daisy();
daisyPainter.setSize(200);
final JLabel fps = new JLabel("FPS: ");
gui.add(fps,BorderLayout.SOUTH);
ActionListener animator = new ActionListener() {
int counter = 0;
long timeLast = 0;
long timeNow = 0;
public void actionPerformed(ActionEvent ae) {
Graphics2D g = daisy.createGraphics();
g.setColor(Color.GREEN.darker());
g.fillRect(0, 0, 200, 200);
daisyPainter.paint(g);
g.dispose();
daisyLabel.repaint();
counter++;
timeNow = System.currentTimeMillis();
if (timeLast<timeNow-1000) {
fps.setText("FPS: " + counter);
counter = 0;
timeLast = timeNow;
}
}
};
Timer timer = new Timer(1,animator);
timer.start();
JOptionPane.showMessageDialog(null, gui);
timer.stop();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DaisyDisplay();
}
});
}
}
class Daisy {
double size = 200;
Point location;
double offset = 0.0;
public void paint(Graphics2D g) {
Area daisyArea = getDaisyShape();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
offset += .02d;
AffineTransform plain = g.getTransform();
g.setTransform(AffineTransform.getRotateInstance(
offset + (Math.PI*1/8),
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
offset + (Math.PI*3/8),
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
offset,
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
offset + (Math.PI*2/8),
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(plain);
}
public void setLocation(Point location) {
this.location = location;
}
public void paintDaisyPart(Graphics2D g, Area daisyArea) {
g.setClip(daisyArea);
g.setColor(Color.YELLOW);
g.fillRect(0, 0, 200, 200);
g.setColor(Color.YELLOW.darker());
g.setClip(null);
g.setStroke(new BasicStroke(3));
g.draw(daisyArea);
}
public void setSize(double size) {
this.size = size;
}
public Area getDaisyShape() {
int diameter = (int)size*6/20;
Ellipse2D.Double core = new Ellipse2D.Double(
(size-diameter)/2,(size-diameter)/2,diameter,diameter);
int pad = 10;
int petalWidth = 50;
int petalLength = 75;
Area area = new Area(core);
// left petal
area.add(new Area(new Ellipse2D.Double(
pad,(size-petalWidth)/2,petalLength,petalWidth)));
// right petal
area.add(new Area(new Ellipse2D.Double(
(size-petalLength-pad),(size-petalWidth)/2,petalLength,petalWidth)));
// top petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,pad,petalWidth,petalLength)));
// bottom petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,(size-petalLength-pad),petalWidth,petalLength)));
return area;
}
}