Issue
I need to scale or zoom my image, but when I try to scale it, it always has centered in top-left. How can I scale from the center or any other places based on my preference?
Link on the full project
@FXML
void plusButton(ActionEvent event) throws IOException {
double height = ImageView.getFitHeight();
double width = ImageView.getFitWidth();
ImageView.setFitHeight(height + 100);
ImageView.setFitWidth(width + 100);
//TODO
//Rectangle2D rectangle2D = new Rectangle2D(800, 800, 800, 800);
//ImageView.setViewport(rectangle2D);
}
Solution
The reason is that you are changing the entire width and height of ImageView
, which is the container of Image
. In order to zoom or scale your images in JavaFX
you need to work with ViewPort
of ImageView
s.
Whenever you zoom-in or zoom-out, it is necessary to set new ViewPort
.
imageView.setViewport(new Rectangle2D(minX, minY, width, height));
According to Oracle's documentation:
- Parameters:
minX
- The x coordinate of the upper-left corner of theRectangle2D
minY
- The y coordinate of the upper-left corner of theRectangle2D
width
- The width of theRectangle2D
height
- The height of theRectangle2D
I did not debug your code, but the following code can be very helpful. Try to tweak it and learn the concept then it is far easier to apply changes to your code.
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class PlutoExplorer extends Application {
private static final String IMAGE_CREDIT_URL = "http://www.nasa.gov/image-feature/global-mosaic-of-pluto-in-true-color";
private static final String IMAGE_URL = "https://www.nasa.gov/sites/default/files/thumbnails/image/global-mosaic-of-pluto-in-true-color.jpg";
private static final int MIN_PIXELS = 10;
@Override
public void start(Stage primaryStage) {
Image image = new Image(IMAGE_URL);
double width = image.getWidth();
double height = image.getHeight();
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
reset(imageView, width / 2, height / 2);
ObjectProperty<Point2D> mouseDown = new SimpleObjectProperty<>();
imageView.setOnMousePressed(e -> {
Point2D mousePress = imageViewToImage(imageView, new Point2D(e.getX(), e.getY()));
mouseDown.set(mousePress);
});
imageView.setOnMouseDragged(e -> {
Point2D dragPoint = imageViewToImage(imageView, new Point2D(e.getX(), e.getY()));
shift(imageView, dragPoint.subtract(mouseDown.get()));
mouseDown.set(imageViewToImage(imageView, new Point2D(e.getX(), e.getY())));
});
imageView.setOnScroll(e -> {
double delta = e.getDeltaY();
Rectangle2D viewport = imageView.getViewport();
double scale = clamp(Math.pow(1.01, delta),
// don't scale so we're zoomed in to fewer than MIN_PIXELS in any direction:
Math.min(MIN_PIXELS / viewport.getWidth(), MIN_PIXELS / viewport.getHeight()),
// don't scale so that we're bigger than image dimensions:
Math.max(width / viewport.getWidth(), height / viewport.getHeight())
);
Point2D mouse = imageViewToImage(imageView, new Point2D(e.getX(), e.getY()));
double newWidth = viewport.getWidth() * scale;
double newHeight = viewport.getHeight() * scale;
// To keep the visual point under the mouse from moving, we need
// (x - newViewportMinX) / (x - currentViewportMinX) = scale
// where x is the mouse X coordinate in the image
// solving this for newViewportMinX gives
// newViewportMinX = x - (x - currentViewportMinX) * scale
// we then clamp this value so the image never scrolls out
// of the imageview:
double newMinX = clamp(mouse.getX() - (mouse.getX() - viewport.getMinX()) * scale,
0, width - newWidth);
double newMinY = clamp(mouse.getY() - (mouse.getY() - viewport.getMinY()) * scale,
0, height - newHeight);
imageView.setViewport(new Rectangle2D(newMinX, newMinY, newWidth, newHeight));
});
imageView.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
reset(imageView, width, height);
}
});
Hyperlink link = new Hyperlink("Image Credit: NASA/JHUAPL/SwRI");
link.setOnAction(e -> getHostServices().showDocument(IMAGE_CREDIT_URL));
link.setPadding(new Insets(10));
link.setTooltip(new Tooltip(IMAGE_CREDIT_URL));
HBox buttons = createButtons(width, height, imageView);
Tooltip tooltip = new Tooltip("Scroll to zoom, drag to pan");
Tooltip.install(buttons, tooltip);
Pane container = new Pane(imageView);
container.setPrefSize(800, 600);
imageView.fitWidthProperty().bind(container.widthProperty());
imageView.fitHeightProperty().bind(container.heightProperty());
VBox root = new VBox(link, container, buttons);
root.setFillWidth(true);
VBox.setVgrow(container, Priority.ALWAYS);
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Pluto explorer");
primaryStage.show();
}
private HBox createButtons(double width, double height, ImageView imageView) {
Button reset = new Button("Reset");
reset.setOnAction(e -> reset(imageView, width / 2, height / 2));
Button full = new Button("Full view");
full.setOnAction(e -> reset(imageView, width, height));
HBox buttons = new HBox(10, reset, full);
buttons.setAlignment(Pos.CENTER);
buttons.setPadding(new Insets(10));
return buttons;
}
// reset to the top left:
private void reset(ImageView imageView, double width, double height) {
imageView.setViewport(new Rectangle2D(0, 0, width, height));
}
// shift the viewport of the imageView by the specified delta, clamping so
// the viewport does not move off the actual image:
private void shift(ImageView imageView, Point2D delta) {
Rectangle2D viewport = imageView.getViewport();
double width = imageView.getImage().getWidth() ;
double height = imageView.getImage().getHeight() ;
double maxX = width - viewport.getWidth();
double maxY = height - viewport.getHeight();
double minX = clamp(viewport.getMinX() - delta.getX(), 0, maxX);
double minY = clamp(viewport.getMinY() - delta.getY(), 0, maxY);
imageView.setViewport(new Rectangle2D(minX, minY, viewport.getWidth(), viewport.getHeight()));
}
private double clamp(double value, double min, double max) {
if (value < min)
return min;
if (value > max)
return max;
return value;
}
// convert mouse coordinates in the imageView to coordinates in the actual image:
private Point2D imageViewToImage(ImageView imageView, Point2D imageViewCoordinates) {
double xProportion = imageViewCoordinates.getX() / imageView.getBoundsInLocal().getWidth();
double yProportion = imageViewCoordinates.getY() / imageView.getBoundsInLocal().getHeight();
Rectangle2D viewport = imageView.getViewport();
return new Point2D(
viewport.getMinX() + xProportion * viewport.getWidth(),
viewport.getMinY() + yProportion * viewport.getHeight());
}
public static void main(String[] args) {
launch(args);
}
}
The above-mentioned code is imported from GitHub.
Answered By - Elyas Hadizadeh