All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jfoenix.skins.JFXColorPickerUI Maven / Gradle / Ivy

There is a newer version: 9.0.10
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.jfoenix.skins;

import com.jfoenix.effects.JFXDepthManager;
import com.jfoenix.transitions.CachedTransition;
import javafx.animation.Animation.Status;
import javafx.animation.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Path;
import javafx.util.Duration;


/**
 * @author Shadi Shaheen & Bassel El Mabsout this UI allows the user to pick a color using HSL color system
 */
class JFXColorPickerUI extends Pane {

    private CachedTransition selectorTransition;
    private int pickerSize = 400;
    // sl circle selector size
    private int selectorSize = 20;
    private double centerX;
    private double centerY;
    private double huesRadius;
    private double slRadius;
    private double currentHue = 0;

    private ImageView huesCircleView;
    private ImageView slCircleView;
    private Pane colorSelector;
    private Pane selector;
    private CurveTransition colorsTransition;

    public JFXColorPickerUI(int pickerSize) {

        JFXDepthManager.setDepth(this, 1);

        this.pickerSize = pickerSize;
        this.centerX = (double) pickerSize / 2;
        this.centerY = (double) pickerSize / 2;
        final double pickerRadius = (double) pickerSize / 2;
        this.huesRadius = pickerRadius * 0.9;
        final double huesSmallR = pickerRadius * 0.8;
        final double huesLargeR = pickerRadius;
        this.slRadius = pickerRadius * 0.7;

        // Create Hues Circle
        huesCircleView = new ImageView(getHuesCircle(pickerSize, pickerSize));
        // clip to smooth the edges
        Circle outterCircle = new Circle(centerX, centerY, huesLargeR - 2);
        Circle innterCircle = new Circle(centerX, centerY, huesSmallR + 2);
        huesCircleView.setClip(Path.subtract(outterCircle, innterCircle));
        this.getChildren().add(huesCircleView);

        // create Hues Circle Selector
        Circle r1 = new Circle(pickerRadius - huesSmallR);
        Circle r2 = new Circle(pickerRadius - huesRadius);
        colorSelector = new Pane();
        colorSelector.setStyle(
            "-fx-border-color:#424242; -fx-border-width:1px; -fx-background-color:rgba(255, 255, 255, 0.87);");
        colorSelector.setPrefSize(pickerRadius - huesSmallR, pickerRadius - huesSmallR);
        colorSelector.setShape(Path.subtract(r1, r2));
        colorSelector.setCache(true);
        colorSelector.setMouseTransparent(true);
        colorSelector.setPickOnBounds(false);
        this.getChildren().add(colorSelector);

        // add Hues Selection Listeners
        huesCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event) -> {
            if (colorsTransition != null) {
                colorsTransition.stop();
            }
            double dx = event.getX() - centerX;
            double dy = event.getY() - centerY;
            double theta = Math.atan2(dy, dx);
            double x = centerX + huesRadius * Math.cos(theta);
            double y = centerY + huesRadius * Math.sin(theta);
            colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(dy, dx)));
            colorSelector.setTranslateX(x - colorSelector.getPrefWidth() / 2);
            colorSelector.setTranslateY(y - colorSelector.getPrefHeight() / 2);
        });
        huesCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
            double dx = event.getX() - centerX;
            double dy = event.getY() - centerY;
            double theta = Math.atan2(dy, dx);
            double x = centerX + huesRadius * Math.cos(theta);
            double y = centerY + huesRadius * Math.sin(theta);
            colorsTransition = new CurveTransition(new Point2D(colorSelector.getTranslateX() + colorSelector.getPrefWidth() / 2,
                colorSelector.getTranslateY() + colorSelector.getPrefHeight() / 2),
                new Point2D(x, y));
            colorsTransition.play();
        });
        colorSelector.translateXProperty()
            .addListener((o, oldVal, newVal) -> updateHSLCircleColor((int) (newVal.intValue() + colorSelector.getPrefWidth() / 2),
                (int) (colorSelector.getTranslateY() + colorSelector
                    .getPrefHeight() / 2)));
        colorSelector.translateYProperty()
            .addListener((o, oldVal, newVal) -> updateHSLCircleColor((int) (colorSelector.getTranslateX() + colorSelector
                .getPrefWidth() / 2), (int) (newVal.intValue() + colorSelector.getPrefHeight() / 2)));


        // Create SL Circle
        slCircleView = new ImageView(getSLCricle(pickerSize, pickerSize));
        slCircleView.setClip(new Circle(centerX, centerY, slRadius - 2));
        slCircleView.setPickOnBounds(false);
        this.getChildren().add(slCircleView);

        // create SL Circle Selector
        selector = new Pane();
        Circle c1 = new Circle(selectorSize / 2);
        Circle c2 = new Circle((selectorSize / 2) * 0.5);
        selector.setShape(Path.subtract(c1, c2));
        selector.setStyle(
            "-fx-border-color:#424242; -fx-border-width:1px;-fx-background-color:rgba(255, 255, 255, 0.87);");
        selector.setPrefSize(selectorSize, selectorSize);
        selector.setMinSize(selectorSize, selectorSize);
        selector.setMaxSize(selectorSize, selectorSize);
        selector.setCache(true);
        selector.setMouseTransparent(true);
        this.getChildren().add(selector);


        // add SL selection Listeners
        slCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event) -> {
            if (selectorTransition != null) {
                selectorTransition.stop();
            }
            if (Math.pow(event.getX() - centerX, 2) + Math.pow(event.getY() - centerY, 2) < Math.pow(slRadius - 2, 2)) {
                selector.setTranslateX(event.getX() - selector.getPrefWidth() / 2);
                selector.setTranslateY(event.getY() - selector.getPrefHeight() / 2);
            } else {
                double dx = event.getX() - centerX;
                double dy = event.getY() - centerY;
                double theta = Math.atan2(dy, dx);
                double x = centerX + (slRadius - 2) * Math.cos(theta);
                double y = centerY + (slRadius - 2) * Math.sin(theta);
                selector.setTranslateX(x - selector.getPrefWidth() / 2);
                selector.setTranslateY(y - selector.getPrefHeight() / 2);
            }
        });
        slCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
            selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000),
                new KeyValue(selector.translateXProperty(),
                    event.getX() - selector
                        .getPrefWidth() / 2,
                    Interpolator.EASE_BOTH),
                new KeyValue(selector.translateYProperty(),
                    event.getY() - selector
                        .getPrefHeight() / 2,
                    Interpolator.EASE_BOTH)))) {{
                setCycleDuration(Duration.millis(160));
                setDelay(Duration.seconds(0));
            }};
            selectorTransition.play();
        });
        // add slCircleView listener
        selector.translateXProperty()
            .addListener((o, oldVal, newVal) -> setColorAtLocation(newVal.intValue() + selectorSize / 2,
                (int) selector.getTranslateY() + selectorSize / 2));
        selector.translateYProperty()
            .addListener((o, oldVal, newVal) -> setColorAtLocation((int) selector.getTranslateX() + selectorSize / 2,
                newVal.intValue() + selectorSize / 2));


        // initial color selection
        double dx = 20 - centerX;
        double dy = 20 - centerY;
        double theta = Math.atan2(dy, dx);
        double x = centerX + huesRadius * Math.cos(theta);
        double y = centerY + huesRadius * Math.sin(theta);
        colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(dy, dx)));
        colorSelector.setTranslateX(x - colorSelector.getPrefWidth() / 2);
        colorSelector.setTranslateY(y - colorSelector.getPrefHeight() / 2);
        selector.setTranslateX(centerX - selector.getPrefWidth() / 2);
        selector.setTranslateY(centerY - selector.getPrefHeight() / 2);
    }

    /**
     * List of Color Nodes that needs to be updated when picking a color
     */
    private ObservableList colorNodes = FXCollections.observableArrayList();

    public void addColorSelectionNode(Node... nodes) {
        colorNodes.addAll(nodes);
    }

    public void removeColorSelectionNode(Node... nodes) {
        colorNodes.removeAll(nodes);
    }

    private void updateHSLCircleColor(int x, int y) {
        // transform color to HSL space
        Color color = huesCircleView.getImage().getPixelReader().getColor(x, y);
        double max = Math.max(color.getRed(), Math.max(color.getGreen(), color.getBlue()));
        double min = Math.min(color.getRed(), Math.min(color.getGreen(), color.getBlue()));
        double hue = 0;
        if (max != min) {
            double d = max - min;
            if (max == color.getRed()) {
                hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0);
            } else if (max == color.getGreen()) {
                hue = (color.getBlue() - color.getRed()) / d + 2;
            } else if (max == color.getBlue()) {
                hue = (color.getRed() - color.getGreen()) / d + 4;
            }
            hue /= 6;
        }
        currentHue = map(hue, 0, 1, 0, 255);

        // refresh the HSL circle
        refreshHSLCircle();
    }

    private void refreshHSLCircle() {
        ColorAdjust colorAdjust = new ColorAdjust();
        colorAdjust.setHue(map(currentHue + (currentHue < 127.5 ? 1 : -1) * 127.5, 0, 255, -1, 1));
        slCircleView.setEffect(colorAdjust);
        setColorAtLocation((int) selector.getTranslateX() + selectorSize / 2,
            (int) selector.getTranslateY() + selectorSize / 2);
    }


    /**
     * this method is used to move selectors to a certain color
     */
    private boolean allowColorChange = true;
    private ParallelTransition pTrans;

    public void moveToColor(Color color) {
        allowColorChange = false;
        double max = Math.max(color.getRed(),
            Math.max(color.getGreen(), color.getBlue())), min = Math.min(color.getRed(),
            Math.min(color.getGreen(),
                color.getBlue()));
        double hue = 0;
        double l = (max + min) / 2;
        double s = 0;
        if (max == min) {
            hue = s = 0; // achromatic
        } else {
            double d = max - min;
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            if (max == color.getRed()) {
                hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0);
            } else if (max == color.getGreen()) {
                hue = (color.getBlue() - color.getRed()) / d + 2;
            } else if (max == color.getBlue()) {
                hue = (color.getRed() - color.getGreen()) / d + 4;
            }
            hue /= 6;
        }
        currentHue = map(hue, 0, 1, 0, 255);

        // Animate Hue
        double theta = map(currentHue, 0, 255, -Math.PI, Math.PI);
        double x = centerX + huesRadius * Math.cos(theta);
        double y = centerY + huesRadius * Math.sin(theta);
        colorsTransition = new CurveTransition(new Point2D(colorSelector.getTranslateX() + colorSelector.getPrefWidth() / 2,
            colorSelector.getTranslateY() + colorSelector.getPrefHeight() / 2),
            new Point2D(x, y));


        // Animate SL
        s = map(s, 0, 1, 0, 255);
        l = map(l, 0, 1, 0, 255);
        Point2D point = getPointFromSL((int) s, (int) l, slRadius);
        double pX = centerX - point.getX();
        double pY = centerY - point.getY();

        double endPointX;
        double endPointY;
        if (Math.pow(pX - centerX, 2) + Math.pow(pY - centerY, 2) < Math.pow(slRadius - 2, 2)) {
            endPointX = pX - selector.getPrefWidth() / 2;
            endPointY = pY - selector.getPrefHeight() / 2;
        } else {
            double dx = pX - centerX;
            double dy = pY - centerY;
            theta = Math.atan2(dy, dx);
            x = centerX + (slRadius - 2) * Math.cos(theta);
            y = centerY + (slRadius - 2) * Math.sin(theta);
            endPointX = x - selector.getPrefWidth() / 2;
            endPointY = y - selector.getPrefHeight() / 2;
        }
        selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000),
            new KeyValue(selector.translateXProperty(),
                endPointX,
                Interpolator.EASE_BOTH),
            new KeyValue(selector.translateYProperty(),
                endPointY,
                Interpolator.EASE_BOTH)))) {{
            setCycleDuration(Duration.millis(160));
            setDelay(Duration.seconds(0));
        }};

        if (pTrans != null) {
            pTrans.stop();
        }
        pTrans = new ParallelTransition(colorsTransition, selectorTransition);
        pTrans.setOnFinished((finish) -> {
            if (pTrans.getStatus() == Status.STOPPED) {
                allowColorChange = true;
            }
        });
        pTrans.play();

        refreshHSLCircle();
    }

    private void setColorAtLocation(int x, int y) {
        if (allowColorChange) {
            Color color = getColorAtLocation(x, y);
            String colorString = "rgb(" + color.getRed() * 255 + "," + color.getGreen() * 255 + "," + color.getBlue() * 255 + ");";
            for (Node node : colorNodes)
                node.setStyle("-fx-background-color:" + colorString + "; -fx-fill:" + colorString+";");
        }
    }

    private Color getColorAtLocation(double x, double y) {
        double dy = x - centerX;
        double dx = y - centerY;
        return getColor(dx, dy);
    }

    private Image getHuesCircle(int width, int height) {
        WritableImage raster = new WritableImage(width, height);
        PixelWriter pixelWriter = raster.getPixelWriter();
        Point2D center = new Point2D((double) width / 2, (double) height / 2);
        double rsmall = 0.8 * width / 2;
        double rbig = (double) width / 2;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                double dx = x - center.getX();
                double dy = y - center.getY();
                double distance = Math.sqrt((dx * dx) + (dy * dy));
                double o = Math.atan2(dy, dx);
                if (distance > rsmall && distance < rbig) {
                    double H = map(o, -Math.PI, Math.PI, 0, 255);
                    double S = 255;
                    double L = 152;
                    pixelWriter.setColor(x, y, HSL2RGB(H, S, L));
                }
            }
        }
        return raster;
    }

    private Image getSLCricle(int width, int height) {
        WritableImage raster = new WritableImage(width, height);
        PixelWriter pixelWriter = raster.getPixelWriter();
        Point2D center = new Point2D((double) width / 2, (double) height / 2);
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                double dy = x - center.getX();
                double dx = y - center.getY();
                pixelWriter.setColor(x, y, getColor(dx, dy));
            }
        }
        return raster;
    }

    private double clamp(double from, double small, double big) {
        return Math.min(Math.max(from, small), big);
    }

    private Color getColor(double dx, double dy) {
        double distance = Math.sqrt((dx * dx) + (dy * dy));
        double rverysmall = 0.65 * ((double) pickerSize / 2);
        Color pixelColor = Color.BLUE;

        if (distance <= rverysmall * 1.1) {
            double angle = -Math.PI / 2.;
            double angle1 = angle + 2 * Math.PI / 3.;
            double angle2 = angle1 + 2 * Math.PI / 3.;
            double x1 = rverysmall * Math.sin(angle1);
            double y1 = rverysmall * Math.cos(angle1);
            double x2 = rverysmall * Math.sin(angle2);
            double y2 = rverysmall * Math.cos(angle2);
            dx += 0.01;
            double[] circle = circleFrom3Points(new Point2D(x1, y1), new Point2D(x2, y2), new Point2D(dx, dy));
            double xArc = circle[0];
            double yArc = 0;
            double arcR = circle[2];
            double Arco = Math.atan2(dx - xArc, dy - yArc);
            double Arco1 = Math.atan2(x1 - xArc, y1 - yArc);
            double Arco2 = Math.atan2(x2 - xArc, y2 - yArc);

            double finalX = xArc > 0 ? xArc - arcR : xArc + arcR;

            double saturation = map(finalX, -rverysmall, rverysmall, 255, 0);

            double lightness = 255;
            double diffAngle = Arco2 - Arco1;
            double diffArco = Arco - Arco1;
            if (dx < x1) {
                diffAngle = diffAngle < 0 ? 2 * Math.PI + diffAngle : diffAngle;
                diffAngle = Math.abs(2 * Math.PI - diffAngle);
                diffArco = diffArco < 0 ? 2 * Math.PI + diffArco : diffArco;
                diffArco = Math.abs(2 * Math.PI - diffArco);
            }
            lightness = map(diffArco, 0, diffAngle, 0, 255);


            if (distance > rverysmall) {
                saturation = 255 - saturation;
                if (lightness < 0 && dy < 0) {
                    lightness = 255;
                }
            }
            lightness = clamp(lightness, 0, 255);
            if ((saturation < 10 && dx < x1) || (saturation > 240 && dx > x1)) {
                saturation = 255 - saturation;
            }
            saturation = clamp(saturation, 0, 255);
            pixelColor = HSL2RGB(currentHue, saturation, lightness);
        }
        return pixelColor;
    }


    /***************************************************************************
     *                                                                         *
     * Hues Animation                                                          *
     *                                                                         *
     **************************************************************************/

    private final class CurveTransition extends Transition {
        Point2D from;
        double fromTheta;
        double toTheta;

        public CurveTransition(Point2D from, Point2D to) {
            this.from = from;
            double fromDx = from.getX() - centerX;
            double fromDy = from.getY() - centerY;
            fromTheta = Math.atan2(fromDy, fromDx);
            double toDx = to.getX() - centerX;
            double toDy = to.getY() - centerY;
            toTheta = Math.atan2(toDy, toDx);
            setInterpolator(Interpolator.EASE_BOTH);
            setDelay(Duration.millis(0));
            setCycleDuration(Duration.millis(240));
        }

        @Override
        protected void interpolate(double frac) {
            double dif = Math.min(Math.abs(toTheta - fromTheta), 2 * Math.PI - Math.abs(toTheta - fromTheta));
            if (dif == 2 * Math.PI - Math.abs(toTheta - fromTheta)) {
                int dir = -1;
                if (toTheta < fromTheta) {
                    dir = 1;
                }
                dif = dir * dif;
            } else {
                dif = toTheta - fromTheta;
            }

            Point2D newP = rotate(from, new Point2D(centerX, centerY), frac * dif);
            colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(newP.getY() - centerY, newP.getX() - centerX)));
            colorSelector.setTranslateX(newP.getX() - colorSelector.getPrefWidth() / 2);
            colorSelector.setTranslateY(newP.getY() - colorSelector.getPrefHeight() / 2);
        }
    }


    /***************************************************************************
     *                                                                         *
     * Util methods	                                                           *
     *                                                                         *
     **************************************************************************/

    private double map(double val, double min1, double max1, double min2, double max2) {
        return min2 + (max2 - min2) * ((val - min1) / (max1 - min1));
    }

    private Color HSL2RGB(double hue, double sat, double lum) {
        hue = map(hue, 0, 255, 0, 359);
        sat = map(sat, 0, 255, 0, 1);
        lum = map(lum, 0, 255, 0, 1);
        double v;
        double red, green, blue;
        double m;
        double sv;
        int sextant;
        double fract, vsf, mid1, mid2;

        red = lum;   // default to gray
        green = lum;
        blue = lum;
        v = (lum <= 0.5) ? (lum * (1.0 + sat)) : (lum + sat - lum * sat);
        m = lum + lum - v;
        sv = (v - m) / v;
        hue /= 60.0;  //get into range 0..6
        sextant = (int) Math.floor(hue);  // int32 rounds up or down.
        fract = hue - sextant;
        vsf = v * sv * fract;
        mid1 = m + vsf;
        mid2 = v - vsf;

        if (v > 0) {
            switch (sextant) {
                case 0:
                    red = v;
                    green = mid1;
                    blue = m;
                    break;
                case 1:
                    red = mid2;
                    green = v;
                    blue = m;
                    break;
                case 2:
                    red = m;
                    green = v;
                    blue = mid1;
                    break;
                case 3:
                    red = m;
                    green = mid2;
                    blue = v;
                    break;
                case 4:
                    red = mid1;
                    green = m;
                    blue = v;
                    break;
                case 5:
                    red = v;
                    green = m;
                    blue = mid2;
                    break;
            }
        }
        return new Color(red, green, blue, 1);
    }

    private double[] circleFrom3Points(Point2D a, Point2D b, Point2D c) {
        double ax, ay, bx, by, cx, cy, x1, y11, dx1, dy1, x2, y2, dx2, dy2, ox, oy, dx, dy, radius; // Variables Used and to Declared
        ax = a.getX();
        ay = a.getY(); //first Point X and Y
        bx = b.getX();
        by = b.getY(); // Second Point X and Y
        cx = c.getX();
        cy = c.getY(); // Third Point X and Y

        ////****************Following are Basic Procedure**********************///
        x1 = (bx + ax) / 2;
        y11 = (by + ay) / 2;
        dy1 = bx - ax;
        dx1 = -(by - ay);

        x2 = (cx + bx) / 2;
        y2 = (cy + by) / 2;
        dy2 = cx - bx;
        dx2 = -(cy - by);

        ox = (y11 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2) / (dx1 * dy2 - dy1 * dx2);
        oy = (ox - x1) * dy1 / dx1 + y11;

        dx = ox - ax;
        dy = oy - ay;
        radius = Math.sqrt(dx * dx + dy * dy);
        return new double[] {ox, oy, radius};
    }


    private Point2D getPointFromSL(int saturation, int lightness, double radius) {
        double dy = map(saturation, 0, 255, -radius, radius);
        double angle = 0.;
        double angle1 = angle + 2 * Math.PI / 3.;
        double angle2 = angle1 + 2 * Math.PI / 3.;
        double x1 = radius * Math.sin(angle1);
        double y1 = radius * Math.cos(angle1);
        double x2 = radius * Math.sin(angle2);
        double y2 = radius * Math.cos(angle2);
        double dx = 0;
        double[] circle = circleFrom3Points(new Point2D(x1, y1), new Point2D(dx, dy), new Point2D(x2, y2));
        double xArc = circle[0];
        double yArc = circle[1];
        double arcR = circle[2];
        double Arco1 = Math.atan2(x1 - xArc, y1 - yArc);
        double Arco2 = Math.atan2(x2 - xArc, y2 - yArc);
        double ArcoFinal = map(lightness, 0, 255, Arco2, Arco1);
        double finalX = xArc + arcR * Math.sin(ArcoFinal);
        double finalY = yArc + arcR * Math.cos(ArcoFinal);
        if (dy < y1) {
            ArcoFinal = map(lightness, 0, 255, Arco1, Arco2 + 2 * Math.PI);
            finalX = -xArc - arcR * Math.sin(ArcoFinal);
            finalY = yArc + arcR * Math.cos(ArcoFinal);
        }
        return new Point2D(finalX, finalY);
    }

    private Point2D rotate(Point2D a, Point2D center, double angle) {
        double resultX = center.getX() + (a.getX() - center.getX()) * Math.cos(angle) - (a.getY() - center.getY()) * Math
            .sin(angle);
        double resultY = center.getY() + (a.getX() - center.getX()) * Math.sin(angle) + (a.getY() - center.getY()) * Math
            .cos(angle);
        return new Point2D(resultX, resultY);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy