//[skeleton1.
package visual.statik.sampled;


import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;

/**
 * A BufferedImage that knows how to render itself 
 * (i.e., a BufferedImage along with all of 
 * the attributes necessary for rendering)
 *
 * @author  Prof. David Bernstein, James Madison Univeristy
 * @version 1.0
 */
public class Content
             extends    visual.statik.AbstractTransformableContent
             implements TransformableContent

{
//]skeleton1.
    private boolean              rotatable;
//[skeleton2.
    private boolean            refiltered;    
    private BufferedImageOp    imageOp;    
    private Composite          composite;    
    private BufferedImage      originalImage, transformedImage;    
    private Rectangle2D.Double originalBounds,transformedBounds;
    
    
    private static final double       DEFAULT_X         = 0.0;
    private static final double       DEFAULT_Y         = 0.0;
    

    

    /**
     * Default Constructor
     */
    public Content()
    {
       this(null, DEFAULT_X, DEFAULT_Y);
    }



    /**
     * Explicit Value Constructor
     *
     * @param image        The BufferedImage
     * @param x            The coordinate of the left edge
     * @param y            The coordinate of the top edge
     */
    public Content(BufferedImage  image,  
                                double x, double y)
    {
       this(image, x, y, true);       
    }
//]skeleton2.    
    

    /**
     * Explicit Value Constructor
     *
     * @param image        The BufferedImage
     * @param x            The coordinate of the left edge
     * @param y            The coordinate of the top edge
     * @param rotatable    false to prevent rotations (and improve performance)
     */
    public Content(BufferedImage  image,  
                                double x, double y, 
                                boolean rotatable)
    {
       super();

       setImage(image);
       
       this.rotatable    = rotatable;       
       transformedImage  = image;       

       setLocation(x, y);
       setComposite(null);       
       setBufferedImageOp(null);       
    }


//[transform
    /**
     * Create the transformed version of the shape
     */
    private void createTransformedContent()
    {
       createTransformedContent(getAffineTransform());
    }


    /**
     * Create a transformed version of the BufferedImage
     *
     * @param at   The AffineTransform to use
     */
    private void createTransformedContent(AffineTransform at)
    {
       AffineTransformOp       op;
       
       op = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
       createTransformedContent(op);       
    }


    /**
     * Create a transformed version of the BufferedImage
     *
     * @param op   The BufferedImageOp to use
     */
    private void createTransformedContent(BufferedImageOp op)
    {
       BufferedImage   tempImage;       
       Rectangle2D     temp;

       try
       {
          // Apply the filter
          tempImage = originalImage;          
          if (imageOp != null) 
          {
             tempImage = imageOp.filter(originalImage, null);             
          }
          
          // Create the transformed version
          transformedImage = op.filter(tempImage, null);

          temp = op.getBounds2D(originalImage);
          
          transformedBounds.x      = temp.getX();
          transformedBounds.y      = temp.getY();
          transformedBounds.width  = temp.getWidth();
          transformedBounds.height = temp.getHeight();  

          if (!rotatable)
          {
             transformedBounds.x += x;
             transformedBounds.y += y;
          }

          setTransformationRequired(false);
       }
       catch (RasterFormatException rfe)
       {
          // Unable to transform
          transformedImage = null;          
       }
    }
//]transform


    /**
    * Returns a high precision bounding box of the appropriately
    * transformed content 
    *
    * @return   The bounding box
    */
    public Rectangle2D getBounds2D()
    {
       return getBounds2D(true);       
    }
    

//[bounds
    /**
     * Returns a high precision bounding box of the Content
     * either before or after it is transformed
     * (required by TransformedContent) 
     *
     * @param    transformed    true to get the BB of the transformed content
     * @return   The bounding box
     */
    public Rectangle2D getBounds2D(boolean transformed)
    {
       if   (transformed) return transformedBounds;
       else               return originalBounds;       
    }
//]bounds

//[render.
    /**
     * Render this sampled visual content
     * (required by Content)
     *
     * @param g   The rendering engine to use
     */
    public void render(Graphics g)
    {
       Composite      oldComposite;
       Graphics2D     g2;
       
       g2 = (Graphics2D)g;


       if (originalImage != null)
       {
          oldComposite = g2.getComposite();
          if (composite != null) g2.setComposite(composite);       
          

          // Transform the Image (if necessary)
          if (isTransformationRequired())
          {
             createTransformedContent();
          }

          // Render the image
          if (!rotatable)
             g2.drawImage(transformedImage,(int)x,(int)y,null);          
          else
             g2.drawImage(transformedImage,0,0,null);          


          g2.setComposite(oldComposite);       
       }       
    }
//]render.


    /**
     * Set the BufferedImage
     *
     * @param image  The BufferedImage (or null to render nothing)
     */
    public void setImage(BufferedImage image)
    {
       this.originalImage = image;       

       if (image == null)
       {
          originalBounds    = new Rectangle2D.Double(0., 0., 
                                                     0., 0.);
       }
       else
       {
          originalBounds    = new Rectangle2D.Double(x, y, 
                                           originalImage.getWidth(), 
                                           originalImage.getHeight());
       }


       transformedBounds = new Rectangle2D.Double(originalBounds.x,
                                                  originalBounds.y,
                                                  originalBounds.width,
                                                  originalBounds.height);
    }

    
//[set.
    /**
     * Set the BufferedImageOp to use when transforming
     * the Image
     *
     * @param op   The BufferedImageOp
     */
    public void setBufferedImageOp(BufferedImageOp op)
    {
       imageOp    = op;       
       refiltered = true;
    }
    


    /**
     * Set the transparency/Composite
     *
     * @param c   The Composite
     */
    public void setComposite(Composite c)
    {
       composite = c;;
    }


    /**
     * Set the location
     *
     * Note: This method overrides the version in the parent because
     * the translation of sampled content does not require 
     * transformation
     *
     * @param x   The x position
     * @param y   The y position
     */
    public void setLocation(double x, double y)
    {
       if (!rotatable)
       {
          this.x = x;
          this.y = y;

          transformedBounds.x      = this.x;
          transformedBounds.y      = this.y;
       }
       else
       {
          super.setLocation(x, y);          
       }
    }



    /**
     * Set the rotation
     *
     * Note: This method overrides the version in the parent because
     * efficiency can be improved when rotations are
     * prevented
     *
     * @param angle  The rotation angle
     * @param x       The x-coordinate of the point to rotate around
     * @param y       The y-coordinate of the point to rotate around
     */
    public void setRotation(double angle, double x, double y)
    {
       if (rotatable) super.setRotation(angle, x, y);       
    }
//]set.


    /**
     * Set whether this AbstractTransformableContent object
     * needs to be transformed before being used
     *
     * @param required   true to indicate that a transformation is requried
     */
    protected void setTransformationRequired(boolean required)
    {
       refiltered = required;
       super.setTransformationRequired(required);
    }


    /**
     * Does this AbstractTransformableContent object need
     * to be transformed before being used?
     *
     * @return  true to indicate a transformation is required
     */
    protected boolean isTransformationRequired()
    {
       return (refiltered || super.isTransformationRequired());
    }


//[skeleton3.
}
//]skeleton3.
