Resizing ImageViews - imageview

I have a GridPane (4x5), all it's cells have as child an AnchorPane which cointains an ImageView. I need to resize the image so it fully cover the cell as soon as the gridPane (and thus it's cells) change size.
I managed to resize the image correctly when the size of the cell grows, but when the cell gets tinier the image doesn't resize back.
This leads into partially covering images of the confinant cells.
Can anyone explain what i'm doing wrong or give me the instruction to implement a proper resize?
This is my code:
ImageView image = new ImageView("/dice/" + draftList.get(i) + ".png");
AnchorPane pane = ((AnchorPane)(gridpane.getChildren().get(i)));
pane.getChildren().add(image);
fitToParent(image,pane);
//method in the same class
private void fitToParent(ImageView image, AnchorPane pane) {
image.fitWidthProperty().bind(pane.widthProperty());
image.fitHeightProperty().bind(pane.heightProperty());
}

You can try to use the setPreserveRatio(boolean) function of the ImageView class to true. This will allow you to keep the aspect ratio constant.
Eg:
ImageView iv = new ImageView(/*file path*/);
iv.setPreserveRatio(true);
Src: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/ImageView.html
Other than this you can also try to limit the resizable property to false or set the min width and height so that the image is not partially covered
Src: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/Region.html#resize-double-double-

Related

Get visible elements from AnchorPane

I'm developing a JavaFX application where the user is able to zoom and drag the elements (all contained in a AnchorPane). Some of those elements are simple lines and I need to have something like a ruler that has different parent to stick on the screen on the same position even if the user zooms or drags the mentioned AnchorPane. I got almost everything working but I have one problem, I need to know which lines from the AnchorPane are visible to the user (as if the user zooms and drags the AnchorPane, some of the lines are not visible anymore). Here's what I tried (not working...)
private List<Double> getVisibleVerticalLinesXCoordonate() {
List<Double> xCoordonatesOfVisibleVerticalLines = new ArrayList<>();
List<Node> visibleNodes = new ArrayList<>();
Bounds bounds = rulerParent.getBoundsInLocal();
Bounds paneBounds = rulerParent.localToScene(bounds);
for (Node n : gridVerticalLines) {
Bounds nodeBounds = n.getBoundsInParent();
if (paneBounds.intersects(nodeBounds)) {
visibleNodes.add(n);
}
}
for (Node node : visibleNodes) {
Bounds newBounds = getRelative(node);
xCoordonatesOfVisibleVerticalLines.add(newBounds.getMinX());
}
System.out.println(Arrays.asList(xCoordonatesOfVisibleVerticalLines));
return xCoordonatesOfVisibleVerticalLines;
}
private Bounds getRelative(Node node) {
return rulerParent.sceneToLocal(node.localToScene(node.getBoundsInLocal()));
}
So, the rulerParent is what is fixed on the screen (is not zooming or dragging at all). After I have the visible lines from the AnchorPane I get the x coordinates of the lines relative to rulerParent - so I can align the ruler lines with the lines in the AnchorPane.
The problem is that this is not returning the actual visible lines...
I don't need to be able to see the whole line to consider it visible, that's why I'm using intersect...if any part of a line is visible, I need it.
It's about how you handle the zoom and dragging actions try to use ViewPort instead of scaling as with ViewPort you can know the translation X and Y coordinates of the ViewPort which is the visible X and Y coordinates of the AnchorPane

JavaFX binding only applied after resizing the window

When I run the following code in the start method of my Main (JavaFX) class I get weird results. The window gets displayed but pane (with a green border) has a width of 0. It is supposed to have the same width as the container's height since I binded prefWidth to the height property.
Then, when I resize the window, the binding comes into effect and the pane becomes a square. Notice that if I maximize the window it also doesn't apply the bindings.
Thank you!
//Create a pane with a min width of 10 and a green border to be able to see it
Pane pane = new Pane();
pane.setStyle("-fx-border-color: green; -fx-border-width: 2");
//Bind the pane's preferred width to the pane's height
pane.prefWidthProperty().bind(pane.heightProperty());
//Put the pane in a vbox that does not fill the stage's width and make the pane grow in the vbox
VBox container = new VBox(pane);
container.setFillWidth(false);
VBox.setVgrow(pane, Priority.SOMETIMES);
//Show the vbox
primaryStage.setScene(new Scene(container, 600, 400));
primaryStage.show();
The problem you are running into here is that when the container is laid out, it has no reasonable information as to the order in which it should compute the width and the height of the pane. So essentially what happens is it computes the width, which (since it's empty), is zero; then computes the height (which fills the container, since you told the VBox to do that). After that, the prefWidth property is changed, but by then the actual width has already been set, so it's essentially too late. The next time a layout pass occurs, the new pref width is taken into account.
I haven't checked the actual layout code, but (since the default content bias is null) most likely the layout code for the vbox is going to do something equivalent to the following pseudocode:
protected void layoutChildren() {
// content bias is null:
double prefWidth = pane.prefWidth(-1);
double prefHeight = pane.prefHeight(-1);
// no fill width:
double paneWidth = Math.max(this.getWidth(), prefWidth);
// vgrow, so ignore preferred height and size to height of the vbox:
double paneHeight = this.getHeight();
pane.resizeRelocate(0, 0, paneWidth, paneHeight);
}
The last call actually causes the height of the pane to change, which then causes the prefWidth to change via the binding. Of course, that's too late for the current layout pass, which has already set the width based on the previous preferred width calculation.
Basically, relying on bindings to manage layout like this is not a reliable way of doing things, because you are changing properties (such as prefWidth in this example) during the layout pass, when it may be already too late to resize the component.
The reliable way to manage layout for a pane like this is to override the appropriate layout methods, which are invoked by the layout pass in order to size the component.
For this example, since the width depends on the height, you should return VERTICAL for the contentBias, and you should override computePrefWidth(double height) to return the height (so the width is set to the height):
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane() {
#Override
public Orientation getContentBias() {
return Orientation.VERTICAL ;
}
#Override
public double computePrefWidth(double height) {
return height ;
}
};
pane.setStyle("-fx-border-color: green; -fx-border-width: 2");
//Bind the pane's preferred width to the pane's height
// pane.prefWidthProperty().bind(pane.heightProperty());
//Put the pane in a vbox that does not fill the stage's width and make the pane grow in the vbox
VBox container = new VBox(pane);
container.setFillWidth(false);
VBox.setVgrow(pane, Priority.SOMETIMES);
//Show the vbox
primaryStage.setScene(new Scene(container, 600, 400));
primaryStage.show();
}

JavaFX Drag View for Transparent png image

I am trying to drag an Object which is represented as PNG image with transparent background from an AnchorPane to an HBox.
I set the image to the Drag View with this lines:
SliderItemHandler mh = (SliderItemHandler) event.getSource();
Dragboard db = mh.startDragAndDrop(TransferMode.COPY);
db.setDragView(mh.getModule().getImage());
ClipboardContent content = new ClipboardContent();
db.setContent(content);
It is all fine with nontransparent background images, but with the transparent ones the image got a white background with opacity "0.8" i think.
I tried taking a snapshot for the node:
db.setDragView(mh.snapshot(new SnapshotParameters(), null));
but it didn't work, the white background still there.
Is there any other way to make it transparent like the original image?
You have to change your snapshot parameters to transpartent fill:
SnapshotParameters sp = new SnapshotParameters();
sp.setFill(Color.TRANSPARENT);
db.setDragView(mh.snapshot(sp, null));
The result will be transparent without the white borders.
I'm not really sure how you achieve to get a white background, taking the opacity into account. However, I re-created your use case and will show you how I implemented this.
The following image is a Scene divided between an AnchorPane on the left, and a HBox on the right. The small transparent circle being the source ImageView to copy, the larger circle next to it being a dropped Image and far-most right being a circle currently being dragged. (Screenshot didn't include the cursor.)
As you can see, in none of the 3 scenarios there is a white (or almost white) background. It is just the image itself, with the image itself being a bit more transparent while dragging.
To achieve this, we'll take 2 variables into account. The source ImageView and the destination HBox.
#FXML
private HBox destination;
#FXML
private ImageView image;
We'd like the image to be dragged, so we add a DRAG_DETECTED event onto the ImageView.
image.addEventHandler(MouseEvent.DRAG_DETECTED, mouseEvent -> {
Dragboard db = image.startDragAndDrop(TransferMode.COPY);
ClipboardContent content = new ClipboardContent();
content.putImage(image.getImage());
db.setContent(content);
mouseEvent.consume();
});
Then we'd like the destination HBox to accept the dragged ImageView.
destination.addEventHandler(DragEvent.DRAG_OVER, (DragEvent event) -> {
if (event.getDragboard().hasImage()) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
event.consume();
});
And of course we'd like to place the ImageView into the HBox when actually dropped. In this case, it just places a copy of it into the HBox, but that's open for implementation of course.
destination.addEventHandler(DragEvent.DRAG_DROPPED, (DragEvent event) -> {
Dragboard db = event.getDragboard();
destination.getChildren().add(new ImageView(db.getImage()));
event.setDropCompleted(true);
event.consume();
});
That's all there is to dragging and dropping an image. No white backgrounds involved for transparent images. However, if you're able to create a MCVE, it might be easier to look into your problem if it still maintains.

JavaFX: maximum ImageView size

This may sound like a duplicate of Set maximum size for JavaFX ImageView but it's different.
I'd like to restrict the maximum size of an ImageView. Unfortunately, the only way to set the ImageView's size seems to be fitWidth and fitHeight which however enlarges the image if it's smaller than the fit values.
I tried setting fitWidth/fitHeight to 0/0 and wrap the ImageView into a pane with maxWidth set - no success (image is displayed in original size).
To me it seems as this is not achievable by JavaFX, however I can't believe it. But I couldn't find anything online. Are there any tricks/bugs/workarounds on this?
If you dont want the image to stretch, you can use:
setPreserveRatio(true);
Then you are able to fit width and height as you want
ImageView setPreserveRatio javadoc
In javadoc in this method there are the rules of scaling too
You just needed to add in if statement to set those larger than maxsize to a fixed size. The rest would remain their original size.
ImageView im = new ImageView();
im.setImage(image);
im.maxHeight(690 - 10);
int yoursize = 690;
if(im.maxHeight(690 - 10)> yoursize ){
im.setFitHeight(690- 10);
System.out.println("Fix Size : 690 ");
}
im.setPreserveRatio(true);
im.setSmooth(true);
im.setCache(true);
You can get the actual Image resource and query its width/height to use for the fit values:
Image image = imageView.getImage();
double nativeWidth = image.getWidth();
double nativeHeight = image.getHeight();

ImageView not resizing image

I want to use an ImageView to resize an image, however the image is not being resized:
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
imageView.setFitHeight(40);
System.out.println("imageview image width = " + imageView.getImage().getWidth());
System.out.println("imageview image height = " + imageView.getImage().getHeight());
The output is
imageview image width = 674.0
imageview image height = 888.0
However, the width should be 40. My ImageView is not attached to any scene and I also don't want to attach it, it shall only be used for image resizing. Is there any way to force the ImageView to resize its image, even though the ImageView is not attached to any scene? The reason I am using an ImageView for resizing is, that I want to resize an Image in RAM, without reading it again from the disk, please see this question for more details.
Thanks for any hint!
Using an ImageView for resizing seems to be very hacky.
A better approach is to convert your Image into a BufferedImage and do the resizing the old way. (JavaFx does not (yet) provide an internal way to resize memory images)
int width = 500; // desired size
int height = 400;
Image original = ...; // fx image
BufferedImage img = new BufferedImage(
(int)original.getWidth(),
(int)original.getHeight(),
BufferedImage.TYPE_INT_ARGB);
SwingFXUtils.fromFXImage(original, img);
BufferedImage rescaled = Scalr.rescaleImage(img, width, heigth); // the actual rescale
// convert back to FX image
WritableImage rescaledFX = new WritableImage(width, heigth);
SwingFXUtils.toFXImage(rescaled, rescaledFX);
Where as Scalr is a nice library for resizing images in native java. Obviously, you can use other/simpler methods of rescaling, but the image quality won't be that nice.

Resources