I am trying to understand the imposition of text on the image.
The situation is as follows: there is a javafx application, the main window is marked up as a BorderPane with a size of 1280x800. All borderPane zones, with the exception of center, have fixed sizes. It turns out that when you maximize the window, the center area increases. When launched, this zone is approximately 975x740 px.
In the center zone itself there is a scrollPane, inside which there is a StackPane, with the following parameters (from css)
-fx-hbar-policy: never; -fx-vbar-policy: never; -fx-fit-to-width: true; -fx-fit-to-height: true; -fx-pannable: true; -fx-alignment: center;
Inside the StackPane is an ImageView, which displays various loadable images, centering the center zone and scaling implemented like this:
//центрирование stackPaneImageHolder.minWidthProperty().bind(Bindings.createDoubleBinding(() -> scrollPaneImageView.getViewportBounds().getWidth(), scrollPaneImageView.viewportBoundsProperty())); //масштабирование imageView.fitWidthProperty().bind(scrollPaneImageView.widthProperty()); imageView.fitHeightProperty().bind(scrollPaneImageView.heightProperty());
The question is: I would like to be able to write on the image displayed in the ImageView.
As I understand it - the issue is resolved via Canvas (javafx.scene.canvas). But there was a problem - I don’t understand how to connect the canvas with the loaded image:
For example - I uploaded an image with a resolution of 4000x3000px. I scaled it in ImageView to the same 975x740px. When I experiment with Canvas, it pushes me away from the size of an already scaled image, and if I try to give it the dimensions of the original image, either the test caption is not visible at all in ImageView, or I get vertical scrolling inside the imageView and not the fact that the caption is visible ( In the end, now they have completely removed from the canvas code, because there are already built up very scary constructions that do not work).
I need to figure out how to make a Canvas the size of a real image and a height of say 200 pixels (to create a caption on the photo below), and display the finished canvas with the text \ background on the scaled image so that when expanding / restoring the window size, it does not go somewhere then sideways, and was tied to a specific part of the image. With the subsequent saving of the image with this inscription in the original size.
Please tell me in which direction to dig and maybe I did not come from that side at all. Current code posted below
Thanks in advance for your reply.
mainWindowController
package imageSigner.controller; import imageSigner.MainApp; import imageSigner.model.FileItem; import imageSigner.storage.FileItemsStorage; import imageSigner.tools.FileOperations; import javafx.beans.binding.Bindings; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.TextField; import javafx.scene.image.*; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import java.io.File; public class mainWindowController { //даем контроллеру доступ к экземпляру mainApp private MainApp mainApp; //создаем лист для обработки файлов private ObservableList<FileItem> fileItemsList = FileItemsStorage.getInstance().getFileItemsList(); //переменная для хранения индекса выбранного файла private int currentFileIndex; //объявляем поля и элементы из FXML @FXML public BorderPane borderPaneMain; @FXML public ScrollPane scrollPaneImageView; @FXML public StackPane stackPaneImageHolder; @FXML public ImageView imageView; @FXML public Button buttonSelectFiles; @FXML public Button buttonPrevPhoto; @FXML public Button buttonNextPhoto; @FXML public Button buttonApplySignature; @FXML public Label labelQuantitySelectedFiles; @FXML public TextField textFieldCurrentFile; @FXML public TextField textFieldSignature; //initialize public void initialize() { refreshCounter(); //отслеживание изменения кол-ва выбранных файлов fileItemsList.addListener((ListChangeListener<FileItem>) c -> refreshCounter()); //отслеживание предпросмотра текущего файла (изменение текстового поля с именем файла) textFieldCurrentFile.textProperty().addListener( ((observable, oldValue, newValue) -> showPhotoPreview())); } //обновление счетчика private void refreshCounter() { FileItemsStorage.getInstance().refreshLabelCounter(labelQuantitySelectedFiles); } //передача файла с окна выбора файла в основное окно public void selectFileToTextField() { if (fileItemsList.size() != 0) { try { currentFileIndex = mainApp.getFwController().tableView.getSelectionModel().getSelectedIndex(); currentFileToTextFieldCF(); } catch (ArrayIndexOutOfBoundsException e) { currentFileIndex = 0; currentFileToTextFieldCF(); } } } //прописывает текущий фвйл в соответсвующее текстовое поле private void currentFileToTextFieldCF() { FileItem f = fileItemsList.get(currentFileIndex); getTextFieldCurrentFile().setText(f.getFilePath()); } //показывать превью фото private void showPhotoPreview() { if (fileItemsList.size() != 0) { Image img = getCurrentImage(); imageView.setImage(img); arrangeImageView(); } } //выравнивание и масштабирование изображения в превью private void arrangeImageView() { scrollPaneImageView.setContent(stackPaneImageHolder); //центрирование stackPaneImageHolder.minWidthProperty().bind(Bindings.createDoubleBinding(() -> scrollPaneImageView.getViewportBounds().getWidth(), scrollPaneImageView.viewportBoundsProperty())); //масштабирование imageView.fitWidthProperty().bind(scrollPaneImageView.widthProperty()); imageView.fitHeightProperty().bind(scrollPaneImageView.heightProperty()); } //TODO показ измененного фото ТЕСТ РИСОВАНИЯ БЕЛОЙ ПОЛОСЫ ПОД ФОТО private void showChangedPhoto(int signLineSize) { Image image = getCurrentImage(); // Создаем WritableImage WritableImage writableImage = new WritableImage( (int) image.getWidth(), (int) image.getHeight() + signLineSize); PixelReader pixelReader = image.getPixelReader(); PixelWriter pixelWriter = writableImage.getPixelWriter(); // Проходим все пиксели исходного изображения for (int readY = 0; readY < image.getHeight(); readY++ ) { for (int readX = 0; readX < image.getWidth(); readX++) { Color color = pixelReader.getColor(readX, readY); //считываем цвет пикселя с исходного изображения pixelWriter.setColor(readX, readY, color); //записываем цвет пикселя в writableImage } } // заполнение цветом пространства для подписи for (int rY = (int)image.getHeight() + 1; rY < writableImage.getHeight(); rY++) { for (int rX = 0; rX < image.getWidth(); rX++) { pixelWriter.setColor(rX, rY, Color.WHITE); } } imageView.setImage(writableImage); //отображение измененного изображения } /** buttons */ public void showSelectedFilesWindow() { mainApp.showFilesWindow(); } //переключает на предыдущее фото public void prevPhotoButton() { if (currentFileIndex > 0) { currentFileIndex --; currentFileToTextFieldCF(); //todo preview } } //переключает на следующее фото public void nextPhotoButton() { if (currentFileIndex < fileItemsList.size() - 1) { currentFileIndex++; currentFileToTextFieldCF(); } } //Применить подпись public void buttonApplySignature() { //создаем копию оригинального файла в специальной папке FileOperations.backupOriginal(currentFileIndex); //отображаем измененное фото в ImageView showChangedPhoto(200); //todo передаем пока временный параметр } /**Setters and getters */ public void setMainApp(MainApp mainApp) { this.mainApp = mainApp; } public TextField getTextFieldCurrentFile() { return textFieldCurrentFile; } public int getCurrentFileIndex() { return currentFileIndex; } public Image getCurrentImage() { Image img = new Image(new File(fileItemsList.get(currentFileIndex).getFilePath()).toURI().toString()); return img; } }
mainWindowView.fxml
<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import javafx.scene.control.*?> <?import javafx.scene.image.*?> <?import javafx.scene.layout.*?> <BorderPane fx:id="borderPaneMain" styleClass="BorderPaneMain" stylesheets="@css/mainWindow.css" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="imageSigner.controller.mainWindowController"> <top> <AnchorPane styleClass="panelTop"> <Label layoutX="8.0" layoutY="4.0" styleClass="fontHeaders1" text="Предпросмотр" /> <Label layoutX="985.0" layoutY="4.0" styleClass="fontHeaders1" text="Инструменты" /> </AnchorPane> </top> <bottom> <AnchorPane styleClass="panelBottom"> <TextField fx:id="textFieldSignature" layoutX="5.0" layoutY="56.0" styleClass="text-field-Signature" /> <Button fx:id="buttonApplySignature" layoutX="1072.0" layoutY="18.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#buttonApplySignature" prefHeight="100.0" prefWidth="190.0" styleClass="fontHeaders2_arial, fontHeaders2_bold" text="Применить подпись" /> </AnchorPane> </bottom> <right> <AnchorPane styleClass="panelTools"> <Label layoutX="3.0" layoutY="11.0" styleClass="fontHeaders2, fontHeaders2_bold" text="1. Выберите файл JPEG или папку" /> <Button fx:id="buttonSelectFiles" layoutX="13.0" layoutY="34.0" mnemonicParsing="false" onAction="#showSelectedFilesWindow" styleClass="button-selectFiles" text="Выбор фото" /> <Label fx:id="labelQuantitySelectedFiles" layoutX="220.0" layoutY="34.0" styleClass="fontHeaders2, fontHeaders2_arial" /> <TextField fx:id="textFieldCurrentFile" layoutX="13.0" layoutY="94.0" styleClass="text-field-currentFile" /> <Button fx:id="buttonPrevPhoto" layoutX="14.0" layoutY="136.0" mnemonicParsing="false" onAction="#prevPhotoButton" styleClass="buttons-prev-and-next" text="Предыдущее фото" /> <Label layoutX="20.0" layoutY="76.0" styleClass="fontHeaders2" text="Текущее выбранное фото" /> <Button fx:id="buttonNextPhoto" layoutX="152.0" layoutY="136.0" mnemonicParsing="false" onAction="#nextPhotoButton" styleClass="buttons-prev-and-next" text="Следующее фото" /> </AnchorPane> </right> <left> <AnchorPane styleClass="panelLeft" /> </left> <center> <ScrollPane fx:id="scrollPaneImageView" styleClass="panelImageScrollPane"> <StackPane fx:id="stackPaneImageHolder" styleClass="panelImageStackPane"> <ImageView fx:id="imageView" pickOnBounds="true" preserveRatio="true" styleClass="imageView" /> </StackPane> </ScrollPane> </center> </BorderPane>
mainWindow.css
.BorderPaneMain { -fx-pref-width: 1280px; -fx-pref-height: 800px; -fx-min-width: 1280px; -fx-min-height: 800px; } .panelTop, .panelTools, .panelBottom, .panelLeft { -fx-background-color: #A6ABB2; } .panelTop { -fx-pref-width: 1280px; -fx-pref-height: 25px; -fx-min-height: 25px; -fx-max-height: 25px; -fx-alignment: center; } .panelBottom { -fx-alignment: bottom-left; -fx-pref-width: 980px; -fx-pref-height: 135px; } .panelTools { -fx-alignment: top-right; -fx-pref-width: 300px; -fx-pref-height: 775px; } .panelLeft { -fx-alignment: center; -fx-pref-width: 5px; -fx-pref-height: 640px; } .panelImageScrollPane { -fx-hbar-policy: never; -fx-vbar-policy: never; -fx-fit-to-width: true; -fx-fit-to-height: true; -fx-pannable: true; -fx-alignment: center; } .panelImageStackPane { -fx-background-color: #404040; } .imageView { -fx-alignment: center; } .fontHeaders1 { -fx-font-family: Verdana; -fx-font-size: 14px; -fx-text-fill: #333333; } .fontHeaders2 { -fx-font-family: Verdana; -fx-font-size: 12px; -fx-text-fill: #171717; } .fontHeaders2_bold { -fx-font-weight: bold; } .fontHeaders2_arial { -fx-font-family: Arial; } .text-field-Signature { -fx-pref-height: 25px; -fx-pref-width: 975px; } .text-field-currentFile { -fx-pref-width: 275px; -fx-pref-height: 25px; } .button-selectFiles { -fx-pref-width: 180px; -fx-pref-height: 25px; -fx-font-family: Arial; -fx-font-size: 12px; -fx-text-fill: #171717; } .buttons-prev-and-next { -fx-pref-width: 135px; -fx-pref-height: 25px; -fx-font-family: Arial; -fx-font-size: 12px; -fx-text-fill: #171717; }