
/* @(#) ImageCanvas2D.java 2.0 2001/01/11
 * Copyright (c) 2001 Lawrence Rodrigues
 */
package com.vistech.imageviewer;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

import java.util.*;
import java.beans.*;
import javax.swing.*;
import java.awt.geom.*;
import com.vistech.events.*;

/** Displays a manipulated image.
 * This class doesn't perform image manipulation, but has
 * several properties to hold the manipulation attributes.
 * To manipulate an image, check out the AffineTransform attribute, concatenate it,
 * and then check it in. Call the applyTransform() method to apply the resultinng transformation
 * to the current image.
 * @version 2.0  11 Nov 2001
 * @author Lawrence Rodrigues
 **/
public class ImageCanvas2D extends ImageCanvas implements
                         ImageManipulator{

   protected AffineTransform atx = new AffineTransform();
   protected AffineTransform dispModeAtx = new AffineTransform();
   protected AffineTransform flipAtx = new AffineTransform();

   //Pan variables
   protected Point panOffset = new Point(0,0);
   //zoom variables
   protected boolean magOn = true;
   protected double magFactor = 1.0;
   protected double magFactorX =0.0, magFactorY=0.0;

   protected int magCenterX = 0;
   protected int magCenterY =0;
   protected Point zoomOffset = new Point(0,0);
   //Rotation variables
   protected double rotationAngle = 0.0;
   protected boolean rotateOn = true;
   protected int rotationCenterX = 0;
   protected int rotationCenterY = 0;
   //Shear variables
   protected boolean shearOn = true;
   protected double shearFactor = 0.0;
   protected double shearX =0.0, shearY=0.0;
   //Off screen image width and height
   private int bufImageWidth = -1;
   private int bufImageHeight = -1;
   protected int interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;

   public ImageCanvas2D(){ init();}

   protected void init(){
      setDoubleBuffered(false);
      setBackgroundColor(Color.black);
      setForegroundColor(Color.white);
      this.setSize(400,300);
   }

   public void setMagFactor(double magFactor){
      firePropertyChange("MagFactor",
                          new Double(this.magFactor),
                          new Double(magFactor));
      this.magFactor = magFactor;
   }
   public double getMagFactor(){ return magFactor;}

   public void setMagFactorX(double magFactorX){
      firePropertyChange("MagFactorX",
                          new Double(this.magFactorX),
                          new Double(magFactorX));
      this.magFactorX = magFactorX;
   }
   public double getMagFactorX(){ return magFactorX;}

   public void setMagFactorY(double magFactorY){
      firePropertyChange("MagFactorY",
                          new Double(this.magFactorY),
                          new Double(magFactorY));
      this.magFactorX = magFactorY;
   }
   public double getMagFactorY(){ return magFactorY;}

   public void setShearFactor(double shearFactor){
      firePropertyChange("ShearFactor",
                         new Double(this.shearFactor),
                         new Double(shearFactor));
      this.shearFactor = shearFactor;
   }

   public double getShearFactor(){ return shearFactor; }
   public double getShearFactorX(){ return shearX;}
   public double getShearFactorY(){ return shearY;}

   public void setRotationAngle(double rotationAngle){
      firePropertyChange("RotationAngle",
                          new Double(this.rotationAngle),
                          new Double(rotationAngle));
      this.rotationAngle = rotationAngle;
   }
   public double getRotationAngle(){ 
      return rotationAngle; 
   }

   public void setPanOffset(Point panOffset){
      firePropertyChange("PanOffset",
                          this.panOffset,
                          panOffset);
      this.panOffset = panOffset;
   }

   public Point getPanOffset(){ return panOffset; }

   public void setInterpolationType(int interType) {interpolationType = interType;}
   public int getInterpolationType() { return interpolationType;}

   public AffineTransform getTransform(){ return atx; }
   public void setTransform(AffineTransform at){ atx = at; }

   public  void setMagOn(boolean onOff){ magOn = onOff;}
   public  boolean getMagOn(){ return magOn;}

   /** Applies the current display mode and flip mode settings to the displayed image.
      * The display mode and flip mode settings are applied by
      * using the affine transformation.
      * First computes an affine transformation for current display mode setting.
      * It then creates a BufferedImage object size of which is compted using this transformation,
      * and draws the current image on this BufferedImage.
      *
      * This method then calls the createFlipTransform() method to create an affine transformtion
      * for the current flip mode setting. It then applies the flips transforamtion to the BufferedImage
      * object, and then displays it.
      * @param imWid the image width.
      * @param imHt the image height.
      * @paran if successful returns true.
      */
   public synchronized boolean doDisplayModeAndFlip(int imageWidth, 
                                                    int imageHeight)
                                throws ImageCanvasException {
      boolean painted = false;
      try {
      width = this.getBounds().width;
      height = this.getBounds().height;
      //2.0 changes
      if((imageWidth <0)|| (imageHeight <0)) return false;
      double magX= (double)width/(double)imageWidth;
      double magY = (double)height/(double)imageHeight;
      int bufferWid = width, bufferHt=height;
      dispModeAtx = new AffineTransform();
      switch(displayMode){
        case DisplayMode.ORIG_SIZE:
          bufferWid = imageWidth;
          bufferHt = imageHeight;
          break;
        case DisplayMode.SCALED:
          double mag= (magY > magX)? magX:magY;
          dispModeAtx.setToScale(mag, mag);
          bufferWid = (int)(imageWidth*mag);
          bufferHt = (int)(imageHeight*mag);
          break;
      case DisplayMode.TO_FIT:
          dispModeAtx.setToScale(magX, magY);
          bufferWid = width;
          bufferHt = height;
          default:
          break;
      }
      BufferedImage bi = new BufferedImage(bufferWid, bufferHt,imageType);
      Graphics2D bigc = bi.createGraphics();
      if(originalImageType == TYPE_AWT_IMAGE) bigc.drawImage(awtImage, dispModeAtx, this);
      else bigc.drawImage(bufferedImage, dispModeAtx, this);

      flipAtx = createFlipTransform(flipMode, bufferWid, bufferHt);
      AffineTransformOp atop = new AffineTransformOp(flipAtx, interpolationType);
      if(offScrImage != null) offScrImage.flush();

      offScrImage = new BufferedImage(bufferWid, bufferHt, imageType);// 2.0 changes
      offScrGc = offScrImage.createGraphics();
      offScrGc.setColor(getBackgroundColor());
      offScrGc.fillRect(0,0,bufferWid, bufferHt);
      offScrGc.setRenderingHint(RenderingHints.KEY_RENDERING,
                                  RenderingHints.VALUE_RENDER_QUALITY);
      offScrImage =  atop.filter(bi, offScrImage); //2.0 changes

      //2.0 changes 
      if(getInvert()) {
          BufferedImage invertImage = null;
          byte lut[] = new byte[256];
          for (int j = 0; j < 256 ; j++) lut[j] = (byte) (256-j);
          LookupOp lookupop = new LookupOp(new ByteLookupTable(0,lut), null);
          invertImage = lookupop.filter(offScrImage, invertImage);
          offScrImage = invertImage;
      } 
      applyTransform(offScrImage, atx);
      repaint();
      painted = true;
      } catch (Exception e) {//2.0 changes
         painted = false;
         ImageCanvasException icane= new ImageCanvasException("Failed to apply display or flip mode");
         throw(icane);
      } finally {
         return painted;
      }
   }

   /** Creates a flip transform. It first creates a reflection and then translates to
     * the current quadrant.
     * @param mode the specified flip mode.
     * @param imageWid the width of the BufferedImage.
     * @param imageHt  the height of the BufferedImage.
     * @return the flip transformation encapsulated in the AffineTransform object.
    */
   static public AffineTransform createFlipTransform(int mode,
                                                    int imageWid,
                                                    int imageHt){
       AffineTransform at = new AffineTransform();
       switch(mode){
            case FlipMode.NORMAL:
              break;
            case FlipMode.TOP_BOTTOM:
              at =  new AffineTransform(new double[] {1.0,0.0,0.0,-1.0});
              at.translate(0.0, -imageHt);
              break;
            case FlipMode.LEFT_RIGHT :
              at = new AffineTransform(new double[] {-1.0,0.0,0.0,1.0});
              at.translate(-imageWid, 0.0);
              break;
            case FlipMode.TOP_BOTTOM_LEFT_RIGHT:
              at = new AffineTransform(new double[] {-1.0,0.0,0.0,-1.0});
              at.translate(-imageWid, -imageHt);
              break;
            default:
       }
       return at;
   }

   /** Applies a specified transformation to the input image, and then displays it on the canvas
     * @param bi the buffered image to which the transformation is applied.
     * @param atx the transformation to be applied.
     */
   protected void applyTransform(BufferedImage bi, 
                                 AffineTransform atx) throws ImageCanvasException{
      if(offScrImage == null) return;
      try {
      if(displayImage == null) createDisplayImage();
      AffineTransformOp atop = new AffineTransformOp(atx, interpolationType);
      dispGc.setColor(getBackgroundColor()); //2.0 change
      dispGc.fillRect(0,0,displayImage.getWidth(), displayImage.getHeight());
      dispGc.setClip(clipShape);
      //2.0 changes
 
      if(clipShape == null) atop.filter(bi, displayImage);
      else dispGc.drawImage(bi,atx,this);
      } catch (Exception e){
          ImageCanvasException cane = new ImageCanvasException("Appy Transform");
          throw(cane);
      }
   }

   /** Applies a specified transformation to the current off screen image,
     * and then displays it on the canvas.
     * @param atx the transformation to be applied.
     */
   public void applyTransform(AffineTransform atx){
      try {
        applyTransform(offScrImage, atx);
        this.atx = atx;
        repaint();
      }catch (Exception e) {
         e.printStackTrace();
      }
   }

   public void reset(){
       panOffset = new Point(0,0);
       setRotationAngle(0.0);
       setMagFactorX(1.0);
       setMagFactorY(1.0);
       setMagFactorY(1.0);
       setShearFactor(0.0);
       shearX = 0.0; shearY = 0.0;
       atx = new AffineTransform();
       paintImage();
   }

   /** Resets the manipulation attributes.
     * Applies the default attributes to display the current image.
     */
    public void resetManipulation(){
       reset();
       repaint();
   }

   public static void  main(String[] args){
        ImageCanvas2D iv = new ImageCanvas2D();
        JFrame fr = new JFrame();
        fr.addWindowListener(
            new WindowAdapter(){
               public void windowClosing(WindowEvent e){
                  System.exit(0);
               }
             }
        );
        fr.setTitle("ImageCanvas2D Bean");
        fr.getContentPane().add(iv);
        fr.pack();
        fr.setSize(400,300);
        fr.show();
        iv.paintComponent(iv.getGraphics());
        fr.repaint();
    }
}

