View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2013-2015 ForgeRock AS.
15   */
16  
17  package org.forgerock.doc.maven.utils;
18  
19  import org.apache.commons.io.FileUtils;
20  
21  import javax.imageio.IIOImage;
22  import javax.imageio.ImageIO;
23  import javax.imageio.ImageTypeSpecifier;
24  import javax.imageio.ImageWriteParam;
25  import javax.imageio.ImageWriter;
26  import javax.imageio.metadata.IIOInvalidTreeException;
27  import javax.imageio.metadata.IIOMetadata;
28  import javax.imageio.metadata.IIOMetadataNode;
29  import javax.imageio.stream.ImageOutputStream;
30  import java.awt.Graphics2D;
31  import java.awt.RenderingHints;
32  import java.awt.Transparency;
33  import java.awt.image.BufferedImage;
34  import java.io.File;
35  import java.io.IOException;
36  import java.util.Iterator;
37  
38  /**
39   * Set dots per inch in the metadata of a Portable Network Graphics image.
40   */
41  public final class PngUtils {
42  
43      /**
44       * Return image height in pixels.
45       *
46       * @param image image file.
47       * @throws IOException Failed to read the image.
48       * @return Image height in pixels.
49       */
50      private static int getHeight(final File image) throws IOException {
51          BufferedImage bufferedImage = ImageIO.read(image);
52          return bufferedImage.getHeight();
53      }
54      /**
55       * Return image width in pixels.
56       *
57       * @param image image file.
58       * @throws IOException Failed to read the image.
59       * @return Image width in pixels.
60       */
61      private static int getWidth(final File image) throws IOException {
62          BufferedImage bufferedImage = ImageIO.read(image);
63          return bufferedImage.getWidth();
64      }
65      /**
66       * Creates a thumbnail copy of provided image, prefixed with "thumb_".
67       *
68       * @param image image file.
69       * @throws IOException Failed to read the image or to write the thumbnail.
70       */
71      public static void resizePng(final File image)
72              throws IOException {
73          BufferedImage originalImage = ImageIO.read(image);
74  
75          final int imageWidth = getWidth(image);
76          final int imageHeight = getHeight(image);
77          final int newWidth = 700;
78  
79          String absolutePath = image.getAbsolutePath();
80  
81          /** File thumbFile = new File(absolutePath.substring(0, absolutePath
82                  .lastIndexOf(File.separator)) + File.separator + "thumb_"
83                  + image.getName()); */
84  
85          File thumbFile = new File(image.getParent(), "thumb_" + image.getName());
86  
87          if (imageWidth > newWidth) {
88  
89              final int newHeight = Math.round(imageHeight * newWidth / imageWidth);
90  
91              /* System.out.println("Creating thumbnail of: " + image.getName()
92                      + " (" + newWidth + " x " + newHeight + ")"); */
93  
94              BufferedImage scaledBI = getScaledInstance(
95                      originalImage,
96                      newWidth,
97                      newHeight,
98                      RenderingHints.VALUE_INTERPOLATION_BILINEAR,
99                      true);
100 
101             saveBufferedImage(scaledBI, thumbFile, 160);
102         } else {
103             saveBufferedImage(originalImage, thumbFile, 160);
104         }
105     }
106 
107     /**
108      * Convenience method that returns a scaled instance of the
109      * provided {@code BufferedImage}.
110      *
111      * @param img the original image to be scaled
112      * @param targetWidth the desired width of the scaled instance,
113      *    in pixels
114      * @param targetHeight the desired height of the scaled instance,
115      *    in pixels
116      * @param hint one of the rendering hints that corresponds to
117      *    {@code RenderingHints.KEY_INTERPOLATION} (e.g.
118      *    {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
119      *    {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
120      *    {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
121      * @param higherQuality if true, this method will use a multi-step
122      *    scaling technique that provides higher quality than the usual
123      *    one-step technique (only useful in downscaling cases, where
124      *    {@code targetWidth} or {@code targetHeight} is
125      *    smaller than the original dimensions, and generally only when
126      *    the {@code BILINEAR} hint is specified)
127      * @return a scaled version of the original {@code BufferedImage}
128      */
129     public static BufferedImage getScaledInstance(
130         BufferedImage img,
131         int targetWidth,
132         int targetHeight,
133         Object hint,
134         boolean higherQuality) {
135         int type = (img.getTransparency() == Transparency.OPAQUE)
136                 ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
137         BufferedImage ret = (BufferedImage) img;
138         int w, h;
139         if (higherQuality) {
140             // Use multi-step technique: start with original size, then
141             // scale down in multiple passes with drawImage()
142             // until the target size is reached
143             w = img.getWidth();
144             h = img.getHeight();
145         } else {
146             // Use one-step technique: scale directly from original
147             // size to target size with a single drawImage() call
148             w = targetWidth;
149             h = targetHeight;
150         }
151 
152         do {
153             if (higherQuality && w > targetWidth) {
154                 w /= 2;
155                 if (w < targetWidth) {
156                     w = targetWidth;
157                 }
158             }
159 
160             if (higherQuality && h > targetHeight) {
161                 h /= 2;
162                 if (h < targetHeight) {
163                     h = targetHeight;
164                 }
165             }
166 
167             BufferedImage tmp = new BufferedImage(w, h, type);
168             Graphics2D g2 = tmp.createGraphics();
169             g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
170             g2.drawImage(ret, 0, 0, w, h, null);
171             g2.dispose();
172 
173             ret = tmp;
174         } while (w != targetWidth || h != targetHeight);
175 
176         return ret;
177     }
178 
179     /**
180      * Set the DPI on {@code image} so that it fits in {@code maxHeightInInches},
181      * or to 160 if short enough.
182      *
183      * @param image PNG image file.
184      * @param maxHeightInInches maximum available image height in inches.
185      * @throws IOException Failed to save the image.
186      */
187     public static void setSafeDpi(final File image, final int maxHeightInInches)
188             throws IOException {
189         final int imageHeight = getHeight(image);
190         final int defaultDpi = 160;
191         final int defaultMaxHeight = maxHeightInInches * defaultDpi;
192 
193         // Images that do not fit by default must be
194         if (imageHeight > defaultMaxHeight) {
195             final double dpi = imageHeight * 1.0 / maxHeightInInches;
196             setDpi(image, (int) Math.round(dpi));
197         } else {
198             setDpi(image);
199         }
200     }
201 
202     /**
203      * Set the DPI on {@code image} to 160.
204      *
205      * @param image PNG image file.
206      * @throws IOException Failed to save the image.
207      */
208     public static void setDpi(final File image) throws IOException {
209         setDpi(image, 160);
210     }
211 
212     /**
213      * Set the DPI on {@code image} to {@code dotsPerInch}.
214      *
215      * @param image PNG image file.
216      * @param dotsPerInch DPI to set in metadata.
217      * @throws IOException Failed to save the image.
218      */
219     public static void setDpi(final File image, final int dotsPerInch) throws IOException {
220         BufferedImage in = ImageIO.read(image);
221         File updatedImage = File.createTempFile(image.getName(), ".tmp");
222         saveBufferedImage(in, updatedImage, dotsPerInch);
223 
224         FileUtils.deleteQuietly(image);
225         FileUtils.moveFile(updatedImage, image);
226     }
227 
228     /*
229      * Save an image, setting the DPI.
230      *
231      * @param bufferedImage The image to save.
232      * @param outputFile The file to save the image to.
233      * @param dotsPerInch The DPI setting to use.
234      * @throws IOException Failed to write the image.
235      */
236     private static void saveBufferedImage(final BufferedImage bufferedImage,
237                                           final File outputFile,
238                                           final int dotsPerInch)
239             throws IOException {
240         for (Iterator<ImageWriter> iw = ImageIO.getImageWritersByFormatName("png"); iw.hasNext();) {
241             ImageWriter writer = iw.next();
242             ImageWriteParam writeParam = writer.getDefaultWriteParam();
243             ImageTypeSpecifier typeSpecifier =
244                     ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
245             IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);
246             if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) {
247                 continue;
248             }
249 
250             setDpi(metadata, dotsPerInch);
251 
252             final ImageOutputStream stream = ImageIO.createImageOutputStream(outputFile);
253             try {
254                 writer.setOutput(stream);
255                 writer.write(metadata, new IIOImage(bufferedImage, null, metadata), writeParam);
256             } finally {
257                 stream.close();
258             }
259             break;
260         }
261     }
262 
263     /*
264      * Set the DPI in image metadata.
265      *
266      * @param metadata Image metadata.
267      * @param dotsPerInch DPI setting to set.
268      * @throws IIOInvalidTreeException Failed to write metadata.
269      */
270     private static void setDpi(IIOMetadata metadata, final int dotsPerInch)
271             throws IIOInvalidTreeException {
272 
273         final double inchesPerMillimeter = 1.0 / 25.4;
274         final double dotsPerMillimeter = dotsPerInch * inchesPerMillimeter;
275 
276         IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
277         horizontalPixelSize.setAttribute("value", Double.toString(dotsPerMillimeter));
278 
279         IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
280         verticalPixelSize.setAttribute("value", Double.toString(dotsPerMillimeter));
281 
282         IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
283         dimension.appendChild(horizontalPixelSize);
284         dimension.appendChild(verticalPixelSize);
285 
286         IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
287         root.appendChild(dimension);
288 
289         metadata.mergeTree("javax_imageio_1.0", root);
290     }
291 
292     private PngUtils() {
293         // Not used.
294     }
295 }