Search code examples
javaimage-processingjavafxline

draw a X letter above image


I'm writing for a university project a software in java - javafx but I have some problems in implementing a vote function graphically speaking.

I have a political party icon like this:

enter image description here

I want in java put a X letter above this icon, to obtain something like this:

enter image description here

My solution could be the possibility to draw one line and other line above the Image, but I don't know how to do.

package unimi.sysvotes.elettore;

import java.io.File;
import java.io.IOException;

import javafx.fxml.FXML;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;

public class VotoController {
    @FXML Pane listaUnoCandidatoUninominale;
    @FXML ImageView listaUnoSimboloUno;

    @FXML
    private void initialize() {
        File fileUno = new File("C:\\Users\\Administrator\\Desktop\\progettazione\\simboli\\popoloDellaLiberta.jpg");
        
        Image imageUno = new Image("file:///"+fileUno.getAbsolutePath());
        
        listaUnoSimboloUno.setImage(imageUno);

    }

    @FXML
    private void listaUnoSimboloUnoAction(MouseEvent me) throws IOException {
        System.out.println("Votato");

        /*
           How put Two line above ImageView popolo?
         */

        Line lineOne = new Line(10, 10, 80, 80);
        lineOne.setFill(null);
        lineOne.setStroke(Color.BLACK);
        lineOne.setStrokeWidth(2);

        Line lineTwo = new Line(80, 10, 10, 80);
        lineTwo.setFill(null);
        lineTwo.setStroke(Color.BLACK);
        lineTwo.setStrokeWidth(2);

    }
    

}

Solution

  • Here is an alternate solution based upon a Pane.

    A Pane is used rather than a Group so that the Pane can be styled with CSS. If that wasn't needed, then a Group could be used instead.

    The layering is accomplished implicitly by the painter's algorithm which JavaFX uses for rendering. This algorithm paints items added later in a list over the top of items added earlier in the list.

    The solution implements the ability to select and deselect images, which wasn't really the question asked, but seemed implied in your question text.

    The notion of a selectable image is similar to a ToggleButton or CheckBox. So, a ToggleButton could be used instead and styled as required using CSS. For instance, the X could be achieved via a svgpath background layer in CSS, that is how the in-built controls such as radio button work. However, I have not applied that approach here (it is a bit more sophisticated than what I present).

    The example extends from Pane, but, if you wish, you can use aggregation rather than extension.

    The images used are provided in the answer to this question:

    selectable images

    The X is added to the pane's children on top of the image with the following code.

    public SelectableImageView(Image image) {
        imageView = new ImageView(image);
        getChildren().add(imageView);
    
        selectionMark = createSelectionMark();
    
        // ... additional listener and initialization code here.
    }
    
    
    private Node[] createSelectionMark() {
        double w = imageView.getImage().getWidth();
        double h = imageView.getImage().getHeight();
    
        Line l1 = new Line(INSET, INSET, w - INSET, h - INSET);
        l1.setStrokeWidth(INSET * .75);
        l1.setStrokeLineCap(StrokeLineCap.ROUND);
        Line l2 = new Line(INSET, h - INSET, w - INSET, INSET);
        l2.setStrokeWidth(INSET * .75);
        l2.setStrokeLineCap(StrokeLineCap.ROUND);
    
        return new Node[] { l1, l2 };
    }
    
    private void addSelection() {
        getChildren().addAll(selectionMark);
    }
    

    Executable Example

    import javafx.application.Application;
    import javafx.beans.property.*;
    import javafx.geometry.Insets;
    import javafx.scene.*;
    import javafx.scene.image.*;
    import javafx.scene.layout.*;
    import javafx.scene.shape.*;
    import javafx.stage.Stage;
    
    import java.util.*;
    import java.util.stream.Collectors;
    
    public class SelectableImagesApp extends Application {
    
        private static final double PADDING = 20;
    
        @Override
        public void start(Stage stage) throws Exception {
            TilePane imageTiles = new TilePane(PADDING, PADDING);
            imageTiles.setPadding(
                    new Insets(PADDING)
            );
            imageTiles.getChildren().addAll(
                    createSelectableImages()
            );
            imageTiles.setPrefColumns(ImageNames.values().length);
    
            stage.setScene(new Scene(imageTiles));
            stage.show();
        }
    
        public enum ImageNames {
            Medusa,
            Dragon,
            Treant,
            Unicorn
        }
    
        private List<SelectableImageView> createSelectableImages() {
            return Arrays.stream(ImageNames.values())
                    .map(m ->
                            new SelectableImageView(
                                    new Image(
                                            Objects.requireNonNull(
                                                    SelectableImagesApp.class.getResource(
                                                            m + "-icon.png"
                                                    )
                                            ).toExternalForm()
                                    )
                            )
                    ).collect(Collectors.toList());
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    class SelectableImageView extends Pane {
        private final ImageView imageView;
    
        private final Node[] selectionMark;
    
        private BooleanProperty selected = new SimpleBooleanProperty(false);
    
        private final double INSET = 10;
    
        public SelectableImageView(Image image) {
            imageView = new ImageView(image);
            getChildren().add(imageView);
    
            selectionMark = createSelectionMark();
    
            selected.addListener((observable, wasSelected, isSelected) -> {
                if (isSelected) {
                    addSelection();
                } else {
                    removeSelection();
                }
            });
    
            this.setStyle("-fx-background-color: lightblue;");
            this.setPickOnBounds(true);
            this.setOnMouseClicked(e -> {
                selected.set(!selected.get());
            });
        }
    
        private Node[] createSelectionMark() {
            double w = imageView.getImage().getWidth();
            double h = imageView.getImage().getHeight();
    
            Line l1 = new Line(INSET, INSET, w - INSET, h - INSET);
            l1.setStrokeWidth(INSET * .75);
            l1.setStrokeLineCap(StrokeLineCap.ROUND);
            Line l2 = new Line(INSET, h - INSET, w - INSET, INSET);
            l2.setStrokeWidth(INSET * .75);
            l2.setStrokeLineCap(StrokeLineCap.ROUND);
    
            return new Node[] { l1, l2 };
        }
    
        private void addSelection() {
            getChildren().addAll(selectionMark);
        }
    
        private void removeSelection() {
            getChildren().removeAll(selectionMark);
        }
    
        public boolean isSelected() {
            return selected.get();
        }
    
        public BooleanProperty selectedProperty() {
            return selected;
        }
    
        public void setSelected(boolean selected) {
            this.selected.set(selected);
        }
    }