I have been searching for quite a bit on a way to make static visual representations of Java, but im not certain that what i found to be the most correct way to accomplish what im looking for.
I looking for a way to simply produce an image to represent the state of a board in Java. For example, lets say i have an implementation of Checkers in Java, what im looking for is a way to, after each individual move, represent the game board.
I have looked into JavaFX and Swing, but judging from what i found out they are more suited for actual dynamic GUI's, not image processing( tho i can be wrong).
What options are there really? Or should i use any external apps/software for this?
You could try something like this (a JavaFX solution) which will render images of a checkerboard to png files. Not sure if that is what you are looking for as your question was a bit unclear to me, but perhaps it is helpful for you.
import javafx.application.*;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.*;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
public class Checkout extends Application {
private static final double SQUARE_SIZE = 20;
private static final double PIECE_RADIUS = SQUARE_SIZE / 2.5;
private static final Color WHITE = Color.BEIGE;
private static final Color BLACK = Color.ROSYBROWN;
// setupindex, player, position (1-32 index into top to bottom black squares on the board).
private static final int[][][] setups =
{
{
{31, 27, 19},
{17, 12, 5}
},
{
{30, 26, 18},
{19, 11, 10},
},
{
{31, 24, 19},
{18, 11, 10}
}
};
private static final String SNAPSHOT_FILE_PREFIX = "snapshot-";
private static final String SNAPSHOT_FILE_SUFFIX = ".png";
private WritableImage snapshotFxImage =
new WritableImage((int) (SQUARE_SIZE * 8), (int) (SQUARE_SIZE * 8));
private BufferedImage snapshotBufferedImage =
new BufferedImage((int) (SQUARE_SIZE * 8), (int) (SQUARE_SIZE * 8), BufferedImage.TYPE_INT_ARGB);
private static final String USER_HOME =
System.getProperties().getProperty("user.home");
#Override
public void start(Stage stage) throws IOException {
Group layout = new Group();
renderBoard(layout);
Scene scene = new Scene(layout);
int i = 0;
for (int[][] setup : setups) {
List<Node> pieces = renderSetup(setup);
layout.getChildren().addAll(pieces);
snapshot(scene, i);
layout.getChildren().removeAll(pieces);
i++;
}
Platform.setImplicitExit(false);
Platform.exit();
}
private void renderBoard(Group layout) {
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Rectangle square = new Rectangle(
c * SQUARE_SIZE,
r * SQUARE_SIZE,
SQUARE_SIZE,
SQUARE_SIZE
);
Color fill =
(((c + r) % 2) == 0)
? Color.WHITE
: Color.BLACK;
square.setFill(fill);
layout.getChildren().add(square);
}
}
}
private List<Node> renderSetup(int[][] setup) {
List<Node> pieces = new ArrayList<>();
for (int player = 0; player < 2; player++) {
for (int pieceIndex = 0; pieceIndex < setup[player].length; pieceIndex++) {
Color fill =
player == 0
? WHITE
: BLACK;
Circle piece = new Circle(PIECE_RADIUS, fill);
pieces.add(piece);
movePieceTo(piece, setup[player][pieceIndex]);
}
}
return pieces;
}
private void movePieceTo(Circle circle, int p) {
int r = (p - 1) / 4;
int c = ((p - 1) % 4) * 2 + (((r % 2) == 0) ? 1 : 0);
circle.setCenterX(c * SQUARE_SIZE + SQUARE_SIZE / 2);
circle.setCenterY(r * SQUARE_SIZE + SQUARE_SIZE / 2);
}
private void snapshot(Scene scene, int snapshotIdx) throws IOException {
Path path = Paths.get(
USER_HOME,
SNAPSHOT_FILE_PREFIX + snapshotIdx + SNAPSHOT_FILE_SUFFIX
);
scene.snapshot(snapshotFxImage);
SwingFXUtils.fromFXImage(snapshotFxImage, snapshotBufferedImage);
ImageIO.write(snapshotBufferedImage, "png", path.toFile());
System.out.println("Saved: " + path.toString());
}
public static void main(String[] args) {
launch(args);
}
}
You can use the Graphics class to draw to an in memory image. Then you can save that to a file or display it some way. Heres an example of how you might get started doing that:
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class SO45990731 {
public static void main(String [] args) throws IOException {
BufferedImage image = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
// TODO draw your checker board
g.drawOval(100, 100, 300, 300);
// cleanup and write
g.dispose();
ImageIO.write(image, "png", new File("board.png"));
}
}
Related
I wrote two simple programs, both draw the same Sierpinski Triangle:
One program was implemented using swing, and one using javafx.
There is a very significant performance difference, swing implementation being consistently much faster:
(In this test case : Swing over 1 sec. Javafx over 12 seconds) Is it to be expected or is there something very wrong with my javafx implementation ?
Swing implementation
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SimpleSrpnskTriSw {
private Triangles triPanel;
SimpleSrpnskTriSw(int numberOfLevels){
JFrame frame = new JFrame("Sierpinski Triangles (swing)");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
triPanel = new Triangles();
frame.add(triPanel, BorderLayout.CENTER);
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
triPanel.draw(numberOfLevels);
}
class Triangles extends JPanel{
private static final int PANEL_WIDTH =600, PANEL_HEIGHT = 600;
private static final int TRI_WIDTH= 500, TRI_HEIGHT= 500;
private static final int SIDE_GAP = (PANEL_WIDTH - TRI_WIDTH)/2;
private static final int TOP_GAP = (PANEL_HEIGHT - TRI_HEIGHT)/2;
private int countTriangles;
private long startTime;
boolean working;
private int numberOfLevels = 0;
Triangles() {
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
startTime = System.currentTimeMillis();
countTriangles = 0;
working = true;
draw();
}
void draw(int numLevels) {
numberOfLevels = numLevels;
working = true;
draw();
}
void draw() {
startTime = System.currentTimeMillis();
countTriangles = 0;
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
repaint();
}
});
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(new Font("Ariel", Font.PLAIN, 14));
if(working) {
g.setColor(getBackground());
g.fillRect(0,0,PANEL_WIDTH,PANEL_HEIGHT);
g.setColor(getForeground());
g.drawString("Working.........", 15, 15);
working = false;
return;
}
if(numberOfLevels <= 0 ) {
return;
}
Point top = new Point(PANEL_WIDTH/2, TOP_GAP);
Point left = new Point(SIDE_GAP, TOP_GAP+ TRI_HEIGHT);
Point right = new Point(SIDE_GAP + TRI_WIDTH, TOP_GAP+ TRI_HEIGHT);
BufferedImage bi = getBufferedImage(top, left, right);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(bi,0,0, this);
g.drawString("Number of triangles: "+ countTriangles, 15, 15);
g.drawString("Time : "+ (System.currentTimeMillis()- startTime)+ " mili seconds", 15, 35);
g.drawString("Levels: "+ numberOfLevels, 15, 50);
}
private BufferedImage getBufferedImage(Point top, Point left, Point right) {
BufferedImage bi = new BufferedImage(PANEL_WIDTH,PANEL_HEIGHT,
BufferedImage.TYPE_INT_ARGB);
drawTriangle(bi, numberOfLevels, top, left, right);
return bi;
}
private void drawTriangle(BufferedImage bi, int levels, Point top, Point left, Point right) {
if(levels < 0) {
return ;
}
countTriangles++;
Graphics g = bi.getGraphics();
g.setColor(Color.RED);
Polygon tri = new Polygon();
tri.addPoint(top.x, top.y); //use top,left right rather than fixed points
tri.addPoint(left.x, left.y);
tri.addPoint(right.x, right.y);
g.drawPolygon(tri);
// Get the midpoint on each edge in the triangle
Point p12 = midpoint(top, left);
Point p23 = midpoint(left, right);
Point p31 = midpoint(right, top);
// recurse on 3 triangular areas
drawTriangle(bi, levels - 1, top, p12, p31);
drawTriangle(bi, levels - 1, p12, left, p23);
drawTriangle(bi, levels - 1, p31, p23, right);
}
private Point midpoint(Point p1, Point p2) {
return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
}
public static void main(String[] args) {
new SimpleSrpnskTriSw(13);
}
}
JavaFx implementation
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class SimpleSrpnskTriFx extends Application {
private final int PADDING = 5;
private static int numberOfLevels;
public static void launch(String... args){
numberOfLevels = 8;
if((args != null) && (args.length > 0)) {
try {
int num = Integer.parseInt(args[0]);
numberOfLevels = num ;
} catch (NumberFormatException ex) {
ex.printStackTrace();
return;
}
}
Application.launch(args);
}
#Override
public void start(Stage stage) {
stage.setOnCloseRequest((ae) -> {
Platform.exit();
System.exit(0);
});
stage.setTitle("Sierpinski Triangles (fx)");
BorderPane mainPane = new BorderPane();
mainPane.setPadding(new Insets(PADDING));
Pane triPanel = new Triangles();
BorderPane.setAlignment(triPanel, Pos.CENTER);
mainPane.setCenter(triPanel);
Scene scene = new Scene(mainPane);
stage.setScene(scene);
stage.centerOnScreen();
stage.setResizable(false);
stage.show();
}
class Triangles extends AnchorPane{
private static final int PANEL_WIDTH =600, PANEL_HEIGHT = 600;
private static final int TRI_WIDTH= 500, TRI_HEIGHT= 500;
private static final int SIDE_GAP = (PANEL_WIDTH - TRI_WIDTH)/2;
private static final int TOP_GAP = (PANEL_HEIGHT - TRI_HEIGHT)/2;
private int countTriangles;
private long startTime;
private Point2D top, left, right;
private Canvas canvas;
private Canvas backgroundCanvas;
private GraphicsContext gc;
Triangles(){
setPrefSize(PANEL_WIDTH, PANEL_HEIGHT);
canvas = getCanvas();
backgroundCanvas = getCanvas();
gc = backgroundCanvas.getGraphicsContext2D();
getChildren().add(canvas);
draw(numberOfLevels);
}
void draw(int numberLevels) {
Platform.runLater(new Runnable() {
#Override
public void run() {
canvas.getGraphicsContext2D().fillText("Working....",5,15);
setStartPoints();
startTime = System.currentTimeMillis();
countTriangles = 0;
RunTask task = new RunTask(numberLevels, top, left, right);
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
});
}
private void drawTriangle( int levels, Point2D top, Point2D left, Point2D right) {
if(levels < 0) {//add stop criteria
return ;
}
gc.strokePolygon( //implementing with strokeLine did not make much difference
new double[]{
top.getX(),left.getX(),right.getX()
},
new double[]{
top.getY(),left.getY(), right.getY()
},
3);
countTriangles++;
//Get the midpoint on each edge in the triangle
Point2D p12 = midpoint(top, left);
Point2D p23 = midpoint(left, right);
Point2D p31 = midpoint(right, top);
// recurse on 3 triangular areas
drawTriangle(levels - 1, top, p12, p31);
drawTriangle(levels - 1, p12, left, p23);
drawTriangle(levels - 1, p31, p23, right);
}
private void setStartPoints() {
top = new Point2D(getPrefWidth()/2, TOP_GAP);
left = new Point2D(SIDE_GAP, TOP_GAP + TRI_HEIGHT);
right = new Point2D(SIDE_GAP + TRI_WIDTH, TOP_GAP + TRI_WIDTH);
}
private Point2D midpoint(Point2D p1, Point2D p2) {
return new Point2D((p1.getX() + p2.getX()) /
2, (p1.getY() + p2.getY()) / 2);
}
private void updateGraphics(boolean success){
if(success) {
copyCanvas();
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.fillText("Number of triangles: "+ countTriangles,5,15);
gc.fillText("Time : "+ (System.currentTimeMillis()- startTime )+ " mili seconds", 5,35);
gc.fillText("Levels: "+ numberOfLevels,5,55);
}
}
private Canvas getCanvas() {
Canvas canvas = new Canvas();
canvas.widthProperty().bind(widthProperty());
canvas.heightProperty().bind(heightProperty());
canvas.getGraphicsContext2D().setStroke(Color.RED);
canvas.getGraphicsContext2D().setLineWidth(0.3f);
return canvas;
}
private void copyCanvas() {
WritableImage image = backgroundCanvas.snapshot(null, null);
canvas.getGraphicsContext2D().drawImage(image, 0, 0);
}
/**
*/
class RunTask extends Task<Void>{
private int levels;
private Point2D top, left;
private Point2D right;
RunTask(int levels, Point2D top, Point2D left, Point2D right){
this.levels = levels;
this.top = top;
this.left = left;
this.right = right;
startTime = System.currentTimeMillis();
countTriangles = 0;
}
#Override public Void call() {
drawTriangle(levels,top, left, right);
return null;
}
#Override
protected void succeeded() {
updateGraphics(true);
super.succeeded();
}
#Override
protected void failed() {
updateGraphics(false);
}
}
}
public static void main(String[] args) {
launch("13");
}
}
The Swing example flattens the image to 6002 = 360,000 pixels. In contrast, the JavaFX example strokes almost 2.4 million overlapping polygons when finally rendered. Note that your JavaFX example measures both the time to compose the fractal and the time to render it in the scene graph.
If you want to preserve the strokes comprising the fractal, compose the result in a Canvas, as shown here.
If a flat Image is sufficient, compose the result in a BufferedImage, convert it to a JavaFX Image, and display it in an ImageView, as shown below. The JavaFX result is over a second faster than the Swing example on my hardware.
Because SwingFXUtils.toFXImage makes a copy, a background Task<Image> could continue to update a single BufferedImage while publishing interim Image results via updateValue(). Alternatively, this MandelbrotSet features a Task that updates a WritableImage via its PixelWriter.
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/44136040/230513
*/
public class BufferedImageTest extends Application {
private static final int PANEL_WIDTH = 600, PANEL_HEIGHT = 600;
private static final int TRI_WIDTH = 500, TRI_HEIGHT = 500;
private static final int SIDE_GAP = (PANEL_WIDTH - TRI_WIDTH) / 2;
private static final int TOP_GAP = (PANEL_HEIGHT - TRI_HEIGHT) / 2;
private final int numberOfLevels = 13;
private int countTriangles;
#Override
public void start(Stage stage) {
stage.setTitle("BufferedImageTest");
StackPane root = new StackPane();
Scene scene = new Scene(root);
root.getChildren().add(new ImageView(createImage()));
stage.setScene(scene);
stage.show();
}
private Image createImage() {
BufferedImage bi = new BufferedImage(
PANEL_WIDTH, PANEL_HEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setPaint(Color.white);
g.fillRect(0, 0, PANEL_WIDTH, PANEL_HEIGHT);
Point top = new Point(PANEL_WIDTH / 2, TOP_GAP);
Point left = new Point(SIDE_GAP, TOP_GAP + TRI_HEIGHT);
Point right = new Point(SIDE_GAP + TRI_WIDTH, TOP_GAP + TRI_HEIGHT);
g.setColor(Color.red);
long startTime = System.currentTimeMillis();
drawTriangle(g, numberOfLevels, top, left, right);
g.setPaint(Color.black);
g.drawString("Number of triangles: " + countTriangles, 15, 15);
g.drawString("Time : " + (System.currentTimeMillis() - startTime) + " ms", 15, 35);
g.drawString("Levels: " + numberOfLevels, 15, 50);
WritableImage image = SwingFXUtils.toFXImage(bi, null);
g.dispose();
return image;
}
private void drawTriangle(Graphics2D g, int levels, Point top, Point left, Point right) {
if (levels < 0) {
return;
}
countTriangles++;
Polygon tri = new Polygon();
tri.addPoint(top.x, top.y);
tri.addPoint(left.x, left.y);
tri.addPoint(right.x, right.y);
g.drawPolygon(tri);
// Get the midpoint on each edge in the triangle
Point p12 = midpoint(top, left);
Point p23 = midpoint(left, right);
Point p31 = midpoint(right, top);
// recurse on 3 triangular areas
drawTriangle(g, levels - 1, top, p12, p31);
drawTriangle(g, levels - 1, p12, left, p23);
drawTriangle(g, levels - 1, p31, p23, right);
}
private Point midpoint(Point p1, Point p2) {
return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
public static void main(String[] args) {
launch(args);
}
}
I made a little JavaFX app generating longshadows. At this point I struggle with the rendering (see picture).
The missing line on the rectangle's corner seems hard to fix. Changing the loop, which applies the manipulation, will mess up other shapes' shadow (e.g. circle).
The glitch at 'a' is related to the Bresenham algorithm, I guess.(?)
Additional info:
Changing the image resolution makes no difference: Gitches keep showing.
Question:
How to get it fixed? Does the SDK provide something helpful? Do I have to rewrite the code?
Code
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;
public class Main extends Application {
private PrintWriter writer;
private String colorObjFilter = "0x009688ff";
private static final String IMG_PATH = "img/ls-test-1k.png";
private static final int LONGSHADOW_LENGTH = 100;
private static final String
ANSI_GREEN = "\u001B[32m",
ANSI_RESET = "\u001B[0m";
#Override
public void start(Stage stage) throws Exception {
writer = new PrintWriter("out.txt", "UTF-8");
InputStream is = new FileInputStream(new File(IMG_PATH));
ImageView imageView = new ImageView(new Image(is));
Image image = imageView.getImage();
StackPane root = new StackPane();
Scene scene = new Scene(root, image.getWidth(), image.getHeight(), Paint.valueOf
("#EEEEEE"));
scene.addEventFilter(KeyEvent.KEY_PRESSED, evt -> {
if (evt.getCode().equals(KeyCode.ESCAPE)) {
stage.close();
}
});
final Canvas canvas = new Canvas(image.getWidth(), image.getHeight());
canvas.setOnMouseClicked((MouseEvent e) -> {
Color color = image.getPixelReader().getColor((int) e.getX(), (int) e.getY());
System.out.println(ANSI_GREEN + " -> " + color.toString() + ANSI_RESET);
colorObjFilter = color.toString();
try {
processImage(root, canvas, image);
} catch (IOException e1) {
e1.printStackTrace();
}
});
root.getChildren().addAll(imageView, canvas);
stage.setScene(scene);
stage.show();
}
private void processImage(StackPane root, Canvas canvas, Image image) throws IOException {
long delta = System.currentTimeMillis();
int width = (int) image.getWidth();
int height = (int) image.getHeight();
GraphicsContext gc = canvas.getGraphicsContext2D();
System.out.println("width: " + width + "\theight: " + height);
BufferedImage bufferedImage = ImageIO.read(new File(IMG_PATH));
// keep threshold small to get clean paths to draw
edgeDetection(gc, image, 0.00000001d);
writer.close();
Label label = new Label();
root.setAlignment(Pos.BOTTOM_LEFT);
root.setOnMouseMoved(event -> label.setText(event.getX() + "|" + event.getY()
+ "|" + bufferedImage.getRGB((int) event.getX(), (int) event.getY())));
root.getChildren().addAll(label);
System.out.println("took: " + (System.currentTimeMillis() - delta) + " ms");
}
public void edgeDetection(GraphicsContext gc, Image image, double threshold) {
Color topPxl, lowerPxl;
double topIntensity, lowerIntensity;
PixelWriter pw = gc.getPixelWriter();
for (int y = 0; y < image.getHeight() - 1; y++) {
for (int x = 1; x < image.getWidth(); x++) {
topPxl = image.getPixelReader().getColor(x, y);
lowerPxl = image.getPixelReader().getColor(x - 1, y + 1);
topIntensity = (topPxl.getRed() + topPxl.getGreen() + topPxl.getBlue()) / 3;
lowerIntensity = (lowerPxl.getRed() + lowerPxl.getGreen() + lowerPxl.getBlue()) / 3;
if (Math.abs(topIntensity - lowerIntensity) > threshold) {
int y2 = y;
for (int x2 = x; x2 < x + LONGSHADOW_LENGTH; x2++) {
y2++;
try {
Color color = image.getPixelReader().getColor(x2, y2);
// colorObjFilter protects the purple letter being manipulated
if (!color.toString().toLowerCase()
.contains(colorObjFilter.toLowerCase())) {
pw.setColor(x2, y2, Color.color(.7f, .7f, .7f, .9f));
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
}
}
}
public static void main(String[] args) {
launch(args);
}
}
I have no idea why your original program has some rendering artifacts.
Here is an alternate solution, which is extremely brute force, as it just generates a shadow image that it renders over and over again at different offsets to end up with a long shadow. A couple of methods of generating the shadowImage are demonstrated, one is a ColorAdjust effect on the original image, the other is generation of a shadow image using a PixelWriter.
Your original solution of using a PixelWriter for everything with an appropriate algorithm for shadow generation is more elegant (if you can get it to work ;-).
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ShadowSpray extends Application {
private static final double W = 400;
private static final double H = 400;
private static final int SHADOW_LENGTH = 100;
private static final double IMG_X = 20;
private static final double IMG_Y = 20;
private static final int FONT_SIZE = 200;
private static final double SHADOW_SLOPE_FACTOR = 1.5;
Color SHADOW_COLOR = Color.GRAY.brighter();
#Override
public void start(Stage stage) {
Image image = getImage();
Canvas canvas = new Canvas(W, H);
GraphicsContext gc = canvas.getGraphicsContext2D();
// drawWithShadowUsingStencil(gc, image, IMG_X, IMG_Y, SHADOW_LENGTH, SHADOW_COLOR);
drawWithShadowUsingColorAdjust(gc, image, IMG_X, IMG_Y, SHADOW_LENGTH);
stage.setScene(new Scene(new Group(canvas)));
stage.show();
}
private void drawWithShadowUsingColorAdjust(GraphicsContext gc, Image image, double x, double y, int shadowLength) {
// here the color adjust for the shadow is based upon the intensity of the input image color
// which is a weird way to calculate a shadow color, but does come out nicely
// because it appropriately handles antialiased input images.
ColorAdjust monochrome = new ColorAdjust();
monochrome.setBrightness(+0.5);
monochrome.setSaturation(-1.0);
gc.setEffect(monochrome);
for (int offset = shadowLength; offset > 0; --offset) {
gc.drawImage(image, x + offset, y + offset / SHADOW_SLOPE_FACTOR);
}
gc.setEffect(null);
gc.drawImage(image, x, y);
}
private void drawWithShadowUsingStencil(GraphicsContext gc, Image image, double x, double y, int shadowLength, Color shadowColor) {
Image shadow = createShadowImage(image, shadowColor);
for (int offset = shadowLength; offset > 0; --offset) {
gc.drawImage(shadow, x + offset, y + offset / SHADOW_SLOPE_FACTOR);
}
gc.drawImage(image, x, y);
}
private Image createShadowImage(Image image, Color shadowColor) {
WritableImage shadow = new WritableImage(image.getPixelReader(), (int) image.getWidth(), (int) image.getHeight());
PixelReader reader = shadow.getPixelReader();
PixelWriter writer = shadow.getPixelWriter();
for (int ix = 0; ix < image.getWidth(); ix++) {
for (int iy = 0; iy < image.getHeight(); iy++) {
int argb = reader.getArgb(ix, iy);
int a = (argb >> 24) & 0xFF;
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = argb & 0xFF;
// because we use a binary choice, we lose anti-alising info in the shadow so it looks a bit jaggy.
Color fill = (r > 0 || g > 0 || b > 0) ? shadowColor : Color.TRANSPARENT;
writer.setColor(ix, iy, fill);
}
}
return shadow;
}
private Image getImage() {
Label label = new Label("a");
label.setStyle("-fx-text-fill: forestgreen; -fx-background-color: transparent; -fx-font-size: " + FONT_SIZE + "px;");
Scene scene = new Scene(label, Color.TRANSPARENT);
SnapshotParameters snapshotParameters = new SnapshotParameters();
snapshotParameters.setFill(Color.TRANSPARENT);
return label.snapshot(snapshotParameters, null);
}
public static void main(String[] args) {
launch();
}
}
Inbuilt, JavaFX has a DropShadow effect, which is almost what you want, especially when you set the spread to 1 and the radius to 0, however, it just generates a single offset shadow image rather than a long shadow effect.
With some alternate text and a shorter "long shadow":
I recently wanted to create an animated background in JavaFX, similar to the Swing example seen here. I used a Canvas on which to draw, as shown in Working with the Canvas API, and an AnimationTimer for the drawing loop, as shown in Animation Basics. Unfortunately, I'm not sure how to resize the Canvas automatically as the enclosing Stage is resized. What is a good approach?
A similar question is examined in How to make canvas Resizable in javaFX?, but the accepted answer there lacks the binding illustrated in the accepted answer here.
In the example below, the static nested class CanvasPane wraps an instance of Canvas in a Pane and overrides layoutChildren() to make the canvas dimensions match the enclosing Pane. Note that Canvas returns false from isResizable(), so "the parent cannot resize it during layout," and Pane "does not perform layout beyond resizing resizable children to their preferred sizes." The width and height used to construct the canvas become its initial size. A similar approach is used in the Ensemble particle simulation, FireworksApp, to scale a background image while retaining its aspect ratio.
As an aside, note the difference from using fully saturated colors compared to the original. These related examples illustrate placing controls atop the animated background.
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/a/31761362/230513
* #see https://stackoverflow.com/a/8616169/230513
*/
public class Baubles extends Application {
private static final int MAX = 64;
private static final double WIDTH = 640;
private static final double HEIGHT = 480;
private static final Random RND = new Random();
private final Queue<Bauble> queue = new LinkedList<>();
private Canvas canvas;
#Override
public void start(Stage stage) {
CanvasPane canvasPane = new CanvasPane(WIDTH, HEIGHT);
canvas = canvasPane.getCanvas();
BorderPane root = new BorderPane(canvasPane);
CheckBox cb = new CheckBox("Animate");
cb.setSelected(true);
root.setBottom(cb);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
for (int i = 0; i < MAX; i++) {
queue.add(randomBauble());
}
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext g = canvas.getGraphicsContext2D();
g.setFill(Color.BLACK);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
for (Bauble b : queue) {
g.setFill(b.c);
g.fillOval(b.x, b.y, b.d, b.d);
}
queue.add(randomBauble());
queue.remove();
}
};
loop.start();
cb.selectedProperty().addListener((Observable o) -> {
if (cb.isSelected()) {
loop.start();
} else {
loop.stop();
}
});
}
private static class Bauble {
private final double x, y, d;
private final Color c;
public Bauble(double x, double y, double r, Color c) {
this.x = x - r;
this.y = y - r;
this.d = 2 * r;
this.c = c;
}
}
private Bauble randomBauble() {
double x = RND.nextDouble() * canvas.getWidth();
double y = RND.nextDouble() * canvas.getHeight();
double r = RND.nextDouble() * MAX + MAX / 2;
Color c = Color.hsb(RND.nextDouble() * 360, 1, 1, 0.75);
return new Bauble(x, y, r, c);
}
private static class CanvasPane extends Pane {
private final Canvas canvas;
public CanvasPane(double width, double height) {
canvas = new Canvas(width, height);
getChildren().add(canvas);
}
public Canvas getCanvas() {
return canvas;
}
#Override
protected void layoutChildren() {
super.layoutChildren();
final double x = snappedLeftInset();
final double y = snappedTopInset();
// Java 9 - snapSize is deprecated, use snapSizeX() and snapSizeY() accordingly
final double w = snapSize(getWidth()) - x - snappedRightInset();
final double h = snapSize(getHeight()) - y - snappedBottomInset();
canvas.setLayoutX(x);
canvas.setLayoutY(y);
canvas.setWidth(w);
canvas.setHeight(h);
}
}
public static void main(String[] args) {
launch(args);
}
}
I combined both prior solutions ( #trashgod and #clataq's ) by putting the canvas in a Pane and binding it to it:
private static class CanvasPane extends Pane {
final Canvas canvas;
CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
canvas = new Canvas(width, height);
getChildren().add(canvas);
canvas.widthProperty().bind(this.widthProperty());
canvas.heightProperty().bind(this.heightProperty());
}
}
Couldn't you do this with a Binding as well? The following seems to produce the same results without having to add the derived class.
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.DoubleBinding;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* #see http://stackoverflow.com/a/31761362/230513
* #see http://stackoverflow.com/a/8616169/230513
*/
public class Baubles extends Application {
private static final int MAX = 64;
private static final double WIDTH = 640;
private static final double HEIGHT = 480;
private static final Random RND = new Random();
private final Queue<Bauble> queue = new LinkedList<>();
private Canvas canvas;
#Override
public void start(Stage stage) {
canvas = new Canvas(WIDTH, HEIGHT);
BorderPane root = new BorderPane(canvas);
CheckBox cb = new CheckBox("Animate");
cb.setSelected(true);
root.setBottom(cb);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
// Create bindings for resizing.
DoubleBinding heightBinding = root.heightProperty()
.subtract(root.bottomProperty().getValue().getBoundsInParent().getHeight());
canvas.widthProperty().bind(root.widthProperty());
canvas.heightProperty().bind(heightBinding);
for (int i = 0; i < MAX; i++) {
queue.add(randomBauble());
}
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext g = canvas.getGraphicsContext2D();
g.setFill(Color.BLACK);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
for (Bauble b : queue) {
g.setFill(b.c);
g.fillOval(b.x, b.y, b.d, b.d);
}
queue.add(randomBauble());
queue.remove();
}
};
loop.start();
cb.selectedProperty().addListener((Observable o) -> {
if (cb.isSelected()) {
loop.start();
} else {
loop.stop();
}
});
}
private static class Bauble {
private final double x, y, d;
private final Color c;
public Bauble(double x, double y, double r, Color c) {
this.x = x - r;
this.y = y - r;
this.d = 2 * r;
this.c = c;
}
}
private Bauble randomBauble() {
double x = RND.nextDouble() * canvas.getWidth();
double y = RND.nextDouble() * canvas.getHeight();
double r = RND.nextDouble() * MAX + MAX / 2;
Color c = Color.hsb(RND.nextDouble() * 360, 1, 1, 0.75);
return new Bauble(x, y, r, c);
}
public static void main(String[] args) {
launch(args);
}
}
I need to execute N calculations (with graphics updates) in X seconds using javax.swing.Timer.
What's the best way for do this?
If I set a fixed delay for the timer I exceed the X seconds, because the time required for each execution is delay + calculations.
To work around this I've tried to set the delay of the timer dynamically, but still the time is not accurate.
As a last chance I've tried to set the dalay to 1ms and use Thread.sleep(sleepTime) for control the duration and this work perfectly, without affect the animations.
My question is:
Is this a good solution? Can I use Thread.sleep(sleepTime) inside the javax.swing.Timer?
Edit, here's some code to better understand. I want to say that the graphics is needed only to see if the movements are correct, in the final version i only need to update the game and generate a report of the match result.
Main frame with "game loop":
package engine.test;
import engine.entity.Skill;
import engine.math.Point;
import engine.math.Vector;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.Timer;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public final class EngineFrame extends JFrame implements ActionListener
{
public static final int SCALE = 6;
public static final int WIDTH = 100 * SCALE;
public static final int HEIGHT = 60 * SCALE;
public static final int DURATION = 2; // animation duration in seconds
public static final int FPS = 60;
public static final int SKIP_TICKS = 1000 / FPS;
private final JSONObject data;
private final ArrayList<Player> players;
private PlayersPanel playersPanel;
public EngineFrame(String title) throws JSONException, FileNotFoundException
{
super(title);
setSize(WIDTH, HEIGHT);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
createMenu();
data = new JSONObject(loadData());
players = buildPlayers(data.getJSONArray("teams").getJSONObject(0).getJSONArray("players"));
playersPanel = new PlayersPanel(players);
add(playersPanel);
}
public ArrayList<Player> getPlayers()
{
return players;
}
/**
* Returns the data required to run a match.
*
* #return a json string representing the match data
* #throws FileNotFoundException
*/
private String loadData() throws FileNotFoundException
{
Scanner fileInput = new Scanner(new File(
"C:\\Users\\packard bell\\Documents\\JavaProjects\\Engine\\src\\resources\\data.json"
));
String jsonText = "";
while (fileInput.hasNextLine()) {
jsonText += fileInput.nextLine();
}
return jsonText;
}
/**
* Creates and returns the player entities.
*
* #param playersData
* #return
*/
private ArrayList<Player> buildPlayers(JSONArray playersData) throws JSONException
{
ArrayList<Player> players = new ArrayList<Player>();
JSONObject playerData;
Player player;
for (int i = 0, l = playersData.length(); i < l; i++) {
playerData = playersData.getJSONObject(i);
player = new Player.Builder()
.setId(playerData.getInt("id"))
.setFirstName(playerData.getString("first_name"))
.setLastName(playerData.getString("last_name"))
.setMass(playerData.getInt("weight"))
.setSkills(buildSkills(playerData.getJSONObject("skills")))
.setInitialPosition(new Point(0, i * 10 + 20))
.setInitialVelocity(new Vector(0, 0))
.build();
players.add(player);
}
return players;
}
/**
*
*/
private Map<Skill, Double> buildSkills(JSONObject skillsData) throws JSONException
{
Map<Skill, Double> skills = new HashMap();
for (Skill skill : Skill.values()) {
skills.put(
skill,
skill.getMinValue() + (skill.getMaxValue() - skill.getMinValue()) * (skillsData.getDouble(skill.getName()) / 100)
);
}
return skills;
}
/**
*
*/
private void createMenu()
{
JMenu seekMenu = new JMenu("Seek behavior");
JMenuItem initSeek = new JMenuItem("Init seek");
initSeek.addActionListener(this);
JMenuItem runSeek = new JMenuItem("Run seek");
runSeek.addActionListener(this);
JMenuItem stopSeek = new JMenuItem("Stop seek");
stopSeek.addActionListener(this);
seekMenu.add(initSeek);
seekMenu.add(runSeek);
seekMenu.add(stopSeek);
JMenuBar bar = new JMenuBar();
bar.add(seekMenu);
setJMenuBar(bar);
}
public static void main(String[] args) throws JSONException, FileNotFoundException, InterruptedException
{
EngineFrame frame = new EngineFrame("Engine");
}
#Override
public void actionPerformed(ActionEvent e)
{
String menuString = e.getActionCommand();
if (menuString.equalsIgnoreCase("init seek")) {
Player player1 = getPlayers().get(0);
Player player2 = getPlayers().get(1);
player1.setPosition(new Point(0, 20));
player1.setVelocity(new Vector(0, 0));
player2.setPosition(new Point(0, 30));
player2.setVelocity(new Vector(0, 0));
repaint();
}
else if (menuString.equalsIgnoreCase("run seek")) {
Timer t = new Timer(1, new ActionListener() {
private final long start = System.currentTimeMillis();
private long nextGameUpdate = start;
private long sleepTime = 0;
private int loops = DURATION * 1000 / SKIP_TICKS;
#Override
public void actionPerformed(ActionEvent e) {
Player player1 = getPlayers().get(0);
Player player2 = getPlayers().get(1);
//System.out.println("Position: " + player1.getPosition());
//System.out.println("Velocity: " + player1.getVelocity());
System.out.println();
player1.getSteering().seek(new Point(50, 20));
player2.getSteering().seek(new Point(50, 30));
player1.update();
player2.update();
repaint();
nextGameUpdate += SKIP_TICKS;
sleepTime = nextGameUpdate - System.currentTimeMillis();
//System.out.println(nextGameUpdate);
//System.out.println(sleepTime);
loops--;
if (sleepTime >= 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException ex) {
Logger.getLogger(EngineFrame.class.getName()).log(Level.SEVERE, null, ex);
}
}
if (loops <= 0) {
((Timer)e.getSource()).stop();
long end = System.currentTimeMillis();
// should be 2000ms (equals to DURATION constant)
System.out.println("Duration: " + (end - start) + "ms");
}
}
});
t.setInitialDelay(0);
t.start();
}
}
// version without swing timer (it works if called in the main method)
private void runSeek() throws InterruptedException
{
Player player1 = getPlayers().get(0);
Player player2 = getPlayers().get(1);
player1.setPosition(new Point(0, 20));
player2.setPosition(new Point(0, 30));
// run
long start = System.currentTimeMillis();
long nextGameUpdate = start;
long sleepTime = 0;
int i = DURATION * 1000 / SKIP_TICKS;
System.out.println("Loop executions: " + i);
int steps = 0;
String stepsCode = "[";
String velocitiesCode = "[";
String positionsCode = "[";
while (i > 0) {
stepsCode += steps + ", ";
velocitiesCode += player1.getVelocity().len() + ", ";
positionsCode += player1.getPosition().toVector().len() + ", ";
System.out.println("Position: " + player1.getPosition());
System.out.println("Velocity: " + player1.getVelocity());
System.out.println();
player1.getSteering().seek(new Point(50, 20));
player2.getSteering().seek(new Point(50, 30));
player1.update();
player2.update();
repaint();
nextGameUpdate += SKIP_TICKS;
sleepTime = nextGameUpdate - System.currentTimeMillis();
steps += sleepTime;
//System.out.println(sleepTime);
if (sleepTime >= 0) {
Thread.sleep(sleepTime);
}
i--;
}
stepsCode = stepsCode.substring(0, stepsCode.length() - 2) + "]";
velocitiesCode = velocitiesCode.substring(0, velocitiesCode.length() - 2) + "]";
positionsCode = positionsCode.substring(0, positionsCode.length() - 2) + "]";
long end = System.currentTimeMillis();
System.out.println("Duration: " + (end - start) + "ms");
System.out.println("Steps:");
System.out.println(stepsCode);
System.out.println("Positions:");
System.out.println(positionsCode);
System.out.println("Velocities:");
System.out.println(velocitiesCode);
}
}
Here's the JPanel that draw the entities:
package engine.test;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.ArrayList;
import javax.swing.JPanel;
public class PlayersPanel extends JPanel
{
private ArrayList<Player> players;
public PlayersPanel(ArrayList<Player> players)
{
this.players = players;
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Player player : players) {
int x = (int) (player.getPosition().x() * EngineFrame.SCALE);
int y = (int) (player.getPosition().y() * EngineFrame.SCALE);
g2.setColor(Color.BLACK);
g2.fillArc(x, y, 18, 18, 0, 360);
g2.setColor(new Color(0x11539f));
g2.fillArc(x + 2, y + 2, 14, 14, 0, 360);
}
}
}
Is this a good solution? Can I use Thread.sleep(sleepTime) inside the javax.swing.Timer?
No, never do this. Don't worry about changing the delay. Instead consider what it is you want to draw and when you want to draw them. You can create a class for the different object to draw, and have then maintain a delayed state that will determine when they should be drawn. If you want to change an object velocity, then increase the number of pixels you increment it's movement.
Here's an example you may find useful, that shows my first point of maintaining a delayed state. You can see the balls are thrown at different times.
Other than that, you should post some code that show's exactly what you're trying to do. Your question is somewhat vague.
Let's say I have a BufferedImage of type TYPE_4BYTE_ABGR in Swing and I want to draw only a part of it. For example I would like to draw the left half only or some triangular shape or something more complicated.
Reason is that the final image shall be composed from subparts of individual images I have.
What's the best way to do that?
I would prefer to define a polygon and then use this shape as a mask for drawing, if this is possible.
My current idea: make a copy of the individual image and set all pixels outside the wished shape to transparent, then draw the whole image. I think this might work but might be too slow with the copying and all.
edit:
I tested the solution of Guillaume and found that it works and does not extremely slow down the painting. Using a clip resulted in an increase of drawing time from 14ms to 35ms but these times are very inaccurate. I used profiling the EDT from here. Here is the code.
import java.awt.AWTEvent;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
*
*/
public class ClipTilesTest {
// tile size and number of tiles in each row/column
private static int TILE_SIZE = 100;
private static int TILE_NUM = 6;
// taken from https://stackoverflow.com/questions/5541493/how-do-i-profile-the-edt-in-java-swing
public static class TimedEventQueue extends EventQueue {
#Override
protected void dispatchEvent(AWTEvent event) {
long startNano = System.nanoTime();
super.dispatchEvent(event);
long endNano = System.nanoTime();
if (endNano - startNano > 5000000) {
System.out.println(((endNano - startNano) / 1000000) + "ms : " + event);
}
}
}
private static void initUI() {
Toolkit.getDefaultToolkit().getSystemEventQueue().push(new TimedEventQueue());
// download image
BufferedImage image;
try {
image = ImageIO.read(new URL("http://download.chip.eu//ii/163859211_4b28e1e687.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
return;
}
// take out small chunk
final BufferedImage tile = image.getSubimage(0, 0, TILE_SIZE, TILE_SIZE);
JFrame frame = new JFrame();
frame.setTitle(ClipTilesTest.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// the panel containing some tiles
JPanel view = new JPanel() {
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
for (int i = 0; i < TILE_NUM; i++) {
for (int j = 0; j < TILE_NUM; j++) {
// version 1
/*
g2d.setClip(i * TILE_SIZE, j * TILE_SIZE , (i+1)*TILE_SIZE, (j+1)*TILE_SIZE);
g2d.drawImage(tile, i * TILE_SIZE, j * TILE_SIZE, null);
*/
// version 2
g2d.setClip(i * TILE_SIZE, j * TILE_SIZE , i*TILE_SIZE + TILE_SIZE/2, (j+1)*TILE_SIZE);
g2d.drawImage(tile, i * TILE_SIZE, j * TILE_SIZE, null);
g2d.setClip(i * TILE_SIZE + TILE_SIZE/2, j * TILE_SIZE , (i+1)*TILE_SIZE , (j+1)*TILE_SIZE);
g2d.drawImage(tile, i * TILE_SIZE, j * TILE_SIZE, null);
}
}
}
};
view.setPreferredSize(new Dimension(TILE_SIZE * TILE_NUM, TILE_SIZE * TILE_NUM));
// add, pack, set visible
frame.add(view);
frame.pack();
frame.setVisible(true);
// now make a repaint event, so we can start measuring
view.repaint();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ClipTilesTest.initUI();
}
});
}
}
One easy way to achieve this effect, is to modify the "clip" of the Graphics object and to set it to the shape you want to draw.
I don't know how efficient this is, but you could consider caching the clipped image and then draw the entire cached image.
Here is a small demo code:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestClippedPanel {
private static class ClippedPanel extends JPanel {
private ImageIcon image;
private List<Shape> shapes;
public ClippedPanel() throws MalformedURLException {
shapes = new ArrayList<Shape>();
image = new ImageIcon(new URL("http://download.chip.eu//ii/163859211_4b28e1e687.jpg"));
Random random = new Random();
for (int i = 0; i < 10; i++) {
int x = random.nextInt(image.getIconWidth() - 1);
int y = random.nextInt(image.getIconHeight() - 1);
int w = random.nextInt(image.getIconWidth() - x) + 1;
int h = random.nextInt(image.getIconHeight() - y) + 1;
shapes.add(new Rectangle(x, y, w, h));
}
for (int i = 0; i < 10; i++) {
int x = random.nextInt(image.getIconWidth() - 1);
int y = random.nextInt(image.getIconHeight() - 1);
int w = random.nextInt(image.getIconWidth() - x) + 1;
int h = random.nextInt(image.getIconHeight() - y) + 1;
shapes.add(new Ellipse2D.Double(x, y, w, h));
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Image img = image.getImage();
for (Shape shape : shapes) {
((Graphics2D) g).setClip(shape);
g.drawImage(img, 0, 0, this);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(image.getIconWidth(), image.getIconHeight());
}
}
protected void initUI() throws MalformedURLException {
final JFrame frame = new JFrame(TestClippedPanel.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final ClippedPanel panel = new ClippedPanel();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
new TestClippedPanel().initUI();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}