package cc.abstra.trantor.pdfconverter; /** * * @author nando */ import cc.abstra.trantor.pdfconverter.exceptions.*; import com.sun.media.jai.codec.FileSeekableStream; import com.sun.media.imageio.plugins.tiff.TIFFDirectory; import com.sun.media.jai.codecimpl.TIFFImageDecoder; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.sanselan.ImageInfo; import org.apache.sanselan.ImageReadException; import org.apache.sanselan.Sanselan; import javax.imageio.*; import javax.imageio.metadata.IIOInvalidTreeException; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.spi.IIORegistry; import javax.imageio.stream.FileImageInputStream; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; public class TiffDoc { private ImageReader tiffImageReader = null; private ImageWriter pngImageWriter = null; private File tiffInputFile; private int numPages; private HashMap<String,Integer> dpiRes = new LinkedHashMap<>(); static { IIORegistry registry = IIORegistry.getDefaultInstance(); registry.registerServiceProvider(new com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriterSpi()); registry.registerServiceProvider(new com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi()); if (!getImageReaders().contains(Consts.TIF)) { throw new NoSuchImageIOReader("TIFF reader"); } if (!getImageWriters().contains(Consts.TIF)) { throw new NoSuchImageIOWriter("TIFF writer"); } } public TiffDoc(String tiff) throws IOException, ImageReadException, InsufficientResolution, CorruptMetadata { Iterator<ImageReader> readersIterator = ImageIO.getImageReadersByFormatName(Consts.TIF); this.tiffImageReader = readersIterator.next(); Iterator<ImageWriter> writersIterator = ImageIO.getImageWritersByFormatName(Consts.PNG); this.pngImageWriter = writersIterator.next(); this.tiffInputFile = new File(tiff); final ImageInfo metadata = Sanselan.getImageInfo(tiffInputFile); // For all EXIF tags explained, see: http://topo.math.u-psud.fr/~bousch/exifdump.py if(Consts.MIN_DPI > metadata.getPhysicalHeightDpi() || Consts.MIN_DPI > metadata.getPhysicalWidthDpi()){ if(Consts.CORRUPT_DPI == metadata.getPhysicalHeightDpi() || Consts.CORRUPT_DPI == metadata.getPhysicalWidthDpi()) { Logger.getLogger(TiffDoc.class.getName()).log(Level.WARNING, "EXIF info is corrupt. Trying to read TIFF metadata"); this.dpiRes = getResolutionFromTIFFMetadata(tiffInputFile); } else { throw new InsufficientResolution( "This image is unsuitable for storage. The minimum resolution is "+ Integer.toString(Consts.MIN_DPI)+". Got: ("+ Integer.toString(metadata.getPhysicalWidthDpi())+","+ Integer.toString(metadata.getPhysicalHeightDpi())+")"); } } this.dpiRes.put(Consts.TIFFMetadata.X_RES, metadata.getPhysicalWidthDpi()); this.dpiRes.put(Consts.TIFFMetadata.Y_RES, metadata.getPhysicalHeightDpi()); try { this.numPages = getPageCount(tiffInputFile); } catch (PagesInImageFileIsNull | NoSuchImageDecoder ex) { Logger.getLogger(TiffDoc.class.getName()).log(Level.SEVERE, null, ex); } } public void toPngPreview(String output) throws IOException { try (ImageOutputStream pngOutputStream = ImageIO.createImageOutputStream(new File(output)); ImageInputStream tiffInputStream = ImageIO.createImageInputStream(tiffInputFile)) { BufferedImage imgFirstPage = getTiffImageReaderForImage(tiffInputStream).read(Consts.FIRST_PAGE, null); ImageWriter writer = getPNGImageWriterForImage(Consts.PREVIEW_DPI, pngOutputStream); IIOMetadata writerMetadata = getWriterMetadata(writer); writer.write(writerMetadata, new IIOImage(ImageHelper.sanitizeImage(imgFirstPage, false), null, writerMetadata), writer.getDefaultWriteParam()); imgFirstPage.flush(); } } public void toPdf(String output) throws IOException, NoSuchImageDecoder { List<Map<String, Object>> imgList = new ArrayList<>(); try (FileSeekableStream tss = new FileSeekableStream(tiffInputFile); ImageInputStream tis = ImageIO.createImageInputStream(tiffInputFile)) { TIFFImageDecoder dec = new TIFFImageDecoder(tss,null); for (int page = Consts.FIRST_PAGE; page < numPages; page++) { Map<String, Object> imgInfo = new LinkedHashMap<>(); Logger.getLogger(TiffDoc.class.getName()).log(Level.INFO, "Page "+(page+1)+" of "+numPages+" read."); RenderedImage ri; try { ri = dec.decodeAsRenderedImage(page); } catch (IOException ex){ Logger.getLogger(TiffDoc.class.getName()).log(Level.WARNING, "The image couldn't be read as a FileSeekableStream. Trying with ImageInputStream..."); ri = getTiffImageReaderForImage(tis).read(Consts.FIRST_PAGE, null); } BufferedImage img = ImageHelper.resizeImageToDINA4WithDPI( ImageHelper.sanitizeImage(ri, true), Consts.PREVIEW_DPI, Consts.PREVIEW_DPI, dpiRes.get(Consts.TIFFMetadata.X_RES), dpiRes.get(Consts.TIFFMetadata.Y_RES)); imgInfo.put(Consts.IMAGE_KEY, img); imgInfo.put(Consts.LANDSCAPE_KEY, ImageHelper.hasLandscapeOrientation(img)); imgInfo.put(Consts.PAGE_SIZE_KEY, PDPage.PAGE_SIZE_A4); //default preview size imgList.add(imgInfo); img.flush(); } PdfDoc.writePageListToPdf(imgList, output); } } public static List<String> getImageReaders() { return Arrays.asList(ImageIO.getReaderFormatNames()); } public static List<String> getImageWriters() { return Arrays.asList(ImageIO.getWriterFormatNames()); } private ImageReader getTiffImageReaderForImage(ImageInputStream iis) throws NoSuchImageIOReader, IOException { tiffImageReader.setInput(iis, true, true); return tiffImageReader; } private ImageWriter getPNGImageWriterForImage(int dpi, ImageOutputStream img) throws NoSuchImageIOWriter { setDpiPNG(getWriterMetadata(pngImageWriter), dpi); pngImageWriter.setOutput(img); return pngImageWriter; } private static IIOMetadata getWriterMetadata(ImageWriter writer) { ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); return writer.getDefaultImageMetadata(typeSpecifier, writer.getDefaultWriteParam()); } private static int getPageCount(File file) throws IOException, PagesInImageFileIsNull, NoSuchImageDecoder { Integer numPages = null; try (FileSeekableStream tiffSeekableStream = new FileSeekableStream(file)) { TIFFImageDecoder dec = new TIFFImageDecoder(tiffSeekableStream, null); numPages = dec.getNumPages(); } catch (NullPointerException ex) { throw new NoSuchImageDecoder("TIFF"); } if (null == numPages) { throw new PagesInImageFileIsNull("TIFF file " + file.getName()); } return numPages; } private static void setDpiPNG(IIOMetadata metadata, int dpi) { // // See: http://stackoverflow.com/questions/10660695/how-to-set-dots-per-square-inch-dpi-for-png-in-java // //for PNG, it's dots per millimeter String dotsPerMilli = Consts.getDotsPerMilliAsString(dpi); IIOMetadataNode horiz = new IIOMetadataNode(Consts.PNGMetadata.H_PIXEL_SIZE); horiz.setAttribute(Consts.PNGMetadata.VALUE, dotsPerMilli); IIOMetadataNode vert = new IIOMetadataNode(Consts.PNGMetadata.V_PIXEL_SIZE); vert.setAttribute(Consts.PNGMetadata.VALUE, dotsPerMilli); IIOMetadataNode dim = new IIOMetadataNode(Consts.PNGMetadata.DIMENSION); dim.appendChild(horiz); dim.appendChild(vert); IIOMetadataNode root = new IIOMetadataNode(Consts.IMAGEIO_API_VERSION); root.appendChild(dim); try { metadata.mergeTree(Consts.IMAGEIO_API_VERSION, root); } catch (IIOInvalidTreeException ex) { Logger.getLogger(TiffDoc.class.getName()).log(Level.SEVERE, null, ex); } } private HashMap<String, Integer> getResolutionFromTIFFMetadata(File tiffInputFile) throws IOException, CorruptMetadata { HashMap<String, Integer> resInfo = new LinkedHashMap<>(); try (ImageInputStream tiffInputStream = new FileImageInputStream(tiffInputFile)) { ImageReader reader = getTiffImageReaderForImage(tiffInputStream); IIOMetadata imageMetadata = reader.getImageMetadata(Consts.FIRST_PAGE); TIFFDirectory ifd = TIFFDirectory.createFromMetadata(imageMetadata); Integer xRes, yRes; try { xRes = Integer.parseInt(ifd.getTIFFField(Consts.TIFFMetadata.X_RES_TAG).getValueAsString(0).split( Consts.TIFFMetadata.RES_DELIMITER)[0]); yRes = Integer.parseInt(ifd.getTIFFField(Consts.TIFFMetadata.X_RES_TAG).getValueAsString(0).split( Consts.TIFFMetadata.RES_DELIMITER)[0]); } catch (NullPointerException npe){ // Some TIFF reading/generating tools default to 72dpi // See: http://www.imagemagick.org/discourse-server/viewtopic.php?f=3&t=22457 // However: throw new CorruptMetadata("Unable to read resolution"); } String resUnit; try{ resUnit = ifd.getTIFFField(Consts.TIFFMetadata.RESOLUTION_UNIT_TAG).getValueAsString(0); } catch (NullPointerException npe) { Logger.getLogger(TiffDoc.class.getName()).log(Level.WARNING, "Unable to read resolution unit. Defaulting to inches..."); resUnit = Consts.TIFFMetadata.RESOLUTION_UNIT_INCH; } if (Consts.TIFFMetadata.RESOLUTION_UNIT_CM.equals(resUnit)) { xRes = new Double(xRes / Consts.INCH_TO_CM).intValue(); yRes = new Double(yRes / Consts.INCH_TO_CM).intValue(); } resInfo.put(Consts.TIFFMetadata.X_RES, xRes); resInfo.put(Consts.TIFFMetadata.Y_RES, yRes); } return resInfo; } }