javafximagefilter

Desaturation effect washes away contrast


I have a pretty specific problem with javaFx's ColorAdjust effect, I'm trying to apply a grayscale filter on an image, I'm using a ColorAdjust effect and setting the saturation Here is a reproducible example of what I'm trying to do

public class App extends Application {
    @Override
    public void start(Stage ps) {
        Pane root = new Pane();
        root.setMinSize(300, 300);
        
        root.setStyle("-fx-background-color: #40444b;");
        
        ImageView view = new ImageView(new Image("https://res.cloudinary.com/mesa-clone/image/upload/v1642936429/1f914_tydc44.png"));
        view.setTranslateX(5);
        view.setTranslateY(5);
        view.setEffect(new ColorAdjust(0, -1, 0, 0));
        
        root.getChildren().add(view);
        
        ps.setScene(new Scene(root));

        ps.show();
    }
}

now this piece of code does exactly what it's supposed to do, but I'm not satisfied with the result, I want a grayscale filter that behaves similarly to the web css grayscale filter, which produces much better results for my use case :

<html>

<body style="background-color: #40444b;">
    <img src="https://res.cloudinary.com/mesa-clone/image/upload/v1642936429/1f914_tydc44.png" style="filter: grayscale(100);">
</body>

</html>

javafx grayscale web grayscale

[ Left is javafx, Right is Web (firefox) ]

I know the difference isn't a lot but it's crucial for my use case and I would appreciate if anyone has better ideas to get similar results to the web version of the grayscale filter


Solution

  • manually converting the image to grayscale using a WritableImage and Color.grayscale() gives better results but it would complicate the process of switching between color and grayscale :

    public class App extends Application {
    
        @Override
        public void start(Stage ps) {
            Pane root = new Pane();
            root.setMinSize(300, 300);
    
            root.setStyle("-fx-background-color: #40444b;");
    
            Image image = new Image("https://res.cloudinary.com/mesa-clone/image/upload/v1642936429/1f914_tydc44.png");
            
            ImageView view = new ImageView(grayScale(image));
            
            view.setTranslateX(5);
            view.setTranslateY(5);
    
            root.getChildren().add(view);
    
            ps.setScene(new Scene(root));
            ps.setTitle("javafx grayscale test");
            ps.show();
        }
    
        private static Image grayScale(Image img) {
            WritableImage res = new WritableImage((int) img.getWidth(), (int) img.getHeight());
    
            PixelReader pr = img.getPixelReader();
            PixelWriter pw = res.getPixelWriter();
            for (int y = 0; y < img.getHeight(); y++) {
                for (int x = 0; x < img.getWidth(); x++) {
                    pw.setColor(x, y, pr.getColor(x, y).grayscale());
                }
            }
            return res;
        }
    }
    

    enter image description here

    you have the choice of saving the filtered image or generating it every time, depending on whether that trade-off (increased memory usage for increased performance) is worthwhile.