PngUtils.java
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2013-2015 ForgeRock AS.
*/
package org.forgerock.doc.maven.utils;
import org.apache.commons.io.FileUtils;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
/**
* Set dots per inch in the metadata of a Portable Network Graphics image.
*/
public final class PngUtils {
/**
* Return image height in pixels.
*
* @param image image file.
* @throws IOException Failed to read the image.
* @return Image height in pixels.
*/
private static int getHeight(final File image) throws IOException {
BufferedImage bufferedImage = ImageIO.read(image);
return bufferedImage.getHeight();
}
/**
* Return image width in pixels.
*
* @param image image file.
* @throws IOException Failed to read the image.
* @return Image width in pixels.
*/
private static int getWidth(final File image) throws IOException {
BufferedImage bufferedImage = ImageIO.read(image);
return bufferedImage.getWidth();
}
/**
* Creates a thumbnail copy of provided image, prefixed with "thumb_".
*
* @param image image file.
* @throws IOException Failed to read the image or to write the thumbnail.
*/
public static void resizePng(final File image)
throws IOException {
BufferedImage originalImage = ImageIO.read(image);
final int imageWidth = getWidth(image);
final int imageHeight = getHeight(image);
final int newWidth = 700;
String absolutePath = image.getAbsolutePath();
/** File thumbFile = new File(absolutePath.substring(0, absolutePath
.lastIndexOf(File.separator)) + File.separator + "thumb_"
+ image.getName()); */
File thumbFile = new File(image.getParent(), "thumb_" + image.getName());
if (imageWidth > newWidth) {
final int newHeight = Math.round(imageHeight * newWidth / imageWidth);
/* System.out.println("Creating thumbnail of: " + image.getName()
+ " (" + newWidth + " x " + newHeight + ")"); */
BufferedImage scaledBI = getScaledInstance(
originalImage,
newWidth,
newHeight,
RenderingHints.VALUE_INTERPOLATION_BILINEAR,
true);
saveBufferedImage(scaledBI, thumbFile, 160);
} else {
saveBufferedImage(originalImage, thumbFile, 160);
}
}
/**
* Convenience method that returns a scaled instance of the
* provided {@code BufferedImage}.
*
* @param img the original image to be scaled
* @param targetWidth the desired width of the scaled instance,
* in pixels
* @param targetHeight the desired height of the scaled instance,
* in pixels
* @param hint one of the rendering hints that corresponds to
* {@code RenderingHints.KEY_INTERPOLATION} (e.g.
* {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
* {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
* {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
* @param higherQuality if true, this method will use a multi-step
* scaling technique that provides higher quality than the usual
* one-step technique (only useful in downscaling cases, where
* {@code targetWidth} or {@code targetHeight} is
* smaller than the original dimensions, and generally only when
* the {@code BILINEAR} hint is specified)
* @return a scaled version of the original {@code BufferedImage}
*/
public static BufferedImage getScaledInstance(
BufferedImage img,
int targetWidth,
int targetHeight,
Object hint,
boolean higherQuality) {
int type = (img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
int w, h;
if (higherQuality) {
// Use multi-step technique: start with original size, then
// scale down in multiple passes with drawImage()
// until the target size is reached
w = img.getWidth();
h = img.getHeight();
} else {
// Use one-step technique: scale directly from original
// size to target size with a single drawImage() call
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
return ret;
}
/**
* Set the DPI on {@code image} so that it fits in {@code maxHeightInInches},
* or to 160 if short enough.
*
* @param image PNG image file.
* @param maxHeightInInches maximum available image height in inches.
* @throws IOException Failed to save the image.
*/
public static void setSafeDpi(final File image, final int maxHeightInInches)
throws IOException {
final int imageHeight = getHeight(image);
final int defaultDpi = 160;
final int defaultMaxHeight = maxHeightInInches * defaultDpi;
// Images that do not fit by default must be
if (imageHeight > defaultMaxHeight) {
final double dpi = imageHeight * 1.0 / maxHeightInInches;
setDpi(image, (int) Math.round(dpi));
} else {
setDpi(image);
}
}
/**
* Set the DPI on {@code image} to 160.
*
* @param image PNG image file.
* @throws IOException Failed to save the image.
*/
public static void setDpi(final File image) throws IOException {
setDpi(image, 160);
}
/**
* Set the DPI on {@code image} to {@code dotsPerInch}.
*
* @param image PNG image file.
* @param dotsPerInch DPI to set in metadata.
* @throws IOException Failed to save the image.
*/
public static void setDpi(final File image, final int dotsPerInch) throws IOException {
BufferedImage in = ImageIO.read(image);
File updatedImage = File.createTempFile(image.getName(), ".tmp");
saveBufferedImage(in, updatedImage, dotsPerInch);
FileUtils.deleteQuietly(image);
FileUtils.moveFile(updatedImage, image);
}
/*
* Save an image, setting the DPI.
*
* @param bufferedImage The image to save.
* @param outputFile The file to save the image to.
* @param dotsPerInch The DPI setting to use.
* @throws IOException Failed to write the image.
*/
private static void saveBufferedImage(final BufferedImage bufferedImage,
final File outputFile,
final int dotsPerInch)
throws IOException {
for (Iterator<ImageWriter> iw = ImageIO.getImageWritersByFormatName("png"); iw.hasNext();) {
ImageWriter writer = iw.next();
ImageWriteParam writeParam = writer.getDefaultWriteParam();
ImageTypeSpecifier typeSpecifier =
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);
if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) {
continue;
}
setDpi(metadata, dotsPerInch);
final ImageOutputStream stream = ImageIO.createImageOutputStream(outputFile);
try {
writer.setOutput(stream);
writer.write(metadata, new IIOImage(bufferedImage, null, metadata), writeParam);
} finally {
stream.close();
}
break;
}
}
/*
* Set the DPI in image metadata.
*
* @param metadata Image metadata.
* @param dotsPerInch DPI setting to set.
* @throws IIOInvalidTreeException Failed to write metadata.
*/
private static void setDpi(IIOMetadata metadata, final int dotsPerInch)
throws IIOInvalidTreeException {
final double inchesPerMillimeter = 1.0 / 25.4;
final double dotsPerMillimeter = dotsPerInch * inchesPerMillimeter;
IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
horizontalPixelSize.setAttribute("value", Double.toString(dotsPerMillimeter));
IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
verticalPixelSize.setAttribute("value", Double.toString(dotsPerMillimeter));
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
dimension.appendChild(horizontalPixelSize);
dimension.appendChild(verticalPixelSize);
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
root.appendChild(dimension);
metadata.mergeTree("javax_imageio_1.0", root);
}
private PngUtils() {
// Not used.
}
}