2
\$\begingroup\$

I am making my own JavaFX color picker just for learning more about JavaFX. I am rendering a color wheel in JavaFX using WritableImage and I have a renderImage method to render a color wheel in my program. I am also using Slider to change brightness of the color wheel.

The code is working fine but it's rendering slower. The other reason I asked this question is also to learn about fast rendering algorithms in Java because if I make something else like Mandelbrot set or if I want to zoom that canvas containing WritableImage then just using image as resource (as suggested in comment below) won't work.

How can I improve my code to render the color wheel faster?

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Slider;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import static java.lang.Math.atan2;
import static java.lang.Math.toDegrees;

public class Example extends Application{

    private static final int ANTIALIASING_BASE = 3;

    private WritableImage writableImage;
    private PixelWriter pixelWriter;
    private Canvas canvas;

    private IntegerProperty w;
    private IntegerProperty h;
    private Slider slider;

    public static void main(String... arguments){
        launch(arguments);
    }

    @Override
    public void start(Stage primaryStage){

        w = new SimpleIntegerProperty(255 + 42);
        h = new SimpleIntegerProperty(360);
        slider = new Slider(0, 100, 100);

        primaryStage.setWidth(w.get());
        primaryStage.setHeight(h.get());

        canvas = new Canvas(255, 255);
        writableImage = new WritableImage(255, 255);
        pixelWriter = writableImage.getPixelWriter();

        canvas.getGraphicsContext2D()
              .drawImage(writableImage, 0, 0, w.get(), h.get(), 0, 0, w.get(), h.get());

        renderImage();

        slider.valueProperty().addListener(this::change);

        VBox root = new VBox(12.5, canvas, slider);
        root.setPadding(new Insets(12.5));

        primaryStage.setScene(new Scene(root));
        primaryStage.setResizable(false);
        primaryStage.show();
    }

    //method to change brightness using slider
    private void change(Observable observable){
        writableImage = new WritableImage(255, 255);
        pixelWriter = writableImage.getPixelWriter();
        canvas.getGraphicsContext2D()
              .drawImage(writableImage, 0, 0, w.get(), h.get(), 0, 0, w.get(), h.get());

        renderImage();
    }

    //method that renders color wheel image
    private void renderImage(){
        for(int x = 0; x < 255; x++){
            for(int y = 0; y < 255; y++){
                Color c = getAntialiasedPixel(x, y);
                if(c.getRed() == 0.0 && c.getGreen() == 0.0 && c.getBlue() == 0.0
                        && slider.valueProperty().get() != 0.0){
                    c = Color.web("#f4f4f4");
                }
                pixelWriter.setColor(x, y, c);
            }
        }

    }

    //method that calculates color
    private Color getColor(double x, double y){

        double distance = getDistance(x, y);

        if(distance > 127.5){
            return Color.web("#f4f4f4");
        }

        double h = getAngle(x, y);
        double s = (distance / 127.5);
        double b = slider.valueProperty().get() / 100;

        return Color.hsb(h, s, b);
    }

    //method to make color wheel look smoother
    private Color getAntialiasedPixel(int x, int y){
        double step = 1d / ANTIALIASING_BASE;
        double N = ANTIALIASING_BASE * ANTIALIASING_BASE;
        double r = 0, g = 0, b = 0;

        for(int i = 0; i < ANTIALIASING_BASE; i++){
            for(int j = 0; j < ANTIALIASING_BASE; j++){
                Color c = getColor(x + step * (i + 0.5) - 0.5,
                                   y + step * (j + 0.5) - 0.5);
                r += c.getRed() / N;
                g += c.getGreen() / N;
                b += c.getBlue() / N;
            }
        }

        return new Color(clamp(r), clamp(g), clamp(b), 1);
    }

    private double clamp(double val){
        return val > 1 ? 1 : val < 0 ? 0 : val;
    }

    private double getDistance(double x1, double y1){
        var y = 127.5 - y1;
        var x = 127.5 - x1;
        return Math.sqrt(y * y + x * x);
    }

    private double getAngle(double x1, double y1){
        var angle = toDegrees(atan2(y1 - 127.5, x1 - 127.5));
        if(angle < 0) angle += 360;
        return angle;
    }

}
\$\endgroup\$
  • \$\begingroup\$ The solution (as in the JavaScript) is to create an image of the color wheel, and render that. One might create the colorwheel in java, but outside the project and store the image as resource in the project. \$\endgroup\$ – Joop Eggen Feb 1 at 15:07
  • \$\begingroup\$ Okay. but the reason I asked this question is also to learn about fast rendering algorithms in java because if I make something else like Mandelbrot set or If I want to zoom that canvas containing WritableImage then just using image as resource won't work. I hope you are understanding the problem. \$\endgroup\$ – pollux Feb 1 at 15:17
  • \$\begingroup\$ Interlaced JPEG can be hi-res and fast show a lower resolution smaller scaled image. The CSS support of FX might be interesting (gradients). Pixel oriented processing is indeed suboptimal. Will be interesting to see the answers. \$\endgroup\$ – Joop Eggen Feb 1 at 15:27

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.