/* * Copyright (c) 2015 by Gerrit Grunwald * * Licensed 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 eu.hansolo.medusa.tools; import eu.hansolo.medusa.Gauge.ScaleDirection; import javafx.animation.Interpolator; import javafx.geometry.Bounds; import javafx.geometry.Point2D; import javafx.scene.image.Image; import javafx.scene.image.PixelWriter; import javafx.scene.image.WritableImage; import javafx.scene.paint.Color; import javafx.scene.paint.ImagePattern; import javafx.scene.paint.Stop; import javafx.scene.shape.Rectangle; import javafx.scene.shape.Shape; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; /** * Created by hansolo on 21.12.15. */ public class ConicalGradient { private static final double ANGLE_FACTOR = 1.0 / 360.0; private double centerX; private double centerY; private List<Stop> sortedStops; private ScaleDirection scaleDirection; private WritableImage rectRaster; private WritableImage roundRaster; // ******************** Constructors ************************************** public ConicalGradient() { this(0, 0, 0, ScaleDirection.CLOCKWISE, Arrays.asList(new Stop[]{})); } public ConicalGradient(final Stop... STOPS) { this(0, 0, 0, ScaleDirection.CLOCKWISE, Arrays.asList(STOPS)); } public ConicalGradient(final List<Stop> STOPS) { this(0, 0, 0, ScaleDirection.CLOCKWISE, STOPS); } public ConicalGradient(final double CENTER_X, final double CENTER_Y, final Stop... STOPS) { this(CENTER_X, CENTER_Y, ScaleDirection.CLOCKWISE, STOPS); } public ConicalGradient(final double CENTER_X, final double CENTER_Y, final ScaleDirection DIRECTION, final Stop... STOPS) { this(CENTER_X, CENTER_Y, 0.0, DIRECTION, Arrays.asList(STOPS)); } public ConicalGradient(final double CENTER_X, final double CENTER_Y, final ScaleDirection DIRECTION, final List<Stop> STOPS) { this(CENTER_X, CENTER_Y, 0.0, DIRECTION, STOPS); } public ConicalGradient(final double CENTER_X, final double CENTER_Y, final double OFFSET, final ScaleDirection DIRECTION, final Stop... STOPS) { this(CENTER_X, CENTER_Y, OFFSET, DIRECTION, Arrays.asList(STOPS)); } public ConicalGradient(final double CENTER_X, final double CENTER_Y, final double OFFSET, final ScaleDirection DIRECTION, final List<Stop> STOPS) { centerX = CENTER_X; centerY = CENTER_Y; scaleDirection = DIRECTION; sortedStops = normalizeStops(OFFSET, STOPS); } // ******************** Methods ******************************************* public void recalculateWithAngle(final double ANGLE) { double angle = ANGLE % 360.0; sortedStops = calculate(sortedStops, ANGLE_FACTOR * angle); rectRaster = null; roundRaster = null; } public List<Stop> getStops() { return sortedStops; } public void setStops(final Stop... STOPS) { setStops(Arrays.asList(STOPS)); } public void setStops(final double OFFSET, final Stop... STOPS) { setStops(OFFSET, Arrays.asList(STOPS)); } public void setStops(final List<Stop> STOPS) { setStops(0 ,STOPS); } public void setStops(final double OFFSET, final List<Stop> STOPS) { sortedStops = normalizeStops(OFFSET, STOPS); rectRaster = null; roundRaster = null; } public double[] getCenter() { return new double[]{ centerX, centerY }; } public Point2D getCenterPoint() { return new Point2D(centerX, centerY); } public Image getImage(final double WIDTH, final double HEIGHT) { int width = (int) WIDTH <= 0 ? 100 : (int) WIDTH; int height = (int) HEIGHT <= 0 ? 100 : (int) HEIGHT; if (rectRaster != null && width == rectRaster.getWidth() && height == rectRaster.getHeight()) return rectRaster; Color color = Color.TRANSPARENT; rectRaster = new WritableImage(width, height); final PixelWriter PIXEL_WRITER = rectRaster.getPixelWriter(); if (Double.compare(0.0, centerX) == 0) centerX = width * 0.5; if (Double.compare(0.0, centerY) == 0) centerY = height * 0.5; int calculatedStopsLength = sortedStops.size() - 1; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { double dx = x - centerX; double dy = y - centerY; double distance = Math.sqrt((dx * dx) + (dy * dy)); distance = Double.compare(distance, 0) == 0 ? 1 : distance; double angle = adjustAngle(dx, dy, Math.abs(Math.toDegrees(Math.acos(dx / distance)))); for (int i = 0; i < calculatedStopsLength; i++) { double offsetI = (sortedStops.get(i).getOffset() * 360.0); double offsetIPlus1 = (sortedStops.get(i + 1).getOffset() * 360.0); if (Double.compare(angle, offsetI) >= 0 && Double.compare(angle, offsetIPlus1) < 0) { double fraction = (angle - offsetI) / (offsetIPlus1 - offsetI); color = (Color) Interpolator.LINEAR.interpolate(sortedStops.get(i).getColor(), sortedStops.get(i + 1).getColor(), fraction); } } PIXEL_WRITER.setColor(x, y, color); } } return rectRaster; } public Image getRoundImage(final double SIZE) { int size = (int) SIZE <= 0 ? 100 : (int) SIZE; if (roundRaster != null && size == roundRaster.getWidth()) return roundRaster; Color color = Color.TRANSPARENT; roundRaster = new WritableImage(size, size); final PixelWriter PIXEL_WRITER = roundRaster.getPixelWriter(); if (Double.compare(0.0, centerX) == 0) centerX = size * 0.5; if (Double.compare(0.0, centerY) == 0) centerY = size * 0.5; double radius = size * 0.5; int calculatedStopsLength = sortedStops.size() - 1; for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { double dx = x - centerX; double dy = y - centerY; double distance = Math.sqrt((dx * dx) + (dy * dy)); distance = Double.compare(distance, 0) == 0 ? 1 : distance; double angle = adjustAngle(dx, dy, Math.abs(Math.toDegrees(Math.acos(dx / distance)))); double radiusMinus05 = radius - 0.25; double radiusMinus10 = radius - 0.5; double radiusMinus15 = radius - 1.0; double radiusMinus20 = radius - 1.5; if (distance > radius) { color = Color.TRANSPARENT; } else { for (int i = 0; i < calculatedStopsLength; i++) { if (angle >= (sortedStops.get(i).getOffset() * 360) && angle < (sortedStops.get(i + 1).getOffset() * 360)) { double fraction = (angle - sortedStops.get(i).getOffset() * 360) / ((sortedStops.get(i + 1).getOffset() - sortedStops.get(i).getOffset()) * 360); color = (Color) Interpolator.LINEAR.interpolate(sortedStops.get(i).getColor(), sortedStops.get(i + 1).getColor(), fraction); if (distance > radiusMinus05) { color = color.deriveColor(0.0, 1.0, 1.0, 0.25); } else if (distance > radiusMinus10) { color = color.deriveColor(0.0, 1.0, 1.0, 0.45); } else if (distance > radiusMinus15) { color = color.deriveColor(0.0, 1.0, 1.0, 0.65); } else if (distance > radiusMinus20) { color = color.deriveColor(0.0, 1.0, 1.0, 0.85); } } } } PIXEL_WRITER.setColor(x, y, color); } } return roundRaster; } public ImagePattern apply(final Shape SHAPE) { double x = SHAPE.getLayoutBounds().getMinX(); double y = SHAPE.getLayoutBounds().getMinY(); double width = SHAPE.getLayoutBounds().getWidth(); double height = SHAPE.getLayoutBounds().getHeight(); centerX = width * 0.5; centerY = height * 0.5; return new ImagePattern(getImage(width, height), x, y, width, height, false); } public ImagePattern getImagePattern(final Bounds BOUNDS) { return getImagePattern(new Rectangle(BOUNDS.getMinX(), BOUNDS.getMinY(), BOUNDS.getWidth(), BOUNDS.getHeight())); } public ImagePattern getImagePattern(final Rectangle BOUNDS) { double x = BOUNDS.getX(); double y = BOUNDS.getY(); double width = BOUNDS.getWidth(); double height = BOUNDS.getHeight(); centerX = width * 0.5; centerY = height * 0.5; return new ImagePattern(getImage(width, height), x, y, width, height, false); } private double adjustAngle(final double DX, final double DY, double angle) { if (Double.compare(DX, 0) >= 0 && Double.compare(DY, 0) <= 0) { angle = 90.0 - angle; // Upper Right Quadrant } else if (Double.compare(DX, 0) >= 0 && Double.compare(DY, 0) >= 0) { angle += 90.0; // Lower Right Quadrant } else if (Double.compare(DX, 0) <= 0 && Double.compare(DY, 0) >= 0) { angle += 90.0; // Lower Left Quadrant } else if (Double.compare(DX, 0) <= 0 && Double.compare(DY, 0) <= 0) { angle = 450.0 - angle; // Upper Left Qudrant } /* if (DX >= 0 && DY <= 0) { angle = 90.0 - angle; // Upper Right Quadrant } else if (DX >= 0 && DY >= 0) { angle += 90.0; // Lower Right Quadrant } else if (DX <= 0 && DY >= 0) { angle += 90.0; // Lower Left Quadrant } else if (DX <= 0 && DY <= 0) { angle = 450.0 - angle; // Upper Left Qudrant } */ return angle; } private List<Stop> calculate(final List<Stop> STOPS, final double OFFSET) { List<Stop> stops = new ArrayList<>(STOPS.size()); final BigDecimal STEP = new BigDecimal(Double.MIN_VALUE); for (Stop stop : STOPS) { double offset = stop.getOffset(); Color color = stop.getColor(); BigDecimal newOffsetBD = new BigDecimal(offset + OFFSET).remainder(BigDecimal.ONE); if (newOffsetBD.equals(BigDecimal.ZERO)) { newOffsetBD = BigDecimal.ONE; stops.add(new Stop(Double.MIN_VALUE, color)); } else if (Double.compare((offset + OFFSET), 1.0) > 0) { newOffsetBD = newOffsetBD.subtract(STEP); } stops.add(new Stop(newOffsetBD.doubleValue(), color)); } HashMap<Double, Color> stopMap = new LinkedHashMap<>(stops.size()); for (Stop stop : stops) { stopMap.put(stop.getOffset(), stop.getColor()); } List<Stop> sortedStops = new ArrayList<>(stops.size()); SortedSet<Double> sortedFractions = new TreeSet<>(stopMap.keySet()); if (sortedFractions.last() < 1) { stopMap.put(1.0, stopMap.get(sortedFractions.first())); sortedFractions.add(1.0); } if (sortedFractions.first() > 0) { stopMap.put(0.0, stopMap.get(sortedFractions.last())); sortedFractions.add(0.0); } for (double fraction : sortedFractions) { sortedStops.add(new Stop(fraction, stopMap.get(fraction))); } return sortedStops; } /* private List<Stop> normalizeStops(final Stop... STOPS) { return normalizeStops(0, Arrays.asList(STOPS)); } private List<Stop> normalizeStops(final double OFFSET, final Stop... STOPS) { return normalizeStops(OFFSET, Arrays.asList(STOPS)); } private List<Stop> normalizeStops(final List<Stop> STOPS) { return normalizeStops(0, STOPS); } */ private List<Stop> normalizeStops(final double OFFSET, final List<Stop> STOPS) { double offset = Helper.clamp(0.0, 1.0, OFFSET); List<Stop> stops; if (null == STOPS || STOPS.isEmpty()) { stops = new ArrayList<>(); stops.add(new Stop(0.0, Color.TRANSPARENT)); stops.add(new Stop(1.0, Color.TRANSPARENT)); } else { stops = STOPS; } List<Stop> sortedStops = calculate(stops, offset); // Reverse the Stops for CCW direction if (ScaleDirection.COUNTER_CLOCKWISE == scaleDirection) { List<Stop> sortedStops3 = new ArrayList<>(); Collections.reverse(sortedStops); for (Stop stop : sortedStops) { sortedStops3.add(new Stop(1.0 - stop.getOffset(), stop.getColor())); } sortedStops = sortedStops3; } return sortedStops; } }