/*
 * ===========================================
 * 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-2017 IDRsolutions and Contributors.
 *
 * This file is part of JPedal/JPDF2HTML5
 *
 
 *
 * ---------------
 * ConvertPagesToHiResImages.java
 * ---------------
 */

package org.jpedal.examples.images;

import com.idrsolutions.image.tiff.TiffEncoder;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.imageio.ImageIO;
import org.jpedal.PdfDecoderServer;
import org.jpedal.color.ColorSpaces;
import org.jpedal.constants.JPedalSettings;
import org.jpedal.constants.PageInfo;
import org.jpedal.exception.PdfException;
import org.jpedal.fonts.FontMappings;
import org.jpedal.io.ColorSpaceConvertor;

/**
 <h2>Image Extraction from PDF files</h2>
 *
 * This class provides a simple Java API to convert pages in a PDF files into images and also
 * a static convenience method if you just want to dump all the pages as images from a PDF file
 * or directory containing PDF files<p>
 *
 <h3>Example 1 - access API methods</h3>
 <pre><code>
 * ConvertPagesToHiResImages extract=new ConvertPagesToHiResImages("C:/pdfs/mypdf.pdf");
 * //extract.setPassword("password");
 * HashMap options=new HashMap(); //see https://javadoc.idrsolutions.com/org/jpedal/constants/JPedalSettings.html
 * if (extract.openPDFFile()) {
 *     int pageCount=extract.getPageCount();
 *     for (int page=1; page&lt;=pageCount; page++) {
 *
 *        BufferedImage image=extract.getPageAsHiResImage(page, isBackgroundTransparent,  options);
 *     }
 * }
 *
 * extract.closePDFfile();
 </code></pre>
 *
 <h3>Example 2 - convenience static method</h3>
 * Convert all pages as images with options to create higher resolution output<p>
 *
 <pre><code>
 * ConvertPagesToHiResImages.writeAllPagesAsHiResImagesToDir("pdfs", "images" , "png", options); 
 </code></pre>
 
 <h3>Example 3 - Access directly from the Jar</h3>
 * ExtractImages 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/images/ConvertPagesToHiResImages inputValues</code><p>
 
 * Where inputValues is 3 values:
 <ul>
 <li>First value:  The PDF filename (including the path if needed) or a directory containing PDF files. If it contains spaces it must be enclosed by double quotes (ie "C:/Path with spaces/").</li>
 <li>Second value: The location to write out images extracted from the PDF file or files. If it contains spaces it must be enclosed by double quotes (ie "C:/Path with spaces/").</li>
 <li>Third value:  This indicates the required output image type (default is png if nothing specified). Options are tiff, png, jpg.</li>
 </ul>
 *
 * There is another example (org.jpedal.examples.images.ConvertPagesToImages) for producing images of pages if extra features not needed.
 <p><a href="https://www.idrsolutions.com/how-to-convert-pdf-files-to-image">Click here for a list of code examples to convert images</a></p>
 */
public final class ConvertPagesToHiResImages extends BaseImageExtraction{
    
    //only used if between 0 and 1
    private float JPEGcompression=-1f;
    
    public static void main(final String[] args) {
        
        if(args!=null && args.length>1){
            
            try{
                if (args.length == 2) {
                    writeAllPagesAsHiResImagesToDir(args[0], args[1]"png");
                else {
                    writeAllPagesAsHiResImagesToDir(args[0],args[1],args[2]);
                }
            catch (final PdfException ex) {
                throw new RuntimeException(ex.getMessage());
            }
        }else{
            System.out.println("wrong arguments entered");
            
            final StringBuilder arguments=new StringBuilder();
            for (final String arg : args) {
                arguments.append(arg).append('\n');
            }
            System.out.println("you entered:\n"+ arguments +"as the arguments");
        }
    }
    
    /**
     * static method to write out all pages in a PDF files or directory of PDF files as images
     *
     */
    public static void writeAllPagesAsHiResImagesToDir(final String inputDir, final String outputDir, final String formatthrows PdfException{
        
        /*
         * this process is very flaxible to we create a Map and pass in values to select what sort
         * of results we want. There is a choice between methods used and image size. Larger images use more
         * memory and are slower but look better
         */
        final Map<Integer, java.io.Serializable> mapValues = new HashMap<Integer, java.io.Serializable>();
        
        /* USEFUL OPTIONS*/
        //do not scale above this figure
        mapValues.put(JPedalSettings.EXTRACT_AT_BEST_QUALITY_MAXSCALING, 2);
        
        //alternatively secify a page size (aspect ratio preserved so will do best fit)
        //set a page size (JPedal will put best fit to this)
        mapValues.put(JPedalSettings.EXTRACT_AT_PAGE_SIZE, new String[]{"2000","1600"});
        
        //which takes priority (default is false)
        mapValues.put(JPedalSettings.PAGE_SIZE_OVERRIDES_IMAGE, Boolean.TRUE);
        
        PdfDecoderServer.modifyJPedalParameters(mapValues);
        
        final ConvertPagesToHiResImages convert=new ConvertPagesToHiResImages(inputDir);
        
        convert.setup(format,outputDir);
        
        convert.processFiles(inputDir);
        
        convert.closePDFfile();
    }
    
    /** Sets up an ConvertPagesToHiResImages instance to open a PDF File
     @param fileName full path to a single PDF file
     */
    public ConvertPagesToHiResImages(final String fileName) {
        super(fileName);
        
        init();
    }
    
    /** Sets up an ConvertPagesToHiResImages instance to open  a PDF file contained as a BLOB within a byte[] stream
     *
     @param byteArray
     */
    public ConvertPagesToHiResImages(final byte[] byteArray )
    {
        super(byteArray);
        
        init();
        
    }
    
    /**
     * main constructor to convert PDF to img
     @param pdfFile
     @throws Exception
     */
    @Override
    void decodeFile(final String pdfFilethrows PdfException {
        
        
        System.out.println(pdfFile);
        System.out.println(output_dir);
        
        if(openPDFFile()){
            
                         /*
             * allow output to multiple images with different values on each
             *
             * Note we REMOVE shapes as it is a new feature and we do not want to break existing functions
             */
            final String separation=System.getProperty("org.jpedal.separation");
            if(separation!=null){
                
                Object[] sepValues= {7,"",Boolean.FALSE}//default of normal
                if(separation.equals("all")){
                    sepValues=new Object[]{PdfDecoderServer.RENDERIMAGES,"image_and_shapes",Boolean.FALSE,
                        PdfDecoderServer.RENDERIMAGES + PdfDecoderServer.REMOVE_RENDERSHAPES,"image_without_shapes",Boolean.FALSE,
                        PdfDecoderServer.RENDERTEXT,"text_and_shapes",Boolean.TRUE,
                        7,"all",Boolean.FALSE,
                        PdfDecoderServer.RENDERTEXT + PdfDecoderServer.REMOVE_RENDERSHAPES,"text_without_shapes",Boolean.TRUE
                    };
                }
                
                final int sepCount =sepValues.length;
                for(int seps=0;seps<sepCount;seps += 3){
                    
                    decode_pdf.setRenderMode((IntegersepValues[seps]);
                    try{
                        extractPagesAsImages(output_dir, imageType,"_"+sepValues[seps+1](BooleansepValues[seps + 2])//boolean makes last transparent so we can see white text
                    catch (final IOException ex) {
                        throw new PdfException(ex.getMessage());
                    }
                }
                
            }else {
                try {
                    //just get the page
                    extractPagesAsImages(output_dir, imageType, "",false);
                catch (final IOException ex) {
                    throw new PdfException(ex.getMessage());
                }
            }
        }
    }
    
    /**
     * actual conversion of a PDF page into an image
     @param fileType
     @param outputPath
     @param prefix
     @param isTransparent
     @throws PdfException
     @throws IOException
     */
    private void extractPagesAsImages(final String outputPath,final String fileType,  final String prefix, final boolean isTransparentthrows PdfException, IOException {
        
        //create a directory if it doesn't exist
        final File output_path = new File(output_dir);
        if (!output_path.exists()) {
            output_path.mkdirs();
        }
        
        //page range
        final int start=1;
        final int end=getPageCount();
        
        /*
         * set of JVM flags which allow user control on process
         */
        
        
        //////////////////TIFF OPTIONS/////////////////////////////////////////
        
        final String multiPageFlag=System.getProperty("org.jpedal.multipage_tiff");
        final boolean isSingleOutputFile=multiPageFlag!=null && multiPageFlag.equalsIgnoreCase("true");
        
        final String tiffFlag=System.getProperty("org.jpedal.compress_tiff");
        final boolean compressTiffs = tiffFlag!=null && tiffFlag.equalsIgnoreCase("true");
        
        //////////////////JPEG OPTIONS/////////////////////////////////////////
        
        //allow user to specify value
        final String rawJPEGComp=System.getProperty("org.jpedal.compression_jpeg");
        if(rawJPEGComp!=null){
            try{
                JPEGcompression=Float.parseFloat(rawJPEGComp);
            }catch(final Exception e){
                e.printStackTrace();
            }
            if(JPEGcompression<|| JPEGcompression>1) {
                throw new RuntimeException("Invalid value for JPEG compression - must be between 0 and 1");
            }else{
                //remember to update https://www.idrsolutions.com/jvm-flags/ when we fix
                throw new RuntimeException("JPEG compression is not currently implemented. Please do not use");
            }            
        }
        
        final String jpgFlag=System.getProperty("org.jpedal.jpeg_dpi");
        if(jpgFlag!=null){
            //remember to update https://www.idrsolutions.com/jvm-flags/ when we fix
            throw new RuntimeException("JPEG dpi is not currently implemented. Please do not use");
        }
        
        ///////////////////////////////////////////////////////////////////////
        
        for (int pageNo = start; pageNo < end+1; pageNo++) {
            
            /*
             * If you are using decoder.getPageAsHiRes() after passing additional parameters into JPedal using the static method
             * PdfDecoder.modifyJPedalParameters(), then getPageAsHiRes() wont necessarily be thread safe.  If you want to use
             * getPageAsHiRes() and pass in additional parameters, in a thread safe mannor, please use the method
             * getPageAsHiRes(int pageIndex, Map params) or getPageAsHiRes(int pageIndex, Map params, boolean isTransparent) and
             * pass the additional parameters in directly to the getPageAsHiRes() method without calling PdfDecoder.modifyJPedalParameters()
             * first.
             *
             * Please see org/jpedal/examples/images/ConvertPagesToImages.java.html for more details on how to use HiRes image conversion
             */
            BufferedImage imageToSave = getPageAsHiResImage(pageNo, isTransparent,null);
            
            decode_pdf.flushObjectValues(true);
            
            //System.out.println("w="+imageToSave.getWidth()+" h="+imageToSave.getHeight());
            //image needs to be sRGB for JPEG
            if(fileType.equals("jpg")) {
                imageToSave = ColorSpaceConvertor.convertToRGB(imageToSave);
            }
            
            
            final String outputFileName;
            if(isSingleOutputFile) {
                outputFileName = outputPath+ "allPages"+prefix+ '.' + fileType;
            else{
                /*
                 * create a name with zeros for if more than 9 pages appears in correct order
                 */
                String pageAsString=String.valueOf(pageNo);
                final String maxPageSize=String.valueOf(end);
                final int padding=maxPageSize.length()-pageAsString.length();
                for(int ii=0;ii<padding;ii++) {
                    pageAsString='0'+pageAsString;
                }
                
                outputFileName = outputPath + "page" + pageAsString +prefix + '.' + fileType;
            }
            
            
            //if just gray we can reduce memory usage by converting image to Grayscale
            
            /*
             * see what Colorspaces used and reduce image if appropriate
             * (only does Gray at present)
             *
             * Can return null value if not sure
             */
            final Iterator colorspacesUsed=decode_pdf.getPageInfo(PageInfo.COLORSPACES);
            
            int nextID;
            boolean isGrayOnly=colorspacesUsed!=null//assume true and disprove
            
            while(colorspacesUsed!=null && colorspacesUsed.hasNext()){
                nextID= (Integer) (colorspacesUsed.next());
                
                if(nextID!= ColorSpaces.DeviceGray && nextID!=ColorSpaces.CalGray) {
                    isGrayOnly=false;
                }
            }
            
            //draw onto GRAY image to reduce colour depth
            //(converts ARGB to gray)
            if(isGrayOnly){
                final BufferedImage image_to_save2=new BufferedImage(imageToSave.getWidth(),imageToSave.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
                image_to_save2.getGraphics().drawImage(imageToSave,0,0,null);
                imageToSave = image_to_save2;
            }
            
            //we save the image out here
            if (imageToSave != null) {

                if(fileType.startsWith("tif")){
                    
                    final TiffEncoder tiffEncoder = new TiffEncoder();
                    tiffEncoder.setCompressed(compressTiffs);
                    
                    if(isSingleOutputFile){
                        
                        final File file = new File(outputFileName);
                        if(pageNo==start && file.exists()){
                            file.delete();
                            file.createNewFile();
                        }
                        tiffEncoder.append(imageToSave, outputFileName);
                        
                    }else{
                        final File file = new File(outputFileName);
                        final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
                        tiffEncoder.write(imageToSave,bos);
                        bos.flush();
                        bos.close();
                    }
                    
                else {
                    
                    final BufferedOutputStream bos= new BufferedOutputStream(new FileOutputStream(new File(outputFileName)));
                    ImageIO.write(imageToSave, fileType, bos);
                    bos.flush();
                    bos.close();
                }
                
                imageToSave.flush();
            }
        }
    }
    
    public BufferedImage getPageAsHiResImage(final int page, final boolean isTransparent, final Map optionsthrows PdfException {
        
        return decode_pdf.getPageAsHiRes(page,options,isTransparent);
        
    }
    
    private void setup(final String format, String outputDir) {
        
        //check output dir has separator
        if (!outputDir.endsWith(separator)) {
            outputDir += separator;
        }
        
        this.imageType=format;
        this.output_dir=outputDir;
    }
    
    @Override
    void init() {
        
        
        //mappings for non-embedded fonts to use
        FontMappings.setFontReplacements();
        
        type=ExtractTypes.RASTERIZED_PAGE;
        
        super.init();
        
        //decode_pdf.setExtractionMode(0, pageScaling);
    }
}