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