/**
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info:  http://www.jpedal.org
* (C) Copyright 1997-2008, IDRsolutions and Contributors.
* Main Developer: Simon Barnett
*
*   This file is part of JPedal
*
* Copyright (c) 2008, IDRsolutions
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the IDRsolutions nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY IDRsolutions ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL IDRsolutions BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Other JBIG2 image decoding implementations include
* jbig2dec (http://jbig2dec.sourceforge.net/)
* xpdf (http://www.foolabs.com/xpdf/)

* The final draft JBIG2 specification can be found at http://www.jpeg.org/public/fcd14492.pdf

* All three of the above resources were used in the writing of this software, with methodologies,
* processes and inspiration taken from all three.
*
* ---------------
* JBIG2Viewer.java
* ---------------
*/
package org.jpedal.jbig2.examples.viewer;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.PixelGrabber;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager;

import org.jpedal.jbig2.JBIG2Decoder;
import org.jpedal.jbig2.JBIG2Exception;
import org.jpedal.jbig2.image.JBIG2Bitmap;

public class JBIG2Viewer extends JFrame {

  private JFrame mainFrame = this;

  private JScrollPane jsp;

  private BufferedImage image;
  private JLabel imageLabel = new JLabel();

  private JComboBox scalingBox;
  private String scalingItem = "";
  private double scaling;

  private JComboBox rotationBox;
  private String rotationItem = "";
  private int rotation;

  private NavigationToolbar navToolbar;

  private JBIG2Decoder decoder;

  private int currentPage;

  public static void main(String[] args) {

    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
      new JBIG2Viewer();
    catch (Exception e) {
      e.printStackTrace();
    }

  }

  public JBIG2Viewer() {
    setSize(500500);
    getContentPane().setLayout(new BorderLayout());
    setLocationRelativeTo(null);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    getContentPane().setLayout(new BorderLayout());

    setUpToolbar();

    imageLabel.setHorizontalAlignment(JLabel.CENTER);
    imageLabel.setVerticalAlignment(JLabel.TOP);

    jsp = new JScrollPane(imageLabel);

    getContentPane().add(jsp, BorderLayout.CENTER);

    navToolbar = new NavigationToolbar(this);
    navToolbar.setFloatable(false);
    getContentPane().add(navToolbar, BorderLayout.SOUTH);

    setTitle("JPedal JBIG2 Image Decoder");
    
    setVisible(true);
  }

  private void setUpToolbar() {
    // Create a horizontal toolbar
    JToolBar toolbar = new JToolBar();
    toolbar.setFloatable(false);
    toolbar.setBorder(BorderFactory.createEtchedBorder());

    JButton openButton = new JButton(new AbstractAction("Open"new ImageIcon(getClass().getResource("/org/jpedal/jbig2/examples/viewer/res/open.png"))) {
      public void actionPerformed(ActionEvent evt) {
        openFile();
      }
    });
    openButton.setText(null);
    openButton.setToolTipText("Open New File");
    toolbar.add(openButton);

    toolbar.add(Box.createRigidArea(new Dimension(70)));

    JButton saveButton = new JButton(new AbstractAction("Save"new ImageIcon(getClass().getResource("/org/jpedal/jbig2/examples/viewer/res/save.png"))) {
      public void actionPerformed(ActionEvent evt) {
        if (image == null) {
          JOptionPane.showMessageDialog(mainFrame, "No image is open");
        else {
          saveFile();
        }
      }
    });
    saveButton.setText(null);
    saveButton.setToolTipText("Save File As");
    toolbar.add(saveButton);

    toolbar.add(Box.createRigidArea(new Dimension(70)));

    JButton propertiesButton = new JButton(new AbstractAction("Properties"new ImageIcon(getClass().getResource("/org/jpedal/jbig2/examples/viewer/res/properties.png"))) {
      public void actionPerformed(ActionEvent evt) {
        // Perform action
      }
    });
    propertiesButton.setText(null);
    propertiesButton.setToolTipText("File Properties");
//    toolbar.add(propertiesButton);

    toolbar.add(Box.createRigidArea(new Dimension(70)));

    toolbar.add(new JLabel("Zoom:"));
    toolbar.add(Box.createRigidArea(new Dimension(30)));

    ActionListener actionListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        setScalingAndRotation();
      }
    };

    scalingBox = new JComboBox(new String[] { "Window""Height""Width""25""50""75""100""125""150""200""250""500""750""1000" });
    scalingBox.setEditable(true);
    scalingBox.setPreferredSize(new Dimension(scalingBox.getPreferredSize().width, toolbar.getHeight()));
    scalingBox.setPrototypeDisplayValue("XXXXXXXX")// Set a desired
                              // width
    scalingBox.setMaximumSize(new Dimension(100100));
    toolbar.add(scalingBox);
    toolbar.add(Box.createRigidArea(new Dimension(70)));

    toolbar.add(new JLabel("Rotation:"));
    toolbar.add(Box.createRigidArea(new Dimension(30)));

    rotationBox = new JComboBox(new String[] { "0""90""180""270" });
    rotationBox.setEditable(true);
    rotationBox.setPreferredSize(new Dimension(rotationBox.getPreferredSize().width, toolbar.getHeight()));
    rotationBox.setMaximumSize(new Dimension(100100));
    toolbar.add(rotationBox);

    getContentPane().add(toolbar, BorderLayout.NORTH);

    rotationBox.addActionListener(actionListener);
    scalingBox.addActionListener(actionListener);
  }

  private void saveFile() {
    JFileChooser chooser = new JFileChooser();
    chooser.addChoosableFileFilter(new FileFilterer(new String[] { "png" }"PNG (*.png)"));
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    int approved = chooser.showSaveDialog(null);
    if (approved == JFileChooser.APPROVE_OPTION) {
      File file = chooser.getSelectedFile();
      String fileToSave = file.getAbsolutePath();

      if (!fileToSave.toLowerCase().endsWith(".png")) {
        file = new File(fileToSave + ".png");
      }

      try {
        ImageIO.write(image, "png", file);
      catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  private void openFile() {
    JFileChooser open = new JFileChooser(".");
    open.setFileSelectionMode(JFileChooser.FILES_ONLY);
    String[] png = new String[] { "jb2""jbig2" };
    open.addChoosableFileFilter(new FileFilterer(png, "JBIG2 (jb2, jbig2)"));

    int resultOfFileSelect = open.showOpenDialog(mainFrame);

    if (resultOfFileSelect == JFileChooser.APPROVE_OPTION) {

      /** reset rotation */
      rotation = 0;
      rotationItem = "0";
      rotationBox.setSelectedItem(rotationItem);

      /** reset scaling */
      scaling = 1;
      scalingItem = "Window";
      scalingBox.setSelectedItem(scalingItem);

      decoder = new JBIG2Decoder();
      try {
        decoder.decodeJBIG2(open.getSelectedFile());
      catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      catch (JBIG2Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }

//      navToolbar.open();
      navToolbar.setCurrentPage(1);
      navToolbar.setTotalNoOfPages(decoder.getNumberOfPages());

      currentPage = 1;

      image = decoder.getPageAsBufferedImage(currentPage - 1);

      setScalingAndRotation();
    }
  }

  private void setScalingAndRotation() {
    if (image == null)
      return;

    String selectedScaling = (StringscalingBox.getSelectedItem();

    if (selectedScaling.equals("Window")) {
      int windowWidth = jsp.getWidth();
      int windowHeight = jsp.getHeight();

      int imageWidth = image.getWidth();
      int imageHeight = image.getHeight();
      
      if (windowWidth / (doubleimageWidth < windowHeight / (doubleimageHeight) { // scale by width
        scaleToWidth();
      else // scale by height
        scaleToHeight();
      }
      
      jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
      jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
    else {
      
      jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
      jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
      
      if (selectedScaling.equals("Height")) {
        scaleToHeight();
      else if (selectedScaling.equals("Width")) {
        scaleToWidth();
      else // this might be an int
        try {
          int scale = Integer.parseInt(selectedScaling);
          this.scaling = scale / (double100;
        catch (NumberFormatException e) { // not a number
          e.printStackTrace();
          scalingBox.setSelectedItem(scalingItem);
          return;
        }
      }
    }  

    String selectedRotation = (StringrotationBox.getSelectedItem();

    try {
      this.rotation = Integer.parseInt(selectedRotation);
    catch (NumberFormatException e) { // not a number
      e.printStackTrace();
      rotationBox.setSelectedItem(rotationItem);
      return;
    }

    Image scaledImage = image.getScaledInstance((int) (image.getWidth() * scaling), -1, Image.SCALE_DEFAULT);

    BufferedImage rotatedImage = rotate(toBufferedImage(scaledImage), rotation * Math.PI / 180);

    imageLabel.setIcon(new ImageIcon(rotatedImage));

    scalingItem = (StringscalingBox.getSelectedItem();
  }

  private void setRotation() {
    String selectedRotation = (StringrotationBox.getSelectedItem();

    try {
      this.rotation = Integer.parseInt(selectedRotation);
    catch (NumberFormatException e) { // not a number
      e.printStackTrace();
      rotationBox.setSelectedItem(rotationItem);
      return;
    }

    Image rotatedImage = rotate(image, rotation * Math.PI / 180);
    imageLabel.setIcon(new ImageIcon(rotatedImage));

    rotationItem = (StringrotationBox.getSelectedItem();
  }

  private void scaleToWidth() {
    if (image == null)
      return;
    
    scaling = (jsp.getWidth()) (doubleimage.getWidth();
  }

  private void scaleToHeight() {
    if (image == null)
      return;
    scaling = (jsp.getHeight()) (doubleimage.getHeight();
  }

  private static BufferedImage rotate(BufferedImage src, double angle) {
    if (src == null)
      return null;

    int w = src.getWidth();
    int h = src.getHeight();
    int newW = (int) (Math.round(h * Math.abs(Math.sin(angle)) + w * Math.abs(Math.cos(angle))));
    int newH = (int) (Math.round(h * Math.abs(Math.cos(angle)) + w * Math.abs(Math.sin(angle))));
    AffineTransform at = AffineTransform.getTranslateInstance((newW - w2(newH - h2);
    at.rotate(angle, w / 2, h / 2);

    BufferedImage dst = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = dst.createGraphics();
    g.drawRenderedImage(src, at);
    g.dispose();
    return dst;
  }

  private static boolean hasAlpha(Image image) {
    // If buffered image, the color model is readily available
    if (image instanceof BufferedImage) {
      BufferedImage bimage = (BufferedImageimage;
      return bimage.getColorModel().hasAlpha();
    }

    // Use a pixel grabber to retrieve the image's color model;
    // grabbing a single pixel is usually sufficient
    PixelGrabber pg = new PixelGrabber(image, 0011false);
    try {
      pg.grabPixels();
    catch (InterruptedException e) {
            e.printStackTrace();
    }

    // Get the image's color model
    ColorModel cm = pg.getColorModel();
    return cm.hasAlpha();
  }

  private BufferedImage toBufferedImage(Image image) {
    if (image instanceof BufferedImage) {
      return (BufferedImageimage;
    }

    // This code ensures that all the pixels in the image are loaded
    image = new ImageIcon(image).getImage();

    // Determine if the image has transparent pixels; for this method's
    // implementation, see e661 Determining If an Image Has Transparent
    // Pixels
    boolean hasAlpha = hasAlpha(image);

    // Create a buffered image with a format that's compatible with the
    // screen
    BufferedImage bimage = null;
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    try {
      // Determine the type of transparency of the new buffered image
      int transparency = Transparency.OPAQUE;
      if (hasAlpha) {
        transparency = Transparency.BITMASK;
      }

      // Create the buffered image
      GraphicsDevice gs = ge.getDefaultScreenDevice();
      GraphicsConfiguration gc = gs.getDefaultConfiguration();
      bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
    catch (HeadlessException e) {
      // The system does not have a screen
    }

    if (bimage == null) {
      // Create a buffered image using the default color model
      int type = BufferedImage.TYPE_INT_RGB;
      if (hasAlpha) {
        type = BufferedImage.TYPE_INT_ARGB;
      }
      bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
    }

    // Copy image to buffered image
    Graphics g = bimage.createGraphics();

    // Paint the image onto the buffered image
    g.drawImage(image, 00null);
    g.dispose();

    return bimage;
  }

  public static void displayJBIG2AsImage(JBIG2Bitmap bitmap) {

    byte[] bytes = bitmap.getData(true);

    if (bytes == null)
      return;

    // make a a DEEP copy so we cant alter
    int len = bytes.length;
    byte[] copy = new byte[len];
    System.arraycopy(bytes, 0, copy, 0, len);

    int width = bitmap.getWidth();
    int height = bitmap.getHeight();

    /** create an image from the raw data */
    DataBuffer db = new DataBufferByte(copy, copy.length);

    WritableRaster raster = Raster.createPackedRaster(db, width, height, 1null);
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
    image.setData(raster);

    Image scaledImage = image.getScaledInstance(500, -1, Image.SCALE_AREA_AVERAGING);

    BufferedImage result = new BufferedImage(scaledImage.getWidth(null), scaledImage.getHeight(null), BufferedImage.TYPE_INT_BGR);
    Graphics2D g = result.createGraphics();
    g.drawImage(scaledImage, 00null);
    g.dispose();

    JLabel label = new JLabel(new ImageIcon(result));

    JOptionPane.showConfirmDialog(null, label, "JBIG2 Display", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE);
  }

  public JBIG2Decoder getDecoder() {
    return decoder;
  }

  public void displayPage(int page) {
    if (image != null && page > && page <= decoder.getNumberOfPages()) {
      image = decoder.getPageAsBufferedImage(page - 1);
      currentPage = page;
      setScalingAndRotation();
      navToolbar.setCurrentPage(currentPage);
    }
  }

  public int getCurrentPage() {
    return currentPage;
  }
}