001/* 002 * Copyright 2011 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.flipbook.pdfbox; 017 018import java.awt.Dimension; 019import java.awt.Graphics; 020import java.awt.image.BufferedImage; 021import java.io.File; 022import java.io.FileFilter; 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.List; 026 027import javax.imageio.ImageIO; 028 029import org.apache.avalon.framework.logger.AbstractLogEnabled; 030import org.apache.pdfbox.pdmodel.PDDocument; 031import org.apache.pdfbox.pdmodel.PDPage; 032import org.apache.pdfbox.util.ImageIOUtil; 033import org.apache.pdfbox.util.PDFImageWriter; 034 035import org.ametys.plugins.flipbook.Document2ImagesConvertor; 036import org.ametys.plugins.flipbook.FlipbookException; 037 038import net.coobird.thumbnailator.makers.FixedSizeThumbnailMaker; 039import net.coobird.thumbnailator.resizers.DefaultResizerFactory; 040 041/** 042 * PDF to PNG convertor which makes use of the pdfbox library. 043 * Based on pdfbox's {@link PDFImageWriter} utility class, adding the possibility to specify the file name pattern. 044 */ 045public class PdfboxConvertor extends AbstractLogEnabled implements Document2ImagesConvertor 046{ 047 @Override 048 public void convert(File pdfFile, File folder) throws IOException, FlipbookException 049 { 050 String outputPrefix = "page"; 051 String imageFormat = "png"; 052 053 PDDocument document = null; 054 try 055 { 056 document = PDDocument.load(pdfFile); 057 058 if (document.isEncrypted()) 059 { 060 throw new IOException("The PDF file is encrypted, cannot read it."); 061 } 062 063 long start = System.currentTimeMillis(); 064 if (getLogger().isInfoEnabled()) 065 { 066 getLogger().info("Converting PDF to PNG images using pdfbox."); 067 } 068 069 writeImages(document, folder, imageFormat, outputPrefix, 120); 070 071 // Generate preview 072 writePreview(folder, outputPrefix); 073 074 long end = System.currentTimeMillis(); 075 if (getLogger().isInfoEnabled()) 076 { 077 getLogger().info("PDF converted to PNG in " + (end - start) + "ms."); 078 } 079 } 080 finally 081 { 082 if (document != null) 083 { 084 document.close(); 085 } 086 } 087 } 088 089 /** 090 * Converts a given page range of a PDF document to bitmap images. 091 * @param document the PDF document 092 * @param folder the folder where to write 093 * @param imageFormat the target format (ex. "png") 094 * @param outputPrefix used to construct the filename for the individual images 095 * @return true if the images were produced, false if there was an error 096 * @throws IOException if an I/O error occurs 097 * @throws FlipbookException if an error occurs when manipulating the flipbook 098 */ 099 protected List<String> writeImages(PDDocument document, File folder, String imageFormat, String outputPrefix) throws IOException, FlipbookException 100 { 101 return writeImages(document, folder, imageFormat, outputPrefix, 96); 102 } 103 104 /** 105 * Converts a given page range of a PDF document to bitmap images. 106 * @param document the PDF document 107 * @param folder the folder where to write 108 * @param imageFormat the target format (ex. "png") 109 * @param outputPrefix used to construct the filename for the individual images 110 * @param resolution the resolution in dpi (dots per inch) 111 * @return true if the images were produced, false if there was an error 112 * @throws IOException if an I/O error occurs 113 * @throws FlipbookException if an error occurs when manipulating the flipbook 114 */ 115 protected List<String> writeImages(PDDocument document, File folder, String imageFormat, String outputPrefix, int resolution) throws IOException, FlipbookException 116 { 117 return writeImages(document, folder, imageFormat, "", 1, Integer.MAX_VALUE, outputPrefix, BufferedImage.TYPE_INT_RGB, resolution, 1.0f); 118 } 119 120 /** 121 * Converts a given page range of a PDF document to bitmap images. 122 * @param document the PDF document 123 * @param folder the folder where to write 124 * @param imageFormat the target format (ex. "png") 125 * @param password the password (needed if the PDF is encrypted) 126 * @param startPage the start page (1 is the first page) 127 * @param endPage the end page (set to Integer.MAX_VALUE for all pages) 128 * @param outputPrefix used to construct the filename for the individual images 129 * @param imageType the image type (see {@link BufferedImage}.TYPE_*) 130 * @param resolution the resolution in dpi (dots per inch) 131 * @param quality the image compression quality (0 < quality < 1.0f). 132 * @return true if the images were produced, false if there was an error 133 * @throws IOException if an I/O error occurs 134 * @throws FlipbookException if an error occurs when manipulating the flipbook 135 */ 136 protected List<String> writeImages(PDDocument document, File folder, String imageFormat, String password, int startPage, int endPage, String outputPrefix, int imageType, int resolution, float quality) throws IOException, FlipbookException 137 { 138 List<String> fileNames = new ArrayList<>(); 139 140 List pages = document.getDocumentCatalog().getAllPages(); 141 int digitCount = Integer.toString(pages.size()).length(); 142 // %03d.png 143 String format = "%0" + digitCount + "d." + imageFormat; 144 for (int i = startPage - 1; i < endPage && i < pages.size(); i++) 145 { 146 PDPage page = (PDPage) pages.get(i); 147 BufferedImage image = page.convertToImage(imageType, resolution); 148 String fileName = outputPrefix + String.format(format, i + 1, imageFormat); 149 150 fileNames.add(fileName); 151 152 File imageFile = new File(folder, fileName + ".part"); 153 154 boolean foundWriter = ImageIOUtil.writeImage(image, imageFormat, imageFile, resolution, quality); 155 156 imageFile.renameTo(new File(folder, fileName)); 157 158 if (!foundWriter) 159 { 160 throw new FlipbookException("No writer found for format '" + imageFormat + "'"); 161 } 162 } 163 164 return fileNames; 165 } 166 167 public List<String> getSupportedMimeTypes() 168 { 169 List<String> mimeTypeSupported = new ArrayList<>(); 170 mimeTypeSupported.add("application/pdf"); 171 172 return mimeTypeSupported; 173 } 174 175 /** 176 * Generate the preview image 177 * @param folder The folder with the PDF images 178 * @param outputPrefix The prefix of PDF images 179 * @throws IOException if an I/O error occurs 180 */ 181 protected void writePreview(File folder, String outputPrefix) throws IOException 182 { 183 File[] images = folder.listFiles(new FileFilter() 184 { 185 public boolean accept(File pathname) 186 { 187 return pathname.getName().startsWith(outputPrefix); 188 } 189 }); 190 191 // Compute preview dimension from first image 192 File firstImage = images[0]; 193 BufferedImage bfi = ImageIO.read(firstImage); 194 double imgRatio = (double) bfi.getHeight() / bfi.getWidth(); 195 196 int rows = (int) Math.ceil(1 + images.length / 2); 197 198 int imgWidthInPreview = 56; 199 int imgHeightInPreview = (int) Math.round(imgWidthInPreview * imgRatio); 200 201 // Create a image on 2 columns 202 BufferedImage result = new BufferedImage(imgWidthInPreview * 2, imgHeightInPreview * rows, BufferedImage.TYPE_INT_RGB); 203 Graphics g = result.getGraphics(); 204 205 int x = 0; 206 int y = 0; 207 int count = 0; 208 for (File image : images) 209 { 210 BufferedImage bi = ImageIO.read(image); 211 BufferedImage ri = _resizeImage(bi, imgHeightInPreview, imgWidthInPreview); 212 g.drawImage(ri, x, y, null); 213 x += imgWidthInPreview; 214 if (count == 0 || x >= result.getWidth()) 215 { 216 x = 0; 217 y += ri.getHeight(); 218 } 219 count++; 220 } 221 222 ImageIO.write(result, "jpg" , new File(folder, "preview.jpg")); 223 } 224 225 private static BufferedImage _resizeImage(BufferedImage src, int maxHeight, int maxWidth) 226 { 227 int srcHeight = src.getHeight(); 228 int srcWidth = src.getWidth(); 229 230 int destHeight = 0; 231 int destWidth = 0; 232 233 boolean keepAspectRatio = true; 234 235 236 if (srcHeight <= maxHeight && srcWidth <= maxWidth || destHeight == srcHeight && destWidth == srcWidth) 237 { 238 // the source image is already smaller than the destination box or already the good format, don't change anything 239 return src; 240 } 241 242 destWidth = maxWidth; 243 destHeight = maxHeight; 244 245 Dimension srcDimension = new Dimension(srcWidth, srcHeight); 246 Dimension thumbnailDimension = new Dimension(destWidth, destHeight); 247 248 BufferedImage thumbImage = new FixedSizeThumbnailMaker(destWidth, destHeight, keepAspectRatio, true) 249 .resizer(DefaultResizerFactory.getInstance().getResizer(srcDimension, thumbnailDimension)) 250 .imageType(src.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB) 251 .make(src); 252 253 return thumbImage; 254 } 255}