/* @(#) ImageCanvas.java 1.1 99/11/18
 * Copyright (c) 1999 Lawrence Rodrigues
 */
package com.vistech.imageviewer;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

import java.net.*;
import java.util.*;
import java.beans.*;
import javax.swing.*;
import java.awt.print.*;
import java.awt.geom.*;
import java.beans.beancontext.*;
import com.sun.image.codec.jpeg.*;
import com.vistech.events.*;

/** A canvas class for displaying images. It can display the AWT Image as well as the BufferedImage
 * objects.
 * @version 1.0  18 Nov 1999
 * @author Lawrence Rodrigues
 **/
public class ImageCanvas extends JComponent
                       implements ImageDisplay, ImagePrint{

    protected static final int PRINT_SIZE_PAGE =1;
    protected static final int PRINT_SIZE_ACTUAL =2;
    protected int printMode = PRINT_SIZE_PAGE;

    protected int originalImageType = ImageDisplay.TYPE_AWT_IMAGE;

    transient Image awtImage;
    transient public BufferedImage bufferedImage, offScrImage, displayImage;
    transient protected Graphics2D offScrGc, dispGc;

    protected Point printLoc = new Point(0,0);
    protected Dimension printSize= null;

    protected int width, height;
    protected int imageWidth, imageHeight;
    protected int displayMode = DisplayMode.SCALED;
    protected int flipMode = FlipMode.NORMAL;
    protected int imageType = BufferedImage.TYPE_INT_RGB;
    protected Shape clipShape = null;
    protected boolean invertOn = false;
    protected boolean paintFlag = false;

    private Vector imageSentListeners = new Vector();

    public ImageCanvas(){ init();}

    protected void init(){
       setDoubleBuffered(false);
       setBackground(Color.black);
       setForeground(Color.white);
       this.setSize(200,150);
    }

    public Dimension getPreferredSize(){
       if((imageHeight >1) && (imageWidth >1)) return new Dimension(imageWidth,imageHeight);
       return new Dimension(400,300);
    }

    public void setAWTImage(Image image){
       reset();
       Image oldImage = this.awtImage;
       this.awtImage = image;
       originalImageType = ImageDisplay.TYPE_AWT_IMAGE;

       imageWidth = this.awtImage.getWidth(this);
       imageHeight = this.awtImage.getHeight(this);
       if((imageWidth <=0)||(imageHeight<=0)) paintFlag = true;
       else {
         firePropertyChange("awtImage",this.awtImage,oldImage);
         bufferedImage =
             convertToBufferedImage(image, imageWidth, imageHeight, imageType);
         paintImage();
         repaint();
       }
    }
    public Image getAWTImage(){ return awtImage; }

    public void setBufferedImage(BufferedImage bimage){
       reset();
       BufferedImage oldImage = this.bufferedImage;
       this.bufferedImage = bimage;
       originalImageType = ImageDisplay.TYPE_BUFFERED_IMAGE;

       imageWidth = bufferedImage.getWidth();
       imageHeight = bufferedImage.getHeight();

       firePropertyChange("bufferedImage",this.bufferedImage,oldImage);

       this.awtImage = convertToAWTImage(bimage);
       paintImage();
       repaint();
    }

    public BufferedImage getBufferedImage(){ return bufferedImage; }

    public Dimension getImageSize(){
       if(offScrImage == null) return new Dimension(-1,-1);
       return new Dimension(offScrImage.getWidth(),
                            offScrImage.getHeight());
    }

    public static BufferedImage convertToBufferedImage(Image img,
                                     int wid, int ht, int type){
       BufferedImage bi = new BufferedImage(wid, ht, type);
       Graphics2D g2d = bi.createGraphics();
       g2d.drawImage(img, 0,0,wid, ht, null);
       return bi;
    }

    public static Image convertToAWTImage(BufferedImage bufImage) {
       AffineTransformOp aop = new AffineTransformOp(new AffineTransform(),
                                                     AffineTransformOp.TYPE_BILINEAR);
       BufferedImageFilter bif = new BufferedImageFilter(aop);
       FilteredImageSource fsource = new FilteredImageSource(bufImage.getSource(), bif);
       Image img = Toolkit.getDefaultToolkit().createImage(fsource);
       return img;
    }

    public void setOffScreenImage(BufferedImage image){
       if(image == null) return;
       firePropertyChange("offScreenImage",this.offScrImage,image);
       this.offScrImage = image;
       offScrGc = offScrImage.createGraphics();
       repaint();
    }
    public BufferedImage getOffScreenImage() { return offScrImage; }

    public void setDisplayImage(BufferedImage image){
       if(image == null) return;
       firePropertyChange("displayImage",this.displayImage,image);
       this.displayImage = image;
       dispGc = displayImage.createGraphics();
       repaint();
    }

    public BufferedImage getDisplayedImage(){ return displayImage; }

    public Graphics2D getDisplayedImageGC(){ return dispGc; }

    public void setImageType(int imageType){ this.imageType = imageType; }

    public int getImageType(){ return imageType; }

    public synchronized void setDisplayMode(int mode){
       if(mode == displayMode) return;
       int oldmode = displayMode;
       displayMode = mode;
       firePropertyChange("displayMode", oldmode, mode);
       reset();
       paintImage();
       repaint();
    }

    public int getDisplayMode(){ return displayMode; }

    public synchronized void setFlipMode(int mode){
       if(mode == flipMode) return;
       int oldmode = flipMode;
       flipMode = mode;
       firePropertyChange("flipMode",oldmode, mode);
       reset();
       paintImage();
       repaint();
    }

    public int getFlipMode(){ return flipMode; }

    public void setInvert(boolean onOrOff){
       firePropertyChange("image", new Boolean(invertOn), new Boolean(onOrOff));
       invertOn = onOrOff;
       invert(invertOn);
    }

    public boolean getInvert() { return invertOn;}

    /** Creates the offScreen image. The ImageCanvas has to be visible
      * for the offscreen image to be visible.
      **/
    protected void createDisplayImage(){
       displayImage = new BufferedImage(width,height,imageType);
       setOffScrGc();
    }

    /** Sets off screen Graphics. The ImageCanvas has to be visible
      * for the off screen screen to be available
      **/
    protected void setOffScrGc(){
       if(displayImage!= null){
          dispGc = displayImage.createGraphics();
          dispGc.setColor(getBackground());
          dispGc.fillRect(0,0, width, height);
          dispGc.setColor(getForeground());
          dispGc.setRenderingHint(RenderingHints.KEY_RENDERING,
                                  RenderingHints.VALUE_RENDER_QUALITY);
       }
    }

    public Dimension drawingDim(){
       Rectangle d = this.getBounds();
       width = d.width;
       height = d.height;
       double magX= (double)imageWidth/(double)width;
       double magY = (double)imageHeight/(double)height;
       int dwidth, dheight;
       if(magY >  magX){
          Double db = new Double(imageWidth/magY);
          dwidth = db.intValue();
          dheight = height;
       }else{
          Double db = new Double(imageHeight/magX);
          dheight = db.intValue();
          dwidth = width;
       }
       return new Dimension(dwidth, dheight);
    }

    /** Paints the currently set image. Applies the current display mode
      * and filp modes before painting.
      * */
     public boolean paintImage() {
       if((awtImage != null) || (bufferedImage != null))
          return doDisplayModeAndFlip(imageWidth, imageHeight);
       else return false;
    }

    /** Applies the current display mode and flip mode settings to the displayed image.
      * First applies the display mode and then calls the doFlip() method to apply the
      * flip mode to the resulting image. The display mode and flip mode settings are applied by
      * using the appropriate drawImage() method (of Graphics class).
      * @param imWid the image width.
      * @param imHt the image height.
      * @paran if successful returns true.
      * @return true if successful.
      * @see com.vistech.imageviewer.DisplayMode.
      * @see com.vistech.imageviewer.FlipMode.
      */
    protected synchronized boolean doDisplayModeAndFlip(int imWid, int imHt){
       width = this.getBounds().width;
       height = this.getBounds().height;
       //To-fit case
       int dx1 = 0;
       int dy1 = 0;
       int dx2 = width;
       int dy2 = height;
       int sx1=0;
       int sy1=0;
       int sx2=imageWidth;
       int sy2=imageHeight;

       switch(displayMode){
        case DisplayMode.ORIG_SIZE:
          dx2 = imageWidth;
          dy2 = imageHeight;
          break;
        case DisplayMode.SCALED:
          Dimension d = drawingDim();
          dx2 = d.width;
          dy2 = d.height;
          break;
        case DisplayMode.TO_FIT:
          default:
          break;
       }
       int bufferWid = dx2-dx1;
       int bufferHt = dy2-dy1;
       offScrImage = new BufferedImage(bufferWid, bufferHt,imageType);
       offScrGc = offScrImage.createGraphics();
       offScrGc.setColor(Color.black);
       offScrGc.fillRect(0,0,imageWidth,imageHeight);

       if(originalImageType == ImageDisplay.TYPE_AWT_IMAGE)
          if(awtImage != null) doFlip(awtImage, offScrGc, flipMode,
                 dx1, dy1, dx2, dy2, sx1,sy1, sx2, sy2);
       else if(bufferedImage != null) doFlip(bufferedImage,offScrGc,flipMode,
                   dx1, dy1, dx2, dy2, sx1,sy1, sx2, sy2);

       if(dispGc == null) createDisplayImage();
       dispGc.setColor(Color.black);
       dispGc.fillRect(0,0,width,height);
       invert(invertOn);
       return true;
    }

    /** Flips an AWT image. Draws the resulting image on the specified graphics context.
      * @param img the image to be flipped.
      * @param gc the graphics context.
      * @param flipMode the orientation of the flip.
      * @see com.vistech.imageviewer.FlipMode.
      * @param dx1 the x coordinate of the first corner of the destination image.
      * @param dy1 the y coordinate of the first corner of the destination image.
      * @param dx2 the x coordinate of the second corner of the destination image.
      * @param dy2 the y coordinate of the second corner of the destination image.
      * @param dx1 the x coordinate of the first corner of the source image.
      * @param dy1 the y coordinate of the first corner of the source image.
      * @param dx2 the x coordinate of the second corner of the source image.
      * @param dy2 the y coordinate of the second corner of the source image.
      * @return true if successful.
      **/

    public static boolean doFlip(Image img, Graphics gc,int flipMode,
                               int dx1, int dy1, int dx2, int dy2,
                               int sx1, int sy1, int sx2, int sy2){
       switch(flipMode){
          case FlipMode.NORMAL:
            //Nothing is reversed.
            return gc.drawImage(img,dx1, dy1, dx2, dy2, sx1,sy1, sx2, sy2,null);
          case FlipMode.TOP_BOTTOM:
            //Y coordinates of the source image are reversed.
            return gc.drawImage(img,dx1, dy1, dx2, dy2, sx1,sy2, sx2, sy1,null);
          case FlipMode.LEFT_RIGHT:
             // X coordinates of the source image are reversed.
            return gc.drawImage(img,dx1, dy1, dx2, dy2, sx2,sy1, sx1, sy2,null);
          case FlipMode.TOP_BOTTOM_LEFT_RIGHT:
            //Both  the X and Y coordinates of the source image are reversed.
            return gc.drawImage(img,dx1, dy1, dx2, dy2, sx2,sy2, sx1, sy1,null);
          default:
            return false;
       }
    }

    /** Flips a buffered image. Draws the resulting image on the specified graphics context. 
      * @param img the image to be flipped.@see FlipMode
      * @param gc the graphics context.
      * @param flipMode the orientation of the flip.
      * @param dx1 the x coordinate of the first corner of the destination image.
      * @param dy1 the y coordinate of the first corner of the destination image.
      * @param dx2 the x coordinate of the second corner of the destination image.
      * @param dy2 the y coordinate of the second corner of the destination image.
      * @param dx1 the x coordinate of the first corner of the source image.
      * @param dy1 the y coordinate of the first corner of the source image.
      * @param dx2 the x coordinate of the second corner of the source image.
      * @param dy2 the y coordinate of the second corner of the source image.
      **/

    public static boolean doFlip(BufferedImage img, Graphics gc,int flipMode,
                               int dx1, int dy1, int dx2, int dy2,
                               int sx1, int sy1, int sx2, int sy2){
       switch(flipMode){
          case FlipMode.NORMAL:
            //Nothing is reversed.
            return gc.drawImage(img,dx1, dy1, dx2, dy2, sx1,sy1, sx2, sy2,null);
          case FlipMode.TOP_BOTTOM:
            //Y coordinates of the source image are reversed.
            return gc.drawImage(img,dx1, dy1, dx2, dy2, sx1,sy2, sx2, sy1,null);
          case FlipMode.LEFT_RIGHT:
             // X coordinates of the source image are reversed.
            return gc.drawImage(img,dx1, dy1, dx2, dy2, sx2,sy1, sx1, sy2,null);
          case FlipMode.TOP_BOTTOM_LEFT_RIGHT:
            //Both  the X and Y coordinates of the source image are reversed.
            return gc.drawImage(img,dx1, dy1, dx2, dy2, sx2,sy2, sx1, sy1,null);
          default:
            return false;
       }
    }

    /** Inverts and image.
      * @param on If true, inverts the currently displayed image, else redraws
      * the current image. Set the input to true to invert the image. Set the input
      * to false to revert to the normal display mode.
      **/

    public void invert(boolean on){
       if(offScrImage == null) return;
       BufferedImage bi = null;
       if(on) {
          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);
          bi = lookupop.filter(offScrImage, bi);
       } else bi =  offScrImage;
       createDisplayImage();
       dispGc.drawImage(bi, 0,0, this);
       repaint();
   }

    public void setClip(Shape clip) {
       clipShape = clip;
       if(dispGc != null) dispGc.setClip(clip);
    }

    public void draw(Shape shape) {
       if(dispGc != null) dispGc.draw(shape);
    }

    public boolean imageUpdate(Image img, int infoflags, int x, int y, int wid, int ht){
       if((infoflags & ImageObserver.ERROR) != 0){ return false; }
       if((infoflags & (ImageObserver.WIDTH | ImageObserver.HEIGHT))!= 0){
           this.imageWidth = wid;
           this.imageHeight = ht;
       }
       if((infoflags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS))!= 0){
           if(paintFlag) {
              paintFlag = false;
              this.imageWidth = wid;
              this.imageHeight = ht;
              Image oldImage = this.awtImage;
              this.awtImage = img;

              firePropertyChange("awtImage",this.awtImage,oldImage);
              bufferedImage = convertToBufferedImage(awtImage, wid, ht, imageType);
              paintImage();
              repaint();
           }
           return false;
        }
        return true;
    }

    public void setPrintLocation(Point d) {
       printLoc = d;
    }

    public Point getPrintLocation() {
       return printLoc;
    }

    public void setPrintSize(Dimension d){
       printSize = d;
    }

    public Dimension getPrintSize(){
       if(printSize == null) {
          Rectangle r = this.getBounds();
          printSize = new Dimension(r.width, r.height);
       }
       return printSize;
    }

    public int print(Graphics g, PageFormat f, int pageIndex){
       if(pageIndex >= 1) return Printable.NO_SUCH_PAGE;
       Graphics2D g2d = (Graphics2D) g;
       g2d.translate(f.getImageableX(), f.getImageableY());
       if(displayImage != null){
          printImage(g2d);
          return Printable.PAGE_EXISTS;
       } else  return Printable.NO_SUCH_PAGE;
    }

    public void printImage(Graphics2D g2d){
       if((displayImage == null)|| (g2d == null)) return;
       if(printLoc == null) printLoc = new Point(0,0);
       if(printSize == null) printSize = new Dimension(imageWidth, imageHeight);
       int x = printLoc.x;
       int y = printLoc.y;
       int wid = printSize.width;
       int ht = printSize.height;
       g2d.drawImage(displayImage,x,y,wid,ht, this);
    }

    public void paintComponent(Graphics gc){
       super.paintComponent(gc);
       Graphics2D g = (Graphics2D)gc;
       Rectangle d = getBounds();
       if((d.width != width)|| (d.height != height)){
           width = d.width;
           height = d.height;
           redraw();
       }
       if(displayImage!= null){
          g.drawImage(displayImage,0,0,this);
       }else createDisplayImage();
    }

    /** creates a new off screen image and then redraws the current image over it.
      **/
    protected void redraw() {
        createDisplayImage();
        paintImage();
        repaint();
    }

    /** Paints the currently displayed image again with currently set display and flip modes
      */
    public void reset(){
       paintImage();
    }

    /** Clears the image canvas. The currently set image object becomes null.
      * To display a new image, set the image by calling setAWTImage() or setBufferedImage() with
      * the new image.
      */
    public void clearImageCanvas() {
      if(awtImage != null) {
         awtImage.flush();
         awtImage = null;
      }
      if(bufferedImage != null) {
         bufferedImage.flush();
         bufferedImage = null;
      }
      createDisplayImage();
      repaint();
    }

    public void addImageSentListener(ImageSentListener p){
       imageSentListeners.add(p);
    }

    public void removeImageSentListener(ImageSentListener p){
       imageSentListeners.remove(p);
    }

    protected void fireImageSentEvents(BufferedImage img) {
       ImageSentEvent ise = new ImageSentEvent(this,img);
       for(Enumeration e = imageSentListeners.elements(); e.hasMoreElements();){
           ImageSentListener ist = (ImageSentListener)(e.nextElement());
           ist.imageSent(ise);
       }
    }
    //------------------Serialization Methods ---------------------------

    private void writeObject(ObjectOutputStream out)throws IOException{
       //Set loaded to be false; need to load the images again
       //loaded = false;
       //Save all the non transient, non-static fields
       out.defaultWriteObject();
    }

    private void readObject(ObjectInputStream in)
                     throws IOException, ClassNotFoundException{
       //Read all the default fields
       in.defaultReadObject();
       //Now load the images;
       //if((loadedFrom == JAR) && (loader != null)){
       //   loader.loadJarImages();
       //   loaded = true;
       //}
    }
    public static void  main(String[] args){
        ImageCanvas iv = new ImageCanvas();
        JFrame fr = new JFrame();
        fr.addWindowListener(
            new WindowAdapter(){
               public void windowClosing(WindowEvent e){
                  System.exit(0);
               }
             }
        );

        fr.setTitle("ImageCanvas ");
        fr.getContentPane().add(iv);
        fr.pack();
        fr.setSize(400,300);
        fr.show();
        iv.paintComponent(iv.getGraphics());
        fr.repaint();
    }
 }

