Image Viewer in Java
-------------- ImageViewer.java ---------------- import java.util.*; import java.io.*; import javafx.application.*; import javafx.stage.*; import javafx.scene.*; import javafx.scene.control.*; import javafx.scene.layout.*; import javafx.scene.canvas.*; import javafx.scene.paint.*; import javafx.scene.text.*; import javafx.scene.image.*; import javafx.scene.input.*; import javafx.geometry.*; import javafx.beans.*; import javafx.embed.swing.SwingFXUtils; import javax.imageio.ImageIO; public class ImageViewer extends Application { final Paint imageBackground = Color.GOLD; final double scrollScaleDelta = 1.1; final double keyScaleDelta = 1.2; final KeyCombination kcZoomIn = new KeyCodeCombination(KeyCode.ADD); final KeyCombination kcZoomOut = new KeyCodeCombination(KeyCode.SUBTRACT); final KeyCombination kcZoomToFit = new KeyCodeCombination(KeyCode.MULTIPLY); final KeyCombination kcActualSize = new KeyCodeCombination(KeyCode.DIVIDE); final int ACTUAL = 0; final int SHRINK = 1; final int ZOOM = 2; final int imageSize = SHRINK; File[] listFiles; int fileId = 0; double lastX, lastY; double scale = 1; ImageView imageView; ScrollPane scrollPane; Pane pane; @Override public void start(Stage stage) { BorderPane layout = new BorderPane(); stage.setScene(new Scene(layout, 600, 400)); imageView = new ImageView(); pane = new Pane(imageView); pane.setBackground(new Background(new BackgroundFill(imageBackground, null, null))); scrollPane = new ScrollPane(pane); InvalidationListener il = obs -> update(scale, null); scrollPane.widthProperty().addListener(il); scrollPane.heightProperty().addListener(il); pane.setOnMousePressed(me -> { lastX = me.getX(); lastY = me.getY(); }); pane.setOnMouseDragged(me -> { double dw = pane.getWidth() - scrollPane.getViewportBounds().getWidth(); double dh = pane.getHeight() - scrollPane.getViewportBounds().getHeight(); double vw = scrollPane.getHvalue() - (me.getX() - lastX) / dw; double vh = scrollPane.getVvalue() - (me.getY() - lastY) / dh; scrollPane.setHvalue(Math.max(0, Math.min(vw, 1))); scrollPane.setVvalue(Math.max(0, Math.min(vh, 1))); }); pane.setOnDragDetected(me -> { pane.setCursor(Cursor.CLOSED_HAND); }); pane.addEventFilter(MouseEvent.MOUSE_RELEASED, me -> { pane.setCursor(Cursor.DEFAULT); }); scrollPane.addEventFilter(ScrollEvent.SCROLL, se -> { if (se.getDeltaY() != 0) { double sc = (se.getDeltaY() < 0 ? scale * scrollScaleDelta : scale / scrollScaleDelta); update(sc, se.getPickResult().getIntersectedPoint()); se.consume(); } }); scrollPane.addEventFilter(KeyEvent.KEY_PRESSED, ke -> { if (ke.getCode() == KeyCode.PAGE_UP) image(fileId - 1); else if (ke.getCode() == KeyCode.PAGE_DOWN) image(fileId + 1); else if (ke.getCode() == KeyCode.HOME) image(0); else if (ke.getCode() == KeyCode.END) image(listFiles.length - 1); else if (kcZoomIn.match(ke)) update(scale * keyScaleDelta, null); else if (kcZoomOut.match(ke)) update(scale / keyScaleDelta, null); else if (kcZoomToFit.match(ke)) update(scale(SHRINK), null); else if (kcActualSize.match(ke)) update(scale(ACTUAL), null); //else if (ke.getCode() == KeyCode.F5) update(scale(ZOOM), null); else return; ke.consume(); }); //scrollPane.setStyle("-fx-background: rgb(212,208,200);"); layout.setCenter(scrollPane); String path = getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); //if (path.endsWith(".jar")) path = path.substring(0, path.lastIndexOf("/") + 1); listFiles = createListFiles(path); if (listFiles.length == 0) listFiles = createDemo(path); stage.show(); scrollPane.requestFocus(); Platform.runLater(() -> { image(0); }); } void update(double newScale, Point3D point) { if (imageView.getImage() == null) return; double newWidth = imageView.getImage().getWidth() * newScale; double newHeight = imageView.getImage().getHeight() * newScale; if (Math.min(newWidth, newHeight) < 1 || Math.max(newWidth, newHeight) > 20_000) newScale = scale; double vx = scrollPane.getHvalue() * (pane.getWidth() - scrollPane.getViewportBounds().getWidth()); double vy = scrollPane.getVvalue() * (pane.getHeight() - scrollPane.getViewportBounds().getHeight()); if (point == null) point = new Point3D( vx + scrollPane.getViewportBounds().getWidth() / 2, vy + scrollPane.getViewportBounds().getHeight() / 2, 0); double k = newScale / scale - 1; scale = newScale; imageView.setFitWidth(imageView.getImage().getWidth() * scale); imageView.setFitHeight(imageView.getImage().getHeight() * scale); scrollPane.layout(); double dw = pane.getWidth() - scrollPane.getViewportBounds().getWidth(); double dh = pane.getHeight() - scrollPane.getViewportBounds().getHeight(); if (dw > 0) scrollPane.setHvalue(Math.max(0, Math.min((vx + point.getX() * k) / dw, 1))); if (dh > 0) scrollPane.setVvalue(Math.max(0, Math.min((vy + point.getY() * k) / dh, 1))); //dw = pane.getWidth() - scrollPane.getWidth() - scrollPane.getPadding().getLeft() - scrollPane.getPadding().getRight(); //dh = pane.getHeight() - scrollPane.getHeight() - scrollPane.getPadding().getTop() - scrollPane.getPadding().getBottom(); pane.setTranslateX(dw >= 0 ? 0 : -dw / 2); pane.setTranslateY(dh >= 0 ? 0 : -dh / 2); } double scale(int v) { if (v == ACTUAL || imageView.getImage() == null) return 1; double width = scrollPane.getWidth() - scrollPane.getPadding().getLeft() - scrollPane.getPadding().getRight(); double height = scrollPane.getHeight() - scrollPane.getPadding().getTop() - scrollPane.getPadding().getBottom(); double scaleWidth = width / imageView.getImage().getWidth(); double scaleHeight = height / imageView.getImage().getHeight(); double sc = Math.min(scaleWidth, scaleHeight); if (v == SHRINK) sc = Math.min(1, sc); return sc; } void image(int newFileId) { if (listFiles != null && newFileId >= 0 && newFileId < listFiles.length) { fileId = newFileId; imageView.setImage(new Image(listFiles[fileId].toURI().toString())); scrollPane.setHvalue(0); scrollPane.setVvalue(0); update(scale(imageSize), Point3D.ZERO); } } File[] createListFiles(String path) { final String[] extensions = new String[] {".bmp", ".gif", ".jpeg", ".jpg", ".png"}; File[] files = new File(path).listFiles((dir, name) -> { for (String extension : extensions) { if (name.toLowerCase().endsWith(extension)) return true; } return false; }); //if (files == null) return new File[0]; Arrays.sort(files, (f1, f2) -> f1.getName().compareToIgnoreCase(f2.getName())); return files; } File[] createDemo(String path) { final int[][] mm = new int[][] {{3, 3}, {13, 13}, {13, 3}, {3, 13}}; final int sz = 100; final int s = 10; SnapshotParameters sp = new SnapshotParameters(); sp.setFill(Color.TRANSPARENT); Canvas canvas = new Canvas(); GraphicsContext gc = canvas.getGraphicsContext2D(); gc.setTextBaseline(VPos.CENTER); gc.setTextAlign(TextAlignment.CENTER); gc.setFill(Color.web("808080")); for (int[] m : mm) { int nx = m[0]; int ny = m[1]; canvas.setWidth(nx * sz + s); canvas.setHeight(ny * sz + s); gc.setFont(Font.font((sz - s) * .3)); gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); for (int y = 0; y < ny; y++) { gc.fillRect(0, y * sz, sz * nx, s); for (int x = 0; x < nx; x++) { if (y == 0) gc.fillRect(x * sz, 0, s, sz * ny); gc.fillText(x + "," + y, x * sz + s + (sz - s) / 2, y * sz + s + (sz - s) / 2); } } gc.fillRect(0, ny * sz, sz * nx + s, s); gc.fillRect(nx * sz, 0, s, sz * ny); File file = new File(path, "image" + nx + "x" + ny + ".png"); WritableImage snapshot = canvas.snapshot(sp, null); try { ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", file); } catch (Exception e) { e.printStackTrace(); } } return createListFiles(path); } }