/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info:  http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/support/
*
* (C) Copyright 1997-2015 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*

*
* ---------------
* ExtractClippedImages.java
* ---------------
*/


package org.jpedal.examples.images;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;

import java.util.Arrays;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.Messages;

import org.jpedal.color.ColorSpaces;
import org.jpedal.examples.handlers.DefaultImageHelper;
import org.jpedal.exception.PdfException;
import org.jpedal.io.ColorSpaceConvertor;

/**
 <h2>Clipped Image Extraction from PDF files</h2>
 *
 * This class provides a simple Java API to extract clipped images from a PDF file and also
 * a static convenience method if you just want to dump all the images from a PDF file
 * or directory containing PDF files at a set of sizes<p>
 *
 <h3>Example 1 - access API methods</h3>
 <pre><code>ExtractClippedImages extract=new ExtractClippedImages("C:/pdfs/mypdf.pdf");
 * //extract.setPassword("password");
 * if (extract.openPDFFile()) {
 *     int pageCount=extract.getPageCount();
 *     for (int page=1; page&lt;=pageCount; page++) {
 *
 *        int imagesOnPageCount=extract.getImageCount(page);
 *        for (int image=0; image&lt;imagesOnPageCount; image++) {
 *             BufferedImage image=extract.getClippedImage(page, image);
 *        }
 *     }
 * }
 *
 * extract.closePDFfile();</code></pre>
 *
 <h3>Example 2 - convenience static method</h3>
 * Extract images with clip applied and rescale to set of predefined pixel heights (-1 is raw size)<p>
 
 <pre><code>ExtractClippedImages.writeAllClippedImagesToDirs(inputDir, outDir, new String[]{500,"scaled",-1,"rawSize" );</code></pre>
 
 <h3>Example 3 - Access directly from the Jar</h3>
 * ExtractClippedImages can run from jar directly using the command and will extract all files from a PDF file or directory
 * to a defined output directory:<p>
 *
 <code>java -cp libraries_needed org/jpedal/examples/ ExtractClippedImages $inputDir $output_dir imageOutputType h1 dir1 h2 dir2 ... hn dirn</code><p>
 *
 * Values with SPACES must be surrounded by "" as in "This is one value". The values passed are:
 *
 <ul>
 <li>$inputDir - directory containing files.</li>
 <li>$output_dir - directory to put files in.</li>
 <li>imageOutputType - save images as jpeg, tiff or png.</li>
 <li>Any number of pairs of values: h dir</li>
 </ul>
 *
 * h - height required in pixels as an integer for output (-1 means keep current size) dir - directory to write out images.<p>
 *
 * So to create 3 versions of the image (one at original size, one at 100 and one at 50 pixels high), you would use:<p>
 *
 <b>java -cp libraries_needed org/jpedal/examples/images/ExtractClippedImages pdfFiles ouput /logs/image.log -1 raw 100 medium 50 thumbnail/</b><p>
 *
 * Note image quality depends on the raw image in the original. It can be VERY memory intensive.<p>
 *
 <p><a href="http://www.idrsolutions.com/how-to-extract-images-from-pdf-files/">See our support pages for more information on extracting images.</a>
 */
public class ExtractClippedImages extends BaseImageExtraction
{
    
    /**rootDir containing files*/
    private static String inputDir="";
    
    /**number of output directories*/
    private static int outputCount;
    
    /**sizes to output at -1 means unchanged*/
    private static float[] outputSizes;
    
    /**target directories for files*/
    private static String[] outputDirectories;
    
    /**background colour to add to JPEG*/
    private static final Color backgroundColor=Color.WHITE;
    
    /** Sets up an ExtractClippedImages instance to open a PDF File
     @param fileName full path to a single PDF file
     */
    public ExtractClippedImagesString fileName )
    {
        super(fileName);
        
        init();
    }
    
    /** Sets up an ExtractClippedImages instance to open  a PDF file contained as a BLOB within a byte[] stream
     *
     @param byteArray
     */
    public ExtractClippedImages(final byte[] byteArray )
    {
        super(byteArray);
        
        init();
        
    }
   
    /**
     * extract any image from any page - recommended you process images on each page in turn as quicker
     @param page logical page number (1 is first page)
     @param imageNumber image on page (0 is first image)
     @return BufferedImage
     @throws PdfException
     */
    public BufferedImage getClippedImage(int page, int imageNumberthrows PdfException{
        
        return getClippedImage(page,  getImageName(page, imageNumber));
    }
    
    /**
     * extract any image from any page - recommended you process images on each page in turn as quicker
     @param page logical page number (1 is first page)
     @param image_name name of image
     @return BufferedImage
     @throws PdfException
     */
    public BufferedImage getClippedImage(int page, String image_namethrows PdfException{
        
        selectPage(page);
        
        return decode_pdf.getObjectStore().loadStoredImage(  "CLIP_"+image_name );
        
    }
    
    /**
     * Convenience method to Extract all the images in a directory of PDF files
     *
     @param inputDir
     @param outDir
     @param subDirs
     @throws org.jpedal.exception.PdfException
     */
    public static void writeAllClippedImagesToDirsfinal String inputDir, final String outDir, String imageType, final String[] subDirs throws PdfException
    {
        
        ExtractClippedImages extract=new ExtractClippedImages(inputDir);
        
        extract.setup(outDir,imageType, subDirs);
        
        /**create output*/
        extract.processFilesinputDir );
        
        extract.closePDFfile();
        
    }
    
    private void setup(String outDir, String imageType, String[] subDirsthrows PdfException {
        
        //check output dir has separator
        if (!outDir.endsWith(separator)) {
            outDir += separator;
        }
        
        this.imageType=imageType;
        
        /**read output values*/
        outputCount=(subDirs.length)/2;
        
        /**read and create output directories*/
        outputSizes=new float[outputCount];
        outputDirectories=new String[outputCount];
        for(int i=0;i<outputCount;i++){
            
            try{
                outputSizes[i]=Float.parseFloat(subDirs[(i*2)]);
            }catch(final Exception e){
                throw new PdfException("Exception "+e+" reading integer "+subDirs[(i*2)]);
            }
            
            try{
                outputDirectories[i]=outDir;//args[1+(i*2)];
                
                /**make sure has separator*/
                if((!outputDirectories[i].endsWith("\\"))&&(!outputDirectories[i].endsWith("/"))) {
                    outputDirectories[i+= separator;
                }
                
                final File dir=new File(outputDirectories[i]);
                if(!dir.exists()) {
                    dir.mkdirs();
                }
            }catch(final Exception e){
                throw new PdfException("Exception "+e+" with directory "+subDirs[4+(i*2)]);
            }
        }
    }
    
    /**
     * routine to decodeFile a PDF file
     */
    @Override
    void decodeFile(final String file_namethrows PdfException{
        
        if(openPDFFile()){
            
            //page range
            final int start = 1;
            final int end =getPageCount();
            
            try{
                forint page = start;page < end + 1;page++ ){ //read pages
                    
                    LogWriter.writeLog(Messages.getMessage("PdfViewerDecoding.page")' ' +page);
                    
                    //image count (note image 1 is item 0, so any loop runs 0 to count-1)
                    final int image_count = getImageCount(page);
                    
                    //tell user
                    ifimage_count > ) {
                        LogWriter.writeLog("page"' ' +page+"contains "+image_count+ " images");
                    else {
                        LogWriter.writeLog("No bitmapped images on page "+page);
                    }
                    
                    LogWriter.writeLog("Writing out images");
                    
                    //location of images
                    final float[] x1=new float[image_count];
                    final float[] y1=new float[image_count];
                    final float[] w=new float[image_count];
                    final float[] h=new float[image_count];
                    
                    //used to merge images
                    final float[] rawX1=new float[image_count];
                    final float[] rawY1=new float[image_count];
                    final float[] rawH=new float[image_count];
                    
                    final String[] image_name=new String[image_count];
                    final BufferedImage[] image=new BufferedImage[image_count];
                    
                    final boolean[] isMerged=new boolean[image_count];
                    
                    //work through and get each image details
                    forint i = 0;i < image_count;i++ ){
                        
                        image_name[i=getImageName(page,i);
                        
                        //we need some duplicates as we update some values on merge but still need originals at end
                        //so easiest just to store
                        x1[i]=pdf_images.getImageXCoord(i);
                        rawX1[i]=pdf_images.getImageXCoord(i);
                        y1[i]=pdf_images.getImageYCoord(i);
                        rawY1[i]=pdf_images.getImageYCoord(i);
                        w[i]=pdf_images.getImageWidth(i);
                        h[i]=pdf_images.getImageHeight(i);
                        rawH[i]=pdf_images.getImageHeight(i);
                        
                        image[i=getClippedImage(page, image_name[i]);
                        
                    }
                    
                    //merge overlapping images
                    final boolean mergeImages=true;
                    if(mergeImages){
                        boolean imagesMerged=true;
                        final boolean[] isUsed=new boolean[image_count];
                        final ArrayList[] imagesUsed=new ArrayList[image_count];
                        
                        //get list of images to merge for each block
                        //we repeat from start each time as areas grow
                        while(imagesMerged){
                            
                            //if no overlaps found we will exit, otherwise repeat from start as sizes changed
                            imagesMerged=false;
                            
                            //for each image
                            forint i = 0;i < image_count;i++ ){
                                
                                //compare against all others
                                forint i2 = 0;i2 < image_count;i2++ ){
                                    
                                    if(i==i2) {
                                        continue;
                                    }
                                    
                                    /**
                                     * look for overlap
                                     */
                                    if(!isUsed[i&& !isUsed[i2&& image[i]!=null && image[i2]!=null && x1[i]>=x1[i2]&& x1[i]<=(x1[i2]+w[i2]) && y1[i]>=y1[i2]&& y1[i]<=(y1[i2]+h[i2])){
                                        
                                        //System.out.println("\n------Merging------");
                                        //System.out.println(i2+" "+x1[i2]+" "+y1[i2]+" "+(x1[i2]+w[i2])+" "+(y1[i2]+h[i2])+" "+image[i2]);
                                        //System.out.println(i+" "+x1[i]+" "+y1[i]+" "+(x1[i]+w[i])+" "+(y1[i]+h[i]+" "+image[i]));
                                        //work out the new combined size
                                        final float newX=x1[i2];
                                        final float newY=y1[i2];
                                        float newX2=x1[i]+w[i];
                                        final float altNewX2=x1[i2]+w[i2];
                                        if(newX2<altNewX2) {
                                            newX2=altNewX2;
                                        }
                                        float newY2=y1[i]+h[i];
                                        final float altNewY2=y1[i2]+h[i2];
                                        if(newY2<altNewY2) {
                                            newY2=altNewY2;
                                        }
                                        final float newW=newX2-newX;
                                        final float newH=newY2-newY;
                                        
                                        //System.out.println("new size ="+newX+" "+newY+" "+newW+" "+newH);
                                        x1[i2]=newX;
                                        y1[i2]=newY;
                                        w[i2]=newW;
                                        h[i2]=newH;
                                        isMerged[i2]=true;
                                        
                                        if(imagesUsed[i2]==null){
                                            imagesUsed[i2]=new ArrayList<Integer>(image_count);
                                            
                                            imagesUsed[i2].add(i2);
                                            
                                        }
                                        
                                        imagesUsed[i2].add(i);
                                        
                                        isUsed[i]=true;
                                        
                                        //merge any items attached to this image
                                        if(imagesUsed[i]!=null){
                                            imagesUsed[i2].addAll(Arrays.asList(imagesUsed[i].toArray()));
                                            
                                            isMerged[i]=false;
                                            imagesUsed[i]=null;
                                            
                                        }
                                        
                                        //restart
                                        imagesMerged=true;
                                        i=image_count;
                                        i2=image_count;
                                    }
                                }
                            }
                        }
                        
                        //now put together in correct order
                        for(int i=0;i<image_count;i++){
                            
                            if(imagesUsed[i]!=null){
                                
                                final float newX=x1[i];
                                final float newY=y1[i];
                                //float newH=h[i];
                                /**
                                 * put both images together
                                 */
                                final BufferedImage combinedImage=new BufferedImage((int)w[i],(int)h[i],BufferedImage.TYPE_INT_ARGB);
                                
                                final Graphics2D g2=combinedImage.createGraphics();
                                
                                //improve image quality
                                g2.setRenderingHints(ColorSpaces.hints);
                                
                                Collections.sort(imagesUsed[i]);
                                
                                int lastImage=-1;
                                for(final Object currentImage: imagesUsed[i].toArray()){
                                    
                                    final int i2=(Integer)currentImage;
                                    
                                    if(lastImage!=i2){ //avoid duplicates
                                        
                                        final int finalX=(int) (x1[i2- newX);
                                        final int finalY=combinedImage.getHeight() +(int) (-y1[i2- newY-h[i2]);
                                        
                                        g2.drawImage(image[i2], finalX, finalY,(int)w[i2],(int)h[i2]null);
                                        
                                        lastImage=i2;
                                        image[i2]=null;
                                    }
                                }
                                
                                image[i]=combinedImage;
                            }
                        }
                    }
                    
                    //save each image
                    forint i = 0;i < image_count;i++ ){
                        
                        if(image[i]!=null){
                            //if(isMerged[i]){  //uncomment if you want just merged images
                            generateVersions(file_name, page, "<PAGELOCATION x1=\"" + x1[i"\" "
                                    "y1=\"" (y1[i+ h[i]) "\" "
                                    "x2=\"" (x1[i+ w[i]) "\" "
                                    "y2=\"" (y1[i]) "\" />\n", image_name[i], image[i], i);
                            //}
                        }
                    }
                    
                    //flush images in case we do more than 1 page so only contains
                    //images from current page
                    decode_pdf.flushObjectValues(true);
                    
                }
            }catchfinal Exception e ){
                decode_pdf.closePdfFile();
                LogWriter.writeLog"Exception " + e.getMessage() );
                
                e.printStackTrace();
            }
        }
        
        /**close the pdf file*/
        decode_pdf.closePdfFile();
        
    }
    
    private void generateVersions(final String file_name, final int page, final String s, final String image_name, final BufferedImage bufferedImage, final int i) {
        
        for(int versions=0;versions<outputCount;versions++){
            try{
                //find out format image was saved in
                
                //load image (converted to rgb)
                BufferedImage image_to_save = bufferedImage;
                if(image_to_save==null) {
                    continue;
                }
                
                int index = file_name.lastIndexOf('\\');
                if(index==-1) {
                    index = file_name.lastIndexOf('/');
                }
                if(index==-1) {
                    index=0;
                }
                final String nameToUse = file_name.substring(index,file_name.length()-4);
                final String outputName=outputDirectories[versions]+nameToUse + '_' + page + '_' +i;
                
                float scaling=1;
                
                final int newHeight=image_to_save.getHeight();
                
                //scale
                if(outputSizes[versions]>0){
                    
                    scaling=outputSizes[versions]/newHeight;
                    
                    if(scaling>1){
                        scaling=1;
                    }else{
                        
                        final Image scaledImage= image_to_save.getScaledInstance(-1,(int)outputSizes[versions],BufferedImage.SCALE_SMOOTH);
                        
                        image_to_save = new BufferedImage(scaledImage.getWidth(null),scaledImage.getHeight(null, BufferedImage.TYPE_INT_ARGB);
                        
                        final Graphics2D g2 =image_to_save.createGraphics();
                        
                        g2.drawImage(scaledImage, 00,null);
                        //ImageIO.write((RenderedImage) scaledImage,"PNG",new File(outputName));
                        
                        
                    }
                }
                
                final String tiffFlag=System.getProperty("org.jpedal.compress_tiff");
                final String jpgFlag=System.getProperty("org.jpedal.jpeg_dpi");
                final boolean compressTiffs = tiffFlag!=null;
                
                //no transparency on JPEG so give background and draw on
                if(imageType.startsWith("jp")){
                    
                    final int iw=image_to_save.getWidth();
                    final int ih=image_to_save.getHeight();
                    final BufferedImage background=new BufferedImage(iw,ih, BufferedImage.TYPE_INT_RGB);
                    
                    final Graphics2D g2=(Graphics2D)background.getGraphics();
                    g2.setPaint(backgroundColor);
                    g2.fillRect(0,0,iw,ih);
                    
                    g2.drawImage(image_to_save,0,0,null);
                    image_to_save= background;
                    
                }
                
                if(image_to_save.getType()==BufferedImage.TYPE_CUSTOM){
                    image_to_save=ColorSpaceConvertor.convertToARGB(image_to_save);
                }
                
                try {
                    DefaultImageHelper.write(image_to_save, imageType, outputName+ '.'+imageType);
                catch (IOException ex) {
                    if(LogWriter.isOutput()) {
                        LogWriter.writeLog("Exception in writing image "+ex);
                    }
                }
                
                //save an xml file with details
                /**
                 * output the data
                 */
                //LogWriter.writeLog( "Writing out "+(outputName + ".xml"));
                final OutputStreamWriter output_stream = new OutputStreamWriter(new FileOutputStream(outputName + ".xml"),"UTF-8");
                
                output_stream.write(
                        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
                output_stream.write(
                        "<!-- Pixel Location of image x1,y1,x2,y2\n");
                output_stream.write("(x1,y1 is top left corner)\n");
                output_stream.write(
                        "(origin is bottom left corner)  -->\n");
                output_stream.write("\n\n<META>\n");
                output_stream.write(s);
                output_stream.write("<FILE>"+file_name+"</FILE>\n");
                output_stream.write("<ORIGINALHEIGHT>"+newHeight+"</ORIGINALHEIGHT>\n");
                output_stream.write("<SCALEDHEIGHT>"+image_to_save.getHeight()+"</SCALEDHEIGHT>\n");
                output_stream.write("<SCALING>"+scaling+"</SCALING>\n");
                output_stream.write("</META>\n");
                output_stream.close();
            }catchfinal Exception ee ){
                LogWriter.writeLog"Exception " + ee + " in extracting images" );
            }
        }
    }
    
    
    /**
     * main routine which checks for any files passed and runs the demo
     @param args
     */
    public static void mainfinal String[] args )
    {
        
        String[] subDirs=validateInputValues(args);
        
        try{
            ExtractClippedImages.writeAllClippedImagesToDirsargs[0], args[1], args[2], subDirs);
        }catch(PdfException e){
            throw new RuntimeException(e);
        }
        
    }
    
    private static String[] validateInputValues(final String[] argsthrows RuntimeException {
        
        String[] subDirs=null;
        /**exit and report if wrong number of values*/
        if((args.length >= 5&& ((args.length % 2== 1)) {
            
            LogWriter.writeLog("Values read");
            LogWriter.writeLog("inputDir="+args[0]);
            LogWriter.writeLog("processedDir="+args[1]);
            LogWriter.writeLog("logFile="+args[2]);
            LogWriter.writeLog("Directory and height pair values"+args[3]+" <> "+args[4]'<');
            
            /**count output values*/
            outputCount = (args.length-32;
            
            subDirs=new String[outputCount];
            
            for(int i=0; i<outputCount; i++) {
                LogWriter.writeLog(args[i + 3]);
                if(((i % 2== 0&& (!args[i + 3].matches("((-|\\+)?[0-9]+(\\.[0-9]+)?)+"))){
                    throw new RuntimeException("Invalid value: " + args[i+3]);
                }
                subDirs[i]=args[i+3];
            }
            
        }
        else if(((args.length - 32== 1) {
            throw new RuntimeException("Value/Directory pairs invalid");
        }
        else {
            System.out.println("Requires");
            System.out.println("inputDir processedDir imageOutputType");
            System.out.println("height Directory (as many pairs as you like)");
            throw new RuntimeException"Not enough parameters passed to software" );
        }
        
        /**check input directory exists*/
        final File pdf_file = new FileinputDir );
        
        /**if dir exists, open and get number of pages*/
        if(!pdf_file.exists()) {
            new RuntimeException"Directory " + inputDir + " not found" );
        }
        
        return subDirs;
    }
    
    @Override
    void init() {
        
        type=ExtractTypes.CLIPPED_IMAGES;
        
        super.init();
    }
    
    /**
     * returns an image count for the selected page
     @param page logical page number
     @return int number of images (0 if no images)
     @throws PdfException
     */
    public int getImageCount(int pagethrows PdfException{
        
        selectPage(page);
        
        //image count (note image 1 is item 0, so any loop runs 0 to count-1)
        return  pdf_images.getImageCount();
    }
    
    
    /**
     * Return name of image (composite of filename and Internal PDF image name)
     @param page - logical page number
     @param imageNumber - number of image (0 is first image)
     @return - String containing image name
     @throws PdfException
     */
    public String getImageName(int page, int imageNumberthrows PdfException{
        
        selectPage(page);
        
        return  pdf_images.getImageNameimageNumber );
    }
}