content: /* ===========================================================
   * JFreeChart : a free chart library for the Java(tm) platform
   * ===========================================================
   *
   * (C) Copyright 2000-2012, by Object Refinery Limited and Contributors.
   *
   * Project Info:  http://www.jfree.org/jfreechart/index.html
   *
   * This library is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 of the License, or
   * (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
   * License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
   * USA.
   *
   * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   * Other names may be trademarks of their respective owners.]
   *
   * ---------------------------
   * DefaultShadowGenerator.java
   * ---------------------------
   * (C) Copyright 2009, 2011 by Object Refinery Limited and Contributors.
   *
   * Original Author:  David Gilbert (for Object Refinery Limited);
   * Contributor(s):   -;
   *
   * Changes:
   * --------
   * 10-Jul-2009 : Version 1 (DG);
   * 29-Oct-2011 : Fixed Eclipse warnings (DG);
   *
   */
  
  package org.jfree.chart.util;
  
  import java.awt.Color;
  import java.awt.Graphics2D;
  import java.awt.image.BufferedImage;
  import java.awt.image.DataBufferInt;
  import java.io.Serializable;
  import org.jfree.chart.HashUtilities;
  
  /**
   * A default implementation of the {@link ShadowGenerator} interface, based on
   * code in a
   * <a href="http://www.jroller.com/gfx/entry/fast_or_good_drop_shadows">blog
   * post by Romain Guy</a>.
   *
   * @since 1.0.14
   */
  public class DefaultShadowGenerator implements ShadowGenerator, Serializable {
  
      private static final long serialVersionUID = 2732993885591386064L;
  
      /** The shadow size. */
      private int shadowSize;
  
      /** The shadow color. */
      private Color shadowColor;
  
      /** The shadow opacity. */
      private float shadowOpacity;
  
      /** The shadow offset angle (in radians). */
      private double angle;
  
      /** The shadow offset distance (in Java2D units). */
      private int distance;
  
      /**
       * Creates a new instance with default attributes.
       */
      public DefaultShadowGenerator() {
          this(5, Color.BLACK, 0.5f, 5, -Math.PI / 4);
      }
  
      /**
       * Creates a new instance with the specified attributes.
       *
       * @param size  the shadow size.
       * @param color  the shadow color.
       * @param opacity  the shadow opacity.
       * @param distance  the shadow offset distance.
       * @param angle  the shadow offset angle (in radians).
       */
      public DefaultShadowGenerator(int size, Color color, float opacity,
              int distance, double angle) {
          if (color == null) {
              throw new IllegalArgumentException("Null 'color' argument.");
          }
          this.shadowSize = size;
          this.shadowColor = color;
          this.shadowOpacity = opacity;
          this.distance = distance;
          this.angle = angle;
      }
  
      /**
       * Returns the shadow size.
       *
       * @return The shadow size.
       */
      public int getShadowSize() {
          return this.shadowSize;
      }
  
      /**
       * Returns the shadow color.
       *
       * @return The shadow color (never <code>null</code>).
       */
      public Color getShadowColor() {
          return this.shadowColor;
      }
  
      /**
       * Returns the shadow opacity.
       *
       * @return The shadow opacity.
       */
      public float getShadowOpacity() {
          return this.shadowOpacity;
      }
  
      /**
       * Returns the shadow offset distance.
       *
       * @return The shadow offset distance (in Java2D units).
       */
      public int getDistance() {
          return this.distance;
      }
  
      /**
       * Returns the shadow offset angle (in radians).
       *
       * @return The angle (in radians).
       */
      public double getAngle() {
          return this.angle;
      }
  
      /**
       * Calculates the x-offset for drawing the shadow image relative to the
       * source.
       *
       * @return The x-offset.
       */
      @Override
      public int calculateOffsetX() {
          return (int) (Math.cos(this.angle) * this.distance) - this.shadowSize;
      }
  
      /**
       * Calculates the y-offset for drawing the shadow image relative to the
       * source.
       *
       * @return The y-offset.
       */
      @Override
      public int calculateOffsetY() {
          return -(int) (Math.sin(this.angle) * this.distance) - this.shadowSize;
      }
  
      /**
       * Creates and returns an image containing the drop shadow for the
       * specified source image.
       *
       * @param source  the source image.
       *
       * @return A new image containing the shadow.
       */
      @Override
      public BufferedImage createDropShadow(BufferedImage source) {
          BufferedImage subject = new BufferedImage(
                  source.getWidth() + this.shadowSize * 2,
                  source.getHeight() + this.shadowSize * 2,
                  BufferedImage.TYPE_INT_ARGB);
  
          Graphics2D g2 = subject.createGraphics();
          g2.drawImage(source, null, this.shadowSize, this.shadowSize);
          g2.dispose();
          applyShadow(subject);
          return subject;
      }
  
      /**
       * Applies a shadow to the image.
       *
       * @param image  the image.
       */
      protected void applyShadow(BufferedImage image) {
          int dstWidth = image.getWidth();
          int dstHeight = image.getHeight();
  
          int left = (this.shadowSize - 1) >> 1;
          int right = this.shadowSize - left;
          int xStart = left;
          int xStop = dstWidth - right;
          int yStart = left;
          int yStop = dstHeight - right;
  
          int shadowRgb = this.shadowColor.getRGB() & 0x00FFFFFF;
  
          int[] aHistory = new int[this.shadowSize];
          int historyIdx = 0;
  
          int aSum;
  
          int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
          int lastPixelOffset = right * dstWidth;
          float sumDivider = this.shadowOpacity / this.shadowSize;
  
          // horizontal pass
  
          for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) {
              aSum = 0;
              historyIdx = 0;
              for (int x = 0; x < this.shadowSize; x++, bufferOffset++) {
                  int a = dataBuffer[bufferOffset] >>> 24;
                  aHistory[x] = a;
                  aSum += a;
              }
  
              bufferOffset -= right;
  
              for (int x = xStart; x < xStop; x++, bufferOffset++) {
                  int a = (int) (aSum * sumDivider);
                  dataBuffer[bufferOffset] = a << 24 | shadowRgb;
  
                  // substract the oldest pixel from the sum
                  aSum -= aHistory[historyIdx];
  
                  // get the lastest pixel
                  a = dataBuffer[bufferOffset + right] >>> 24;
                  aHistory[historyIdx] = a;
                  aSum += a;
  
                  if (++historyIdx >= this.shadowSize) {
                      historyIdx -= this.shadowSize;
                  }
              }
          }
  
          // vertical pass
          for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
              aSum = 0;
              historyIdx = 0;
              for (int y = 0; y < this.shadowSize; y++,
                      bufferOffset += dstWidth) {
                  int a = dataBuffer[bufferOffset] >>> 24;
                  aHistory[y] = a;
                  aSum += a;
              }
  
              bufferOffset -= lastPixelOffset;
  
              for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
                  int a = (int) (aSum * sumDivider);
                  dataBuffer[bufferOffset] = a << 24 | shadowRgb;
  
                  // substract the oldest pixel from the sum
                  aSum -= aHistory[historyIdx];
  
                  // get the lastest pixel
                  a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
                  aHistory[historyIdx] = a;
                  aSum += a;
  
                  if (++historyIdx >= this.shadowSize) {
                      historyIdx -= this.shadowSize;
                  }
              }
          }
      }
  
      /**
       * Tests this object for equality with an arbitrary object.
       *
       * @param obj  the object (<code>null</code> permitted).
       *
       * @return The object.
       */
      @Override
      public boolean equals(Object obj) {
          if (obj == this) {
              return true;
          }
          if (!(obj instanceof DefaultShadowGenerator)) {
              return false;
          }
          DefaultShadowGenerator that = (DefaultShadowGenerator) obj;
          if (this.shadowSize != that.shadowSize) {
              return false;
          }
          if (!this.shadowColor.equals(that.shadowColor)) {
              return false;
          }
          if (this.shadowOpacity != that.shadowOpacity) {
              return false;
          }
          if (this.distance != that.distance) {
              return false;
          }
          if (this.angle != that.angle) {
              return false;
          }
          return true;
      }
  
      /**
       * Returns a hash code for this instance.
       *
       * @return The hash code.
       */
      @Override
      public int hashCode() {
          int hash = HashUtilities.hashCode(17, this.shadowSize);
          hash = HashUtilities.hashCode(hash, this.shadowColor);
          hash = HashUtilities.hashCode(hash, this.shadowOpacity);
          hash = HashUtilities.hashCode(hash, this.distance);
          hash = HashUtilities.hashCode(hash, this.angle);
          return hash;
      }
  
  }
  