JavaFX Canvas: Draw a shape exclusively within another shape - java

I'm currently developing a game in Java, and I've been trying to figure out how to draw a shape (e.g. a circle) to the canvas, on top of a different shape (e.g. a square), but to only draw the parts of the circle which are intersecting the square, similar to a clipping mask between layers in Photoshop.
I've tried using GraphicsContext.clearRect() to clear the areas where the bottom shape is not, but that removes the background.
The code below produces this result:
However, this is the result I desire:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class CircleWithinSquareTest extends Application {
#Override
public void start(Stage stage) throws Exception {
int width = 200;
int height = 200;
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D();
AnimationTimer timer = new AnimationTimer() {
final int bgCellSize = 8;
final int x = 100;
final int y = 100;
double angle = 0;
#Override
public void handle(long now) {
/* Draw checkered background */
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, width, height);
gc.setFill(Color.LIGHTGRAY);
boolean odd = false;
for (int y = 0; y < height; y += bgCellSize) {
odd = !odd;
for (int x = odd ? 0 : bgCellSize; x < width; x += bgCellSize * 2) {
gc.fillRect(x, y, bgCellSize, bgCellSize);
}
}
/* Draw square */
gc.setFill(Color.BLUE);
gc.fillRect(x, y, 50, 50);
/* Draw circle */
gc.save();
angle += 5;
if (angle >= 360) {
angle = 0;
}
Rotate r = new Rotate(angle, x, y);
gc.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy());
gc.setFill(Color.RED);
gc.fillOval(x, y, 30, 30);
gc.restore();
}
};
timer.start();
Group root = new Group(canvas);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}

You can use clipping, add next code before setTransform:
gc.beginPath();
gc.rect(x, y, 50, 50);
gc.closePath();
gc.clip();

Related

Java block not appearing

Trying to make a game. Want a yellow block at the bottom of the screen, a dark grey (or black, doesnt really matter) bacground, and blue squares flying from the top of the screen at random times. Have been at it for awfully long but the blue squares do not appear, and I have no idea why. Im a beginner at Java, and have been through many resources - please advise, thanks!
Game Class Below
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.awt.*;
public class Game extends JPanel {
//THIS IS DISPLAY
static int width = 1000;
static int height = 700;
int randNumb = 0;
int squareWidth = 50;
int squareHeight = 50;
int squareYLocation = -squareWidth;
boolean numberCreated = false;
static boolean gameRunning = false;
public Game() {
setBackground(Color.DARK_GRAY);
setPreferredSize(new Dimension(1000, 700));
}
//generates a random Y value inside the window for the square to spawn at
public void generateRandomNumber() {
Random rand = new Random();
randNumb = rand.nextInt(width - squareWidth);
numberCreated = true;
}
public void paintComponent ( Graphics g )
{
//int width = getWidth();
//int height = getHeight();
//g.setColor(Color.DARK_GRAY);
super.paintComponent( g );
//g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, width, height);
g.setColor(Color.BLUE);
g.fillRect(randNumb, squareYLocation, squareWidth, squareHeight);
g.setColor( Color.YELLOW );
g.drawRect( 500, 600, 50, 50);
g.fillRect(500, 600, 50, 50);
//g.setColor(Color.blue);
//g.fillRect(100, 200, 32, 32);
}
public void update() {
//calls the generateRandomNumber() method which gives the square a random x value inside the screen
if (!numberCreated) {
generateRandomNumber();
}
//moves the squares y coordinate towards the bottom of the screen and stops once it hits the bottom
if (squareYLocation <= height) {
squareYLocation++;
//resets the x and y location to a new position
} else {
numberCreated = false;
squareYLocation = -squareHeight;
}
}
public void start() {
gameRunning = true;
}
}
Main
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferStrategy;
import java.util.Random;
import javax.swing.*;
public class Main{
public static void main(String[] args) {
//Constructor with frame title
JFrame frame = new JFrame("words");
//Makes frame visible
frame.setVisible(true);
//Size of frame & location
frame.setBounds(240, 80, 1000, 700);
//How to close the window
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new Game());
}
}
This statement means
int squareYLocation = -squareWidth;
that the Y position is -50 so the blue square will appear off screen. Make it 0 or positive
int squareYLocation = 0;

Translate transition reset position in javaFX

I'm trying to move a circle from (100,200) to (400,200) and after one cycle the circle should start moving from (100,100) to (200,100) and keep repeating that motion. After the first cycle I reset the position of the circle using circle.setCenterX(100) and circle.setCenterY(100). However, this is not reflected in the animation. The circle resets to (400,100) and keeps moving forward in the X direction instead of repeating the motion. I'm new to javaFX. Any help would be appreciated.
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application
{
public static void main(String[] args)
{
launch(args);
}
final double lambda = 0.1; // pixel per millisecond
double posX = 100;
double posY = 200;
double time = 0;
double velocityX = 1*lambda;
double velocityY = 0*lambda;
Circle circle = new Circle(posX, posY, 20, Color.AQUA);
Circle ref1 = new Circle(100, 200, 5, Color.CADETBLUE);
Circle ref2 = new Circle(400, 200, 5, Color.CADETBLUE);
Circle ref3 = new Circle(100, 100, 5, Color.CADETBLUE);
#Override
public void start(Stage stage) throws Exception
{
Pane pane = new Pane();
pane.getChildren().addAll(circle, ref1, ref2, ref3);
BorderPane root = new BorderPane();
root.setCenter(pane);
root.setStyle("-fx-background-color: #29353B");
double WIDTH = 800;
double HEIGHT = 600;
Scene scene = new Scene(root, WIDTH, HEIGHT);
stage.setScene(scene);
stage.show();
move(3000);
}
public void move(double dt) // dt in milliseconds
{
System.out.println(circle.getCenterX()+", "+circle.getCenterY());
TranslateTransition translateTransition = new TranslateTransition(Duration.millis(dt), circle);
//translateTransition.setInterpolator(Interpolator.LINEAR);
translateTransition.setByX(this.velocityX*dt);
translateTransition.setByY(this.velocityY*dt);
translateTransition.setCycleCount(1);
translateTransition.play();
translateTransition.setOnFinished(actionEvent -> { updatePos(dt); move(2000); });
}
public void updatePos(double dt)
{
//this.posX += this.velocityX*dt;
//this.posY += this.velocityY*dt;
this.posX = 100;
this.posY = 100;
circle.setCenterX(this.posX);
circle.setCenterY(this.posY);
}
}
The TranslateTransition modifies the translateX and translateY properties, not the centerX and centerY properties. If you modify the centerX and centerY properties when the animation is complete, you should also reset translateX and translateY to 0 for the circle to appear at those coordinates:
public void updatePos(double dt) {
//this.posX += this.velocityX*dt;
//this.posY += this.velocityY*dt;
this.posX = 100;
this.posY = 100;
circle.setCenterX(this.posX);
circle.setCenterY(this.posY);
circle.setTranslateX(0);
circle.setTranslateY(0);
}
Alternatively, you could use a Timeline instead of a TranslateTransition to directly manipulate the centerX and centerY properties in the animation:
public void move(double dt) /* dt in milliseconds */ {
System.out.println(circle.getCenterX() + ", " + circle.getCenterY());
double targetX = circle.getCenterX() + this.velocityX * dt;
double targetY = circle.getCenterY() + this.velocityY * dt;
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(dt),
new KeyValue(circle.centerXProperty(), targetX),
new KeyValue(circle.centerYProperty(), targetY)));
timeline.setOnFinished(actionEvent -> {
updatePos(dt);
move(2000);
});
timeline.play();
}
public void updatePos(double dt) {
this.posX = 100;
this.posY = 100;
circle.setCenterX(this.posX);
circle.setCenterY(this.posY);
}

How to draw circles inside each other with different widths?

Im trying to draw circles inside of each other which have the same centres.
But the width should be different for each circle - it should be done inside a while loop.
The result should look like the picture i have uploaded:
My code is shown below:
package modelwhile;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class exercise4_figure3 extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
GridPane root = initContent();
Scene scene = new Scene(root);
stage.setTitle("Loops");
stage.setScene(scene);
stage.show();
}
private GridPane initContent() {
GridPane pane = new GridPane();
Canvas canvas = new Canvas(200, 200);
pane.add(canvas, 0, 0);
drawShapes(canvas.getGraphicsContext2D());
return pane;
}
// ------------------------------------------------------------------------
// circle figure begins here
private void drawShapes(GraphicsContext gc) {
int x = 80;
int y = 80;
int r1 = 20;
int r2 = 60;
while (r1 <= 80) {
gc.strokeOval(x - r2, y - r2, r1, r2);
r1 = r1 + 10;
}
}
}
any help would be appreciated.
The issue is that you aren't moving on the x-axis to account for the added width of each new oval. You need to move half the distance being added to the oval in order to keep them all in the same relative position.
Below is your drawShapes() method updated to include this movement. You'll notice I removed your x, y, and r2 variables because they didn't really have any need to be variables since nothing was done with them.
private void drawShapes(GraphicsContext gc) {
int r = 20;
while (r <= 80) {
gc.strokeOval(80-(r/2), 80, r, 60);
r = r + 10;
}
}

Drawing rectangles in java

I have this code where I have random rectangles generating over the entire canvas. I also have 1 rectangle at the center.
I need to get the 1 rectangle at the center to move around when the user hovers the mouse over the canvas.
Here is my code
package sample;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
private static final int DRAW_WIDTH = 800;
private static final int DRAW_HEIGHT = 500;
private Animation myAnimation;
private Canvas canvas;
private GraphicsContext gtx;
#Override
public void start(Stage stage) throws Exception
{
stage.setTitle("tRIPPy BoXXX");
canvas = new Canvas(DRAW_WIDTH, DRAW_HEIGHT);
gtx = canvas.getGraphicsContext2D();
gtx.setLineWidth(3);
gtx.setFill(Color.BLACK);
gtx.fillRect(0, 0, DRAW_WIDTH, DRAW_HEIGHT);
VBox vBox = new VBox();
vBox.getChildren().addAll(canvas);
Scene scene = new Scene(vBox, DRAW_WIDTH, DRAW_HEIGHT);
stage.setScene(scene);
stage.show();
myAnimation = new Animation();
myAnimation.start();
}
class Animation extends AnimationTimer
{
#Override
public void handle(long now)
{
Random rand = new Random();
double red = rand.nextDouble();
double green = rand.nextDouble();
double blue = rand.nextDouble();
Color boxColor = Color.color(red,green,blue);
gtx.setStroke(boxColor);
....
This is the box that I want to have moving around with the users mouse. I have tried some things on my own, however I can't get the rect to stay the way it is in the code.
.....
int rectX = 800 / 2;
int rectY = 500 / 2;
for (int side = 10; side <= 100; side += 10) {
gtx.strokeRect(rectX - side / 2, rectY - side / 2, side, side);
}
int centerX = (int)(Math.random()*800);
int centerY = (int)(Math.random()*800);
for (int side = (int)(Math.random()*100); side <= Math.random()* 100; side += Math.random()*100)
{
gtx.strokeRect(centerX - side / 2, centerY - side / 2, side, side);
}
}
}
public static void main(String[] args)
{
launch(args);
}
If you plan to move some graphics arround, why do you then start with a canvas? On a canvas you cannot move anything arround unless you constantly want to redraw everything again and again. Putting your rectangles into the scene graph is much better suited for this task.

JavaFX rendering / image manipulation

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":

Categories

Resources