/* * Copyright (C) 2006-2014 Gabriel Burca (gburca dash virtmus at ebixio dot com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.ebixio.virtmus.imgsrc; import com.ebixio.virtmus.MusicPage; import com.ebixio.virtmus.Utils; import com.ebixio.virtmus.VirtMusKernel; import com.ebixio.virtmus.options.Options.Rotation; import com.ebixio.virtmus.stats.StatsCollector; import com.thoughtworks.xstream.annotations.XStreamAlias; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.renderable.ParameterBlock; import java.io.File; import java.lang.ref.WeakReference; import javax.media.jai.Interpolation; import javax.media.jai.JAI; import javax.media.jai.KernelJAI; import javax.media.jai.PlanarImage; import javax.media.jai.RenderedOp; import org.apache.batik.ext.awt.RenderingHintsKeyExt; /** * A proxy class for PDF image sources. Some PDF pages render better with * org.icepdf, others with com.sun.pdfview. This class will try to defer to * one of these two implementations. * * @author Gabriel Burca <gburca dash virtmus at ebixio dot com> */ @XStreamAlias("pdfImg") public class PdfImg extends ImgSrc { public int pageNum; private PdfImg pdfSrc; private Object pdfSrcLock = new Object(); protected transient String pageErr = null; protected transient File tmpImgFile = null; public PdfImg(File sourceFile, int pageNum) { super(sourceFile); this.pageNum = pageNum; } @Override public ImgType getImgType() { return ImgType.PDF; } @Override public Dimension getDimension() { return getPdfSrc().getDimension(); } // Used by Thumbs and Live display @Override public BufferedImage getImage(Dimension containerSize, Rotation rotation, boolean fillSize, MusicPage page) { RenderedOp srcImg, destImg; Rectangle destSize; // Acquiring the current Graphics Device and Graphics Configuration GraphicsEnvironment graphEnv = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice graphDevice = graphEnv.getDefaultScreenDevice(); GraphicsConfiguration graphicConf = graphDevice.getDefaultConfiguration(); System.gc(); BufferedImage result = graphicConf.createCompatibleImage(containerSize.width, containerSize.height, Transparency.OPAQUE); //BufferedImage result = new BufferedImage(containerSize.width, containerSize.height, BufferedImage.TYPE_INT_ARGB_PRE); // TYPE_4BYTE_ABGR_PRE (instead of TYPE_INT_ARGB_PRE) REQUIRED for OpenGL //BufferedImage result = new BufferedImage(containerSize.width, containerSize.height, BufferedImage.TYPE_4BYTE_ABGR_PRE); Graphics2D g = result.createGraphics(); /** If a BUFFERED_IMAGE hint is not provided, the batik code issues the following warning: * "Graphics2D from BufferedImage lacks BUFFERED_IMAGE hint" * * See: http://mail-archives.apache.org/mod_mbox/xmlgraphics-batik-dev/200603.mbox/%3C20060309110529.1B7C96ACA9@ajax%3E * See: org.apache.batik.ext.awt.image.GraphicsUtil getDestination(Graphics2D g2d) */ RenderingHints renderingHints = new RenderingHints( RenderingHintsKeyExt.KEY_BUFFERED_IMAGE, new WeakReference<>(result)); g.addRenderingHints(renderingHints); g.setColor(Color.BLACK); g.fillRect(0, 0, result.getWidth(), result.getHeight()); g.setColor(Color.WHITE); RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); RenderingHints interpHints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); AffineTransform origXform = g.getTransform(); g.setTransform(rotation.getTransform(containerSize.getSize())); switch (rotation) { case Clockwise_90: case Clockwise_270: g.setRenderingHints(interpHints); // Rotate the container size if the image is rotated sideways destSize = new Rectangle(containerSize.height, containerSize.width); break; default: destSize = new Rectangle(containerSize); break; } float scale = (float)Utils.scaleProportional(destSize, new Rectangle(getDimension())); if (pageErr != null) { return errText(result, g, pageErr, destSize); } srcImg = getFullRenderedOp(); /* When using the "scale" operator to reduce the size of an image, the result is very poor, * even with bicubic interpolation. SubsampleAverage gives much better results, but can * only scale down an image. */ if (scale == 1.0) { destImg = srcImg; } else if (scale < 1.0 && Math.min(destSize.height, destSize.width) < 600) { // For the SubsampleAverage operator, scale must be (0,1] /** SubsampleAverage sometimes creates black horizontal lines which * look almost like staff lines. This only happens at certain scale * factors. We will therefore only use this for icons and thumbnails. * Anything larger than that will be scaled below. */ destImg = JAI.create("SubsampleAverage", srcImg, (double)scale, (double)scale, qualityHints); } else { if (scale < 1.0) { // We apply a mild low-pass filter first. See: // http://archives.java.sun.com/cgi-bin/wa?A2=ind0311&L=jai-interest&P=15036 // http://www.leptonica.com/scaling.html KernelJAI k; k = VirtMusKernel.getKernel(1, 1, 6); //k = VirtMusKernel.getKernel(0, 0, 1); // Identity kernel destImg = JAI.create("Convolve", srcImg, k); srcImg = destImg; } else { // scale > 1.0 scale = Math.min(scale, 2.0F); // Don't zoom in more than 2x } // Create a bicubic interpolation object to be used with the "scale" operator Interpolation interp = Interpolation.getInstance(Interpolation.INTERP_BICUBIC); scale = Math.min(scale, 2.0F); // Don't zoom in more than 2x ParameterBlock params = new ParameterBlock(); params.addSource(srcImg); params.add(scale); // x scale factor params.add(scale); // y scale factor params.add(0.0F); // x translation params.add(0.0F); // y translation params.add(interp); // interpolation method destImg = JAI.create("scale", params); } srcImg = destImg; Point destPt; if (fillSize) { destPt = Utils.centerItem(destSize, Utils.scale(new Rectangle(getDimension()), scale)); } else { destPt = new Point(0, 0); } AffineTransform newXform = g.getTransform(); newXform.concatenate(AffineTransform.getTranslateInstance(destPt.x, destPt.y)); g.setTransform(newXform); // Image is already scaled. Draw it before applying the scaling transform. g.drawImage(srcImg.getAsBufferedImage(), 0, 0, null); // The annotations need to be scaled properly before being drawn. newXform.concatenate(AffineTransform.getScaleInstance(scale, scale)); g.setTransform(newXform); if (page != null) page.paintAnnotations(g); g.setTransform(origXform); Dimension dim = srcImg.getBounds().getSize(); srcImg.dispose(); g.dispose(); if (fillSize) { return result; } else { return result.getSubimage(0, 0, dim.width, dim.height); } } @Override public PlanarImage getFullImg() { return getPdfSrc().getFullImg(); } @Override public File createImageFile() { return getPdfSrc().createImageFile(); } @Override public void destroyImageFile() { try { if (tmpImgFile != null) tmpImgFile.delete(); tmpImgFile = null; } catch (Exception e) { } } protected RenderedOp getFullRenderedOp() { return getPdfSrc().getFullRenderedOp(); } /** * Here we decide what PDF rasterizer to use for this page. * @return */ private PdfImg getPdfSrc() { synchronized (pdfSrcLock) { if (pdfSrc == null) { IcePdfImg icePdfSrc = new IcePdfImg(sourceFile, pageNum); if (icePdfSrc.canRender()) { pdfSrc = icePdfSrc; StatsCollector.findInstance().usingRenderer("org.icepdf"); } else { pdfSrc = new PdfViewImg(sourceFile, pageNum); StatsCollector.findInstance().usingRenderer("com.sun.pdfview"); } } return pdfSrc; } } /** * Used to display the PDF type in the page property sheet. * @return Class of the inner PDF rasterizer. */ public Class<?> getInnerClass() { return getPdfSrc().getClass(); } /** * @return the pageNum */ public int getPageNum() { return pageNum; } /** * @param pageNum the PDF page number */ public void setPageNum(int pageNum) { this.pageNum = pageNum; } @Override public String getName() { return super.getName() + " p" + (pageNum + 1); } public void setDimension(Dimension dim) { } @Override public void setSourceFile(File sourceFile) { synchronized (pdfSrcLock) { pdfSrc = null; // Need to regenerate this with the new PDF. super.setSourceFile(sourceFile); } } }