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

com.harium.etyl.awt.paint.ConicalGradientPaint Maven / Gradle / Ivy

There is a newer version: 1.5.0
Show newest version
package com.harium.etyl.awt.paint;

import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.List;


/**
 * Found at: https://svn.kenai.com/svn/steelseries~v2/SteelSeries2/src/eu/hansolo/steelseries/tools/ConicalGradientPaint.java
 * 
 * A paint class that creates conical gradients around a given center point
 * It could be used in the same way as LinearGradientPaint and RadialGradientPaint
 * and follows the same syntax.
 * You could use floats from 0.0 to 1.0 for the fractions which is standard but it's
 * also possible to use angles from 0.0 to 360 degrees which is most of the times
 * much easier to handle.
 * Gradients always start at the top with a clockwise direction and you could
 * rotate the gradient around the center by given offset.
 * The offset could also be defined from -0.5 to +0.5 or -180 to +180 degrees.
 * If you would like to use degrees instead of values from 0 to 1 you have to use
 * the full constructor and set the USE_DEGREES variable to true
 * @version 1.0
 * @author hansolo
 */
public final class ConicalGradientPaint implements java.awt.Paint {

    private final Point2D center;
    
    private final double[] FRACTION_ANGLES;
    
    private final double[] RED_STEP_LOOKUP;
    
    private final double[] GREEN_STEP_LOOKUP;
    
    private final double[] BLUE_STEP_LOOKUP;
    
    private final double[] ALPHA_STEP_LOOKUP;
    
    private final Color[] COLORS;
    
    private static final float INT_TO_FLOAT_CONST = 1f / 255f;

    /**
     * Standard constructor which takes the FRACTIONS in values from 0.0f to
     * 1.0f
     *
     * @param center
     * @param GIVEN_FRACTIONS
     * @param GIVEN_COLORS
     * @throws IllegalArgumentException
     */
    public ConicalGradientPaint(final Point2D center, final float[] GIVEN_FRACTIONS, final Color[] GIVEN_COLORS) throws IllegalArgumentException {
      this(false, center, 0.0f, GIVEN_FRACTIONS, GIVEN_COLORS);
    }

    /**
     * Enhanced constructor which takes the FRACTIONS in degress from 0.0f to
     * 360.0f and also an GIVEN_OFFSET in degrees around the rotation CENTER
     *
     * @param USE_DEGREES
     * @param CENTER
     * @param GIVEN_OFFSET
     * @param GIVEN_FRACTIONS
     * @param GIVEN_COLORS
     * @throws IllegalArgumentException
     */
    public ConicalGradientPaint(final boolean USE_DEGREES, final Point2D CENTER, final float GIVEN_OFFSET, final float[] GIVEN_FRACTIONS, final Color[] GIVEN_COLORS) throws IllegalArgumentException {
      // Check that fractions and colors are of the same size
      if (GIVEN_FRACTIONS.length != GIVEN_COLORS.length) {
        throw new IllegalArgumentException("Fractions and colors must be equal in size");
      }

      final java.util.ArrayList FRACTION_LIST = new java.util.ArrayList(GIVEN_FRACTIONS.length);
      
      final float OFFSET;
      
      if (USE_DEGREES) {
    	  
        final double DEG_FRACTION = 1f / 360f;
        
        if (Double.compare((GIVEN_OFFSET * DEG_FRACTION), -0.5) == 0) {
          OFFSET = -0.5f;
        } else if (Double.compare((GIVEN_OFFSET * DEG_FRACTION), 0.5) == 0) {
          OFFSET = 0.5f;
        } else {
          OFFSET = (float) (GIVEN_OFFSET * DEG_FRACTION);
        }
        
        for (float fraction : GIVEN_FRACTIONS) {
          FRACTION_LIST.add((float) (fraction * DEG_FRACTION));
        }
        
      } else {
        // Now it seems to work with rotation of 0.5f, below is the old code to correct the problem
//            if (GIVEN_OFFSET == -0.5)
//            {
//                // This is needed because of problems in the creation of the Raster
//                // with a angle offset of exactly -0.5
//                OFFSET = -0.49999f;
//            }
//            else if (GIVEN_OFFSET == 0.5)
//            {
//                // This is needed because of problems in the creation of the Raster
//                // with a angle offset of exactly +0.5
//                OFFSET = 0.499999f;
//            }
//            else
        {
          OFFSET = GIVEN_OFFSET;
        }
        for (float fraction : GIVEN_FRACTIONS) {
          FRACTION_LIST.add(fraction);
        }
      }

      // Check for valid offset
      if (OFFSET > 0.5f || OFFSET < -0.5f) {
        throw new IllegalArgumentException("Offset has to be in the range of -0.5 to 0.5");
      }

      // Adjust fractions and colors array in the case where startvalue != 0.0f and/or endvalue != 1.0f
      final List colorList = new ArrayList(GIVEN_COLORS.length);
      colorList.addAll(java.util.Arrays.asList(GIVEN_COLORS));

      // Assure that fractions start with 0.0f
      if (FRACTION_LIST.get(0) != 0.0f) {
        FRACTION_LIST.add(0, 0.0f);
        final Color TMP_COLOR = colorList.get(0);
        colorList.add(0, TMP_COLOR);
      }

      // Assure that fractions end with 1.0f
      if (FRACTION_LIST.get(FRACTION_LIST.size() - 1) != 1.0f) {
        FRACTION_LIST.add(1.0f);
        colorList.add(GIVEN_COLORS[0]);
      }

      // Recalculate the fractions and colors with the given offset
      final java.util.Map FRACTION_COLORS = recalculate(FRACTION_LIST, colorList, OFFSET);

      // Clear the original FRACTION_LIST and COLOR_LIST
      FRACTION_LIST.clear();
      colorList.clear();

      // Sort the hashmap by fraction and add the values to the FRACION_LIST and COLOR_LIST
      final java.util.SortedSet SORTED_FRACTIONS = new java.util.TreeSet(FRACTION_COLORS.keySet());
      final java.util.Iterator ITERATOR = SORTED_FRACTIONS.iterator();
      while (ITERATOR.hasNext()) {
        final float CURRENT_FRACTION = ITERATOR.next();
        FRACTION_LIST.add(CURRENT_FRACTION);
        colorList.add(FRACTION_COLORS.get(CURRENT_FRACTION));
      }

      // Set the values
      this.center = CENTER;
      COLORS = colorList.toArray(new Color[]{});

      // Prepare lookup table for the angles of each fraction
      final int MAX_FRACTIONS = FRACTION_LIST.size();
      this.FRACTION_ANGLES = new double[MAX_FRACTIONS];
      for (int i = 0; i < MAX_FRACTIONS; i++) {
        FRACTION_ANGLES[i] = FRACTION_LIST.get(i) * 360;
      }

      // Prepare lookup tables for the color stepsize of each color
      RED_STEP_LOOKUP = new double[COLORS.length];
      GREEN_STEP_LOOKUP = new double[COLORS.length];
      BLUE_STEP_LOOKUP = new double[COLORS.length];
      ALPHA_STEP_LOOKUP = new double[COLORS.length];

      for (int i = 0; i < (COLORS.length - 1); i++) {
        RED_STEP_LOOKUP[i] = ((COLORS[i + 1].getRed() - COLORS[i].getRed()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
        GREEN_STEP_LOOKUP[i] = ((COLORS[i + 1].getGreen() - COLORS[i].getGreen()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
        BLUE_STEP_LOOKUP[i] = ((COLORS[i + 1].getBlue() - COLORS[i].getBlue()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
        ALPHA_STEP_LOOKUP[i] = ((COLORS[i + 1].getAlpha() - COLORS[i].getAlpha()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
      }
    }

    /**
     * Recalculates the fractions in the FRACTION_LIST and their associated
     * colors in the COLOR_LIST with a given OFFSET. Because the conical
     * gradients always starts with 0 at the top and clockwise direction you
     * could rotate the defined conical gradient from -180 to 180 degrees which
     * equals values from -0.5 to +0.5
     *
     * @param FRACTION_LIST
     * @param COLOR_LIST
     * @param OFFSET
     * @return Hashmap that contains the recalculated fractions and colors after
     * a given rotation
     */
    private java.util.HashMap recalculate(final List FRACTION_LIST, final List COLOR_LIST, final float OFFSET) {
      // Recalculate the fractions and colors with the given offset
      final int MAX_FRACTIONS = FRACTION_LIST.size();
      final java.util.HashMap FRACTION_COLORS = new java.util.HashMap(MAX_FRACTIONS);
      for (int i = 0; i < MAX_FRACTIONS; i++) {
        // Add offset to fraction
        final float TMP_FRACTION = FRACTION_LIST.get(i) + OFFSET;

        // Color related to current fraction
        final Color TMP_COLOR = COLOR_LIST.get(i);

        // Check each fraction for limits (0...1)
        if (TMP_FRACTION <= 0) {
          FRACTION_COLORS.put(1.0f + TMP_FRACTION + 0.0001f, TMP_COLOR);

          final float NEXT_FRACTION;
          final Color NEXT_COLOR;
          if (i < MAX_FRACTIONS - 1) {
            NEXT_FRACTION = FRACTION_LIST.get(i + 1) + OFFSET;
            NEXT_COLOR = COLOR_LIST.get(i + 1);
          } else {
            NEXT_FRACTION = 1 - FRACTION_LIST.get(0) + OFFSET;
            NEXT_COLOR = COLOR_LIST.get(0);
          }
          if (NEXT_FRACTION > 0) {
            final Color NEW_FRACTION_COLOR = getColorFromFraction(TMP_COLOR, NEXT_COLOR, (int) ((NEXT_FRACTION - TMP_FRACTION) * 10000), (int) ((-TMP_FRACTION) * 10000));
            FRACTION_COLORS.put(0.0f, NEW_FRACTION_COLOR);
            FRACTION_COLORS.put(1.0f, NEW_FRACTION_COLOR);
          }
        } else if (TMP_FRACTION >= 1) {
          FRACTION_COLORS.put(TMP_FRACTION - 1.0f - 0.0001f, TMP_COLOR);

          final float PREVIOUS_FRACTION;
          final Color PREVIOUS_COLOR;
          if (i > 0) {
            PREVIOUS_FRACTION = FRACTION_LIST.get(i - 1) + OFFSET;
            PREVIOUS_COLOR = COLOR_LIST.get(i - 1);
          } else {
            PREVIOUS_FRACTION = FRACTION_LIST.get(MAX_FRACTIONS - 1) + OFFSET;
            PREVIOUS_COLOR = COLOR_LIST.get(MAX_FRACTIONS - 1);
          }
          if (PREVIOUS_FRACTION < 1) {
            final Color NEW_FRACTION_COLOR = getColorFromFraction(TMP_COLOR, PREVIOUS_COLOR, (int) ((TMP_FRACTION - PREVIOUS_FRACTION) * 10000), (int) (TMP_FRACTION - 1.0f) * 10000);
            FRACTION_COLORS.put(1.0f, NEW_FRACTION_COLOR);
            FRACTION_COLORS.put(0.0f, NEW_FRACTION_COLOR);
          }
        } else {
          FRACTION_COLORS.put(TMP_FRACTION, TMP_COLOR);
        }
      }

      // Clear the original FRACTION_LIST and COLOR_LIST
      FRACTION_LIST.clear();
      COLOR_LIST.clear();

      return FRACTION_COLORS;
    }

    /**
     * With the START_COLOR at the beginning and the DESTINATION_COLOR at the
     * end of the given RANGE the method will calculate and return the color
     * that equals the given VALUE. e.g. a START_COLOR of BLACK (R:0, G:0, B:0,
     * A:255) and a DESTINATION_COLOR of WHITE(R:255, G:255, B:255, A:255) with
     * a given RANGE of 100 and a given VALUE of 50 will return the color that
     * is exactly in the middle of the gradient between black and white which is
     * gray(R:128, G:128, B:128, A:255) So this method is really useful to
     * calculate colors in gradients between two given colors.
     *
     * @param START_COLOR
     * @param DESTINATION_COLOR
     * @param RANGE
     * @param VALUE
     * @return Color calculated from a range of values by given value
     */
    public Color getColorFromFraction(final Color START_COLOR, final Color DESTINATION_COLOR, final int RANGE, final int VALUE) {
      final float SOURCE_RED = START_COLOR.getRed() * INT_TO_FLOAT_CONST;
      final float SOURCE_GREEN = START_COLOR.getGreen() * INT_TO_FLOAT_CONST;
      final float SOURCE_BLUE = START_COLOR.getBlue() * INT_TO_FLOAT_CONST;
      final float SOURCE_ALPHA = START_COLOR.getAlpha() * INT_TO_FLOAT_CONST;

      final float DESTINATION_RED = DESTINATION_COLOR.getRed() * INT_TO_FLOAT_CONST;
      final float DESTINATION_GREEN = DESTINATION_COLOR.getGreen() * INT_TO_FLOAT_CONST;
      final float DESTINATION_BLUE = DESTINATION_COLOR.getBlue() * INT_TO_FLOAT_CONST;
      final float DESTINATION_ALPHA = DESTINATION_COLOR.getAlpha() * INT_TO_FLOAT_CONST;

      final float RED_DELTA = DESTINATION_RED - SOURCE_RED;
      final float GREEN_DELTA = DESTINATION_GREEN - SOURCE_GREEN;
      final float BLUE_DELTA = DESTINATION_BLUE - SOURCE_BLUE;
      final float ALPHA_DELTA = DESTINATION_ALPHA - SOURCE_ALPHA;

      final float RED_FRACTION = RED_DELTA / RANGE;
      final float GREEN_FRACTION = GREEN_DELTA / RANGE;
      final float BLUE_FRACTION = BLUE_DELTA / RANGE;
      final float ALPHA_FRACTION = ALPHA_DELTA / RANGE;
      //System.out.println(DISTANCE + "     " + CURRENT_FRACTION);

      return new Color(SOURCE_RED + RED_FRACTION * VALUE, SOURCE_GREEN + GREEN_FRACTION * VALUE, SOURCE_BLUE + BLUE_FRACTION * VALUE, SOURCE_ALPHA + ALPHA_FRACTION * VALUE);
    }

    @Override
    public java.awt.PaintContext createContext(final java.awt.image.ColorModel COLOR_MODEL, final java.awt.Rectangle DEVICE_BOUNDS, final Rectangle2D USER_BOUNDS, final AffineTransform TRANSFORM, final java.awt.RenderingHints HINTS) {
      final Point2D TRANSFORMED_CENTER = TRANSFORM.transform(center, null);
      return new ConicalGradientPaintContext(TRANSFORMED_CENTER);
    }

    @Override
    public int getTransparency() {
      return java.awt.Transparency.TRANSLUCENT;
    }

    private final class ConicalGradientPaintContext implements java.awt.PaintContext {

      final private Point2D center;

      public ConicalGradientPaintContext(final Point2D CENTER) {
        this.center = new Point2D.Double(CENTER.getX(), CENTER.getY());
      }

      @Override
      public void dispose() {
      }

      @Override
      public java.awt.image.ColorModel getColorModel() {
        return java.awt.image.ColorModel.getRGBdefault();
      }

      @Override
      public Raster getRaster(final int X, final int Y, final int TILE_WIDTH, final int TILE_HEIGHT) {
    	  
        final double ROTATION_CENTER_X = -X + center.getX();
        final double ROTATION_CENTER_Y = -Y + center.getY();

        final int MAX = FRACTION_ANGLES.length;

        // Create raster for given colormodel
        final WritableRaster raster = getColorModel().createCompatibleWritableRaster(TILE_WIDTH, TILE_HEIGHT);

        // Create data array with place for red, green, blue and alpha values
        int[] data = new int[(TILE_WIDTH * TILE_HEIGHT * 4)];

        double dx;
        double dy;
        double distance;
        double angle;
        double currentRed = 0;
        double currentGreen = 0;
        double currentBlue = 0;
        double currentAlpha = 0;

        for (int py = 0; py < TILE_HEIGHT; py++) {
          for (int px = 0; px < TILE_WIDTH; px++) {

            // Calculate the distance between the current position and the rotation angle
            dx = px - ROTATION_CENTER_X;
            dy = py - ROTATION_CENTER_Y;
            distance = Math.sqrt(dx * dx + dy * dy);

            // Avoid division by zero
            if (distance == 0) {
              distance = 1;
            }

            // 0 degree on top
            angle = Math.abs(Math.toDegrees(Math.acos(dx / distance)));

            if (dx >= 0 && dy <= 0) {
              angle = 90.0 - angle;
            } else if (dx >= 0 && dy >= 0) {
              angle += 90.0;
            } else if (dx <= 0 && dy >= 0) {
              angle += 90.0;
            } else if (dx <= 0 && dy <= 0) {
              angle = 450.0 - angle;
            }

            // Check for each angle in fractionAngles array
            for (int i = 0; i < (MAX - 1); i++) {
              if ((angle >= FRACTION_ANGLES[i])) {
                currentRed = COLORS[i].getRed() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * RED_STEP_LOOKUP[i];
                currentGreen = COLORS[i].getGreen() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * GREEN_STEP_LOOKUP[i];
                currentBlue = COLORS[i].getBlue() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * BLUE_STEP_LOOKUP[i];
                currentAlpha = COLORS[i].getAlpha() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * ALPHA_STEP_LOOKUP[i];
                continue;
              }
            }

            // Fill data array with calculated color values
            final int BASE = (py * TILE_WIDTH + px) * 4;
            data[BASE + 0] = (int) (currentRed * 255);
            data[BASE + 1] = (int) (currentGreen * 255);
            data[BASE + 2] = (int) (currentBlue * 255);
            data[BASE + 3] = (int) (currentAlpha * 255);
          }
        }

        // Fill the raster with the data
        raster.setPixels(0, 0, TILE_WIDTH, TILE_HEIGHT, data);

        return raster;
      }
      
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy