JavaFX GraphicsContext Inverted Circle - java

I've been trying to find a way to do this online, but I haven't been able to find anything.
I want to draw an "inverse circle" with the JavaFX GraphicsContext.
These images show what I want.
Original:
With "Inverted Circle" (What I want to draw):
In an image editor I can just erase the circle area in the new layer... I don't see any functions that would do that in GraphicsContext.
I'd need to be able to choose the center point and radius of this "circle".
Thanks!

I am not very sure of directly using GraphicsContext, but you can do it using Blending.
ImageView image; // Your image
Circle mask = new Circle();
Group g = new Group();
g.setBlendMode(BlendMode.SRC_ATOP);
g.getChildren.add(image);
g.getChildren.add(mask);

Construct a circular path and use it as clip when drawing the image:
#Override
public void start(Stage primaryStage) {
Image image = new Image("https://i.stack.imgur.com/zEoW1.jpg");
double w = image.getWidth();
double h = image.getHeight();
Canvas canvas = new Canvas(w, h);
GraphicsContext gc = canvas.getGraphicsContext2D();
// draw background
gc.setFill(Color.BLACK);
gc.fillRect(0, 0, w, h);
double r = Math.min(h, w) * 2 / 5;
double cx = w / 2;
double cy = h / 2;
// create circular path
gc.beginPath();
gc.moveTo(cx - r, cy); // to first point on the circle
gc.arc(cx, cy, r, r, 180, 360);
gc.closePath();
gc.clip();
gc.drawImage(image, 0, 0);
StackPane root = new StackPane();
root.getChildren().add(canvas);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

Related

setStroke() and setFill() produce different pixel colors

I'm trying to understand how the JavaFX canvas setStroke method works. It doesn't set the color of pixels to the desired value.
No problem with the setFill method though.
Canvas canvas = new Canvas(500, 500);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.RED);
gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
int x = 10;
int y = 10;
printPixelRGB(x, y); // displays: red=255, green=0, blue=0 -> OK
// scenario 1: using fill-methods
gc.setStroke(Color.WHITE);
gc.strokeRect(x, y, 200, 200);
printPixelRGB(x, y); // displays: red=255, green=191, blue=191 -> ????
// scenario 2: using stroke-methods
gc.setFill(Color.WHITE);
gc.fillRect(x, y, 200, 200);
printPixelRGB(x, y); // displays: red=255, green=255, blue=255 -> OK
private void printPixelRGB(int x, int y) {
WritableImage snap = gc.getCanvas().snapshot(null, null);
int color = snap.getPixelReader().getArgb(x, y);
int red = (color >> 16) & 0xff;
int green = (color >> 8) & 0xff;
int blue = color & 0xff;
System.out.printf("red=%-3d, green=%-3d, blue=%-3d \n", red, green, blue);
} // printPixelRGB()
The result from scenario 2 is as expected.
The result from scenario 1 on the other hand is very strange: the pixel is not completely white!! How come?
How can I fix this?
Thank you
This is the effect of anti-aliasing.
The pixel is not 100% covered by the line. Therefore the resulting color is interpolated between the pixel color before the drawing operation and the stroke color.
The following code allows you to observe the effect:
#Override
public void start(Stage primaryStage) {
Canvas canvas = new Canvas(800, 300);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.RED);
gc.fillRect(0, 0, 800, 300);
gc.setStroke(Color.WHITE);
final double dx = 0;
for (double x = 10, s = 1; x < (800 - 40); s++, x += 50) {
gc.setLineWidth(s);
gc.strokeLine(x + dx, 0, x + dx, 300);
}
WritableImage snap = canvas.snapshot(null, null);
PixelReader reader = snap.getPixelReader();
for (int x = 10; x < (800 - 40); x += 50) {
Color color = reader.getColor(x, 150);
System.out.printf("red=%-3d, green=%-3d, blue=%-3d\n", (int) (color.getRed() * 0xFF),
(int) (color.getGreen() * 0xFF), (int) (color.getBlue() * 0xFF));
}
primaryStage.setScene(new Scene(new StackPane(canvas)));
primaryStage.show();
}
The first line drawn covers the x interval of [10-lineWidth/2, 10+lineWidth/2] = [9.5, 10.5]. The column with index 10 is only half covered which is why the resulting color is ((255, 0, 0) + (255, 255, 255)) / 2 = (255, 127, 127) (already rounded to integral values)
Modify dx to cover the column completely and you get the stroke color:
final double dx = 0.5;
The pixel is covered completely by the rectangle you fill in your code and the color is set to the fill color for this reason. Add 0.5 to one of the starting coordinates of the rectangle you fill, and you'll observe a similar effect as with the stroke.

Increase the hitbox of Circle in JavaFX

I'm creating circles of size 5 with something like Circle c = new Circle(x, y, 5);. Then I do c.setOnMousePressed(mousePressedEventHandler); but I've got a problem here : my circle is too small and it's easy to miss it. I would like to keep this size so is there a way to increase the hitbox of a circle without doing something like creating an invisible circle bigger and then set the listner on it ?
Increase the hit area by adding a transparent stroke to the circles.
Note: To actually use hit boxes you need to set the pickOnBounds property to true.
private static Circle createCircle(double x, double y, double radius, double hitRadius) {
Circle circle = new Circle(x, y, radius, Color.BLACK);
circle.setStrokeType(StrokeType.OUTSIDE);
circle.setStroke(Color.TRANSPARENT);
circle.setStrokeWidth((hitRadius < radius) ? 0 : (hitRadius - radius));
return circle;
}
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Circle circle1 = createCircle(100, 100, 5, 20);
Circle circle2 = createCircle(150, 150, 5, 20);
circle1.setOnMouseClicked(evt -> System.out.println("clicked 1"));
circle2.setOnMouseClicked(evt -> System.out.println("clicked 2"));
root.getChildren().addAll(
circle1,
circle2
);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}

on Javafx, Is there any way to draw a regular polygon without knowing the coordinates of all points(but merely knowing the number of sides?)

So for example, to draw a regular dodecagon on Javafx, you have to know all the coordinates of 12 points. But I was wondering if there's any way to draw it without knowing the coordinates of the points(but merely knowing that there are 12 sides for that polygon) so that if the user types '6', then it draws a regular hexagon and if the user types '12', it draws a regular dodecagon.
No, it's impossible to draw a polygon without knowing the the coordinates of the corners. How would such a object be rendered without the rendering engine knowing the coordinates of the vertices it needs to render?
You can of course calculate the coordinates of the points of a regular polygon given a center point, the distance of the corners from the center and the angle of one of the corners from the center point by convertion polar coordinates to cartesian coordinates:
private static void setPolygonSides(Polygon polygon, double centerX, double centerY, double radius, int sides) {
polygon.getPoints().clear();
final double angleStep = Math.PI * 2 / sides;
double angle = 0; // assumes one point is located directly beneat the center point
for (int i = 0; i < sides; i++, angle += angleStep) {
polygon.getPoints().addAll(
Math.sin(angle) * radius + centerX, // x coordinate of the corner
Math.cos(angle) * radius + centerY // y coordinate of the corner
);
}
}
#Override
public void start(Stage primaryStage) {
Spinner<Integer> spinner = new Spinner(3, Integer.MAX_VALUE, 3);
Polygon polygon = new Polygon();
setPolygonSides(polygon, 200, 200, 150, spinner.getValue());
spinner.valueProperty().addListener((observable, oldValue, newValue) -> {
setPolygonSides(polygon, 200, 200, 150, spinner.getValue());
});
StackPane stackPane = new StackPane(polygon);
stackPane.setPrefSize(400, 400);
HBox root = new HBox(spinner, stackPane);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

Centering ImageView using JavaFX

I am trying to center an ImageView in a JavaFX GUI. However, the image just appears in the top left corner of the window.
My code:
FlowPane picPane = new FlowPane();
Image photo = new Image(url, 300, 300, false, false);
ImageView myView = new ImageView(photo);
picPane.getChildren().add(myView);
picPane.setColumnHalignment(HPos.CENTER);
The property "columnHalignment" is ignored for horizontal flowpanes (default settings).
Take a look at the HBox Layout. There you can specify the alignment properly.
EDIT: some code:
public HBox addHBox(URL url) {
HBox hbox = new HBox();
hbox.setPadding(new Insets(15, 12, 15, 12));
hbox.setSpacing(10);
hbox.setStyle("-fx-background-color: #336699;");
hbox.setAlignment(Pos.TOP_CENTER);
Image photo = new Image(url, 300, 300, false, false);
ImageView myView = new ImageView(photo);
hbox.getChildren().add(myView);
return hbox;
}
I am very sorry. I cannot test it at the moment. But it should work this way.
This is the simplest solution I found.
Image img = new Image( "file:"+ imgURL );
imgViewer.setImage(img);
centerImage(imgViewer); // this does the centering..
public static void centerImage(ImageView imgView) {
Image img = imgView.getImage();
if (img != null) {
double w = 0;
double h = 0;
double ratioX = imgView.getFitWidth() / img.getWidth();
double ratioY = imgView.getFitHeight() / img.getHeight();
double reducCoeff = 0;
if(ratioX >= ratioY) {
reducCoeff = ratioY;
} else {
reducCoeff = ratioX;
}
w = img.getWidth() * reducCoeff;
h = img.getHeight() * reducCoeff;
imgView.setX((imgView.getFitWidth() - w) / 2);
imgView.setY((imgView.getFitHeight() - h) / 2);
}
}
Just modified from this link's solution a little: Centering an image in an ImageView
Your Image object sits in myview,
myview sits in picPain.
picPain is centered, however its' contents are not centered in terms of "drawing" with the image holder Graphics object!
The component drawing is myview.
Second, the size of the container myview requires measuring on each side from the container picPain.
While containers have alignments of their internal contents "they have defaults for their sizing and position they draw from the top left corner".
Set them and calculate them!

LibGDX touch position coords error

So, I'm using LibGdx and trying to use their Rectangle class as a bounds for button pressing onn the touch screen. It works perfectly on a 1:1 scale, but when I put the game on my Phone (With a smaller screen) than the images get scaled and drawn properly, but the Rectangles don't. So I tried keeping my Rectangles at their normal scale, and "upscaling" the touch screen's XY Coords, but I guess I'm not doing that right, cause it doesn't work.
optionsMenu = new Vector<Rectangle>();
optionsMenu.add(new Rectangle(100 + (120 * 0), 100, 100, 100));
optionsMenu.add(new Rectangle(100 + (120 * 1), 100, 100, 100));
optionsMenu.add(new Rectangle(100 + (120 * 2), 100, 100, 100));
optionsMenu.add(new Rectangle(100 + (120 * 3), 100, 100, 100));
That's how i'm initalizing my bounding Rectangles.
This is how I'm initalizing my camera:
camera = new OrthographicCamera();
camera.setToOrtho(true, 800, 480);
This is how I'm drawing my buttons:
spriteBatch.draw(buttonImage, optionsMenu.get(2).bounds.getX(),
optionsMenu.get(2).bounds.getY(), optionsMenu.get(2).bounds.getWidth(),
optionsMenu.get(2).bounds.getHeight(), 0, 0, buttonImage.getWidth(),
buttonImage.getHeight(), false, true);
And this is how I do my touch screen logic:
public boolean tap(float x, float y, int count, int button) {
Vector3 temp = new Vector3(x, y, 0);
camera.unproject(temp);
float scalePos = (int) ((float) Gdx.graphics.getWidth()/800.0f);
temp.x = temp.x * scalePos;
temp.y = temp.y * scalePos;
if(optionsMenu.get(0).bounds.contains(temp.x, temp.y)){
//do sutff
}
else if(optionsMenu.get(1).bounds.contains(temp.x, temp.y)){
//do other stuff
}
return false;
}
A few days ago I ran into the same problem. This is what I did to solve it:
If you are using a viewport, then you should add that data to the camera.unproject call, to make sure that the viewport is being taken into account.
For example:
camera.unproject(lastTouch,viewport.x,viewport.y,viewport.width,viewport.height);
To debug the rectangle bounds and the touch position, I used this method to draw them into the screen:
private static ShapeRenderer debugShapeRenderer = new ShapeRenderer();
public static void showDebugBoundingBoxes(List<Rectangle> boundingBoxes) {
debugShapeRenderer.begin(ShapeType.Line); // make sure to end the spritebatch before you call this line
debugShapeRenderer.setColor(Color.BLUE);
for (Rectangle rect : boundingBoxes) {
debugShapeRenderer.rect(rect.x, rect.y, rect.width, rect.height);
}
debugShapeRenderer.end();
}

Categories

Resources