001: /*
002: * Copyright (c) JForum Team
003: * All rights reserved.
004:
005: * Redistribution and use in source and binary forms,
006: * with or without modification, are permitted provided
007: * that the following conditions are met:
008:
009: * 1) Redistributions of source code must retain the above
010: * copyright notice, this list of conditions and the
011: * following disclaimer.
012: * 2) Redistributions in binary form must reproduce the
013: * above copyright notice, this list of conditions and
014: * the following disclaimer in the documentation and/or
015: * other materials provided with the distribution.
016: * 3) Neither the name of "Rafael Steil" nor
017: * the names of its contributors may be used to endorse
018: * or promote products derived from this software without
019: * specific prior written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
022: * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
023: * EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
024: * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
025: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR
026: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
027: * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
028: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
029: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES
030: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
031: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
032: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
033: * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
034: * IN CONTRACT, STRICT LIABILITY, OR TORT
035: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
036: * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
037: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
038: *
039: * This file creation date: 21/04/2004 - 19:54:16
040: * The JForum Project
041: * http://www.jforum.net
042: */
043: package net.jforum.util.image;
044:
045: import java.awt.Dimension;
046: import java.awt.Image;
047: import java.awt.image.BufferedImage;
048: import java.awt.image.PixelGrabber;
049: import java.io.File;
050: import java.io.IOException;
051: import java.util.Iterator;
052: import java.util.Locale;
053:
054: import javax.imageio.IIOImage;
055: import javax.imageio.ImageIO;
056: import javax.imageio.ImageWriteParam;
057: import javax.imageio.ImageWriter;
058: import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
059: import javax.imageio.stream.ImageOutputStream;
060:
061: import net.jforum.exceptions.ForumException;
062:
063: /**
064: * Utilities methods for image manipulation. It does not support writting of GIF images, but it can
065: * read from. GIF images will be saved as PNG.
066: *
067: * @author Rafael Steil
068: * @version $Id: ImageUtils.java,v 1.23 2007/09/09 01:05:22 rafaelsteil Exp $
069: */
070: public class ImageUtils {
071: public static final int IMAGE_UNKNOWN = -1;
072: public static final int IMAGE_JPEG = 0;
073: public static final int IMAGE_PNG = 1;
074: public static final int IMAGE_GIF = 2;
075:
076: /**
077: * Resizes an image
078: *
079: * @param imgName The image name to resize. Must be the complet path to the file
080: * @param type int
081: * @param maxWidth The image's max width
082: * @param maxHeight The image's max height
083: * @return A resized <code>BufferedImage</code>
084: */
085: public static BufferedImage resizeImage(String imgName, int type,
086: int maxWidth, int maxHeight) {
087: try {
088: return resizeImage(ImageIO.read(new File(imgName)), type,
089: maxWidth, maxHeight);
090: } catch (IOException e) {
091: throw new ForumException(e);
092: }
093: }
094:
095: /**
096: * Resizes an image.
097: *
098: * @param image
099: * The image to resize
100: * @param maxWidth
101: * The image's max width
102: * @param maxHeight
103: * The image's max height
104: * @return A resized <code>BufferedImage</code>
105: * @param type
106: * int
107: */
108: public static BufferedImage resizeImage(BufferedImage image,
109: int type, int maxWidth, int maxHeight) {
110: Dimension largestDimension = new Dimension(maxWidth, maxHeight);
111:
112: // Original size
113: int imageWidth = image.getWidth(null);
114: int imageHeight = image.getHeight(null);
115:
116: float aspectRatio = (float) imageWidth / imageHeight;
117:
118: if (imageWidth > maxWidth || imageHeight > maxHeight) {
119: if ((float) largestDimension.width
120: / largestDimension.height > aspectRatio) {
121: largestDimension.width = (int) Math
122: .ceil(largestDimension.height * aspectRatio);
123: } else {
124: largestDimension.height = (int) Math
125: .ceil(largestDimension.width / aspectRatio);
126: }
127:
128: imageWidth = largestDimension.width;
129: imageHeight = largestDimension.height;
130: }
131:
132: return createHeadlessSmoothBufferedImage(image, type,
133: imageWidth, imageHeight);
134: }
135:
136: /**
137: * Saves an image to the disk.
138: *
139: * @param image The image to save
140: * @param toFileName The filename to use
141: * @param type The image type. Use <code>ImageUtils.IMAGE_JPEG</code> to save as JPEG images,
142: * or <code>ImageUtils.IMAGE_PNG</code> to save as PNG.
143: * @return <code>false</code> if no appropriate writer is found
144: */
145: public static boolean saveImage(BufferedImage image,
146: String toFileName, int type) {
147: try {
148: return ImageIO.write(image, type == IMAGE_JPEG ? "jpg"
149: : "png", new File(toFileName));
150: } catch (IOException e) {
151: throw new ForumException(e);
152: }
153: }
154:
155: /**
156: * Compress and save an image to the disk. Currently this method only supports JPEG images.
157: *
158: * @param image The image to save
159: * @param toFileName The filename to use
160: * @param type The image type. Use <code>ImageUtils.IMAGE_JPEG</code> to save as JPEG images,
161: * or <code>ImageUtils.IMAGE_PNG</code> to save as PNG.
162: */
163: public static void saveCompressedImage(BufferedImage image,
164: String toFileName, int type) {
165: try {
166: if (type == IMAGE_PNG) {
167: throw new UnsupportedOperationException(
168: "PNG compression not implemented");
169: }
170:
171: Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
172: ImageWriter writer;
173: writer = (ImageWriter) iter.next();
174:
175: ImageOutputStream ios = ImageIO
176: .createImageOutputStream(new File(toFileName));
177: writer.setOutput(ios);
178:
179: ImageWriteParam iwparam = new JPEGImageWriteParam(Locale
180: .getDefault());
181:
182: iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
183: iwparam.setCompressionQuality(0.7F);
184:
185: writer
186: .write(null, new IIOImage(image, null, null),
187: iwparam);
188:
189: ios.flush();
190: writer.dispose();
191: ios.close();
192: } catch (IOException e) {
193: throw new ForumException(e);
194: }
195: }
196:
197: /**
198: * Creates a <code>BufferedImage</code> from an <code>Image</code>. This method can
199: * function on a completely headless system. This especially includes Linux and Unix systems
200: * that do not have the X11 libraries installed, which are required for the AWT subsystem to
201: * operate. This method uses nearest neighbor approximation, so it's quite fast. Unfortunately,
202: * the result is nowhere near as nice looking as the createHeadlessSmoothBufferedImage method.
203: *
204: * @param image The image to convert
205: * @param w The desired image width
206: * @param h The desired image height
207: * @return The converted image
208: * @param type int
209: */
210: public static BufferedImage createHeadlessBufferedImage(
211: BufferedImage image, int type, int width, int height) {
212: if (type == ImageUtils.IMAGE_PNG && hasAlpha(image)) {
213: type = BufferedImage.TYPE_INT_ARGB;
214: } else {
215: type = BufferedImage.TYPE_INT_RGB;
216: }
217:
218: BufferedImage bi = new BufferedImage(width, height, type);
219:
220: for (int y = 0; y < height; y++) {
221: for (int x = 0; x < width; x++) {
222: bi.setRGB(x, y, image.getRGB(x * image.getWidth()
223: / width, y * image.getHeight() / height));
224: }
225: }
226:
227: return bi;
228: }
229:
230: /**
231: * Creates a <code>BufferedImage</code> from an <code>Image</code>. This method can
232: * function on a completely headless system. This especially includes Linux and Unix systems
233: * that do not have the X11 libraries installed, which are required for the AWT subsystem to
234: * operate. The resulting image will be smoothly scaled using bilinear filtering.
235: *
236: * @param source The image to convert
237: * @param w The desired image width
238: * @param h The desired image height
239: * @return The converted image
240: * @param type int
241: */
242: public static BufferedImage createHeadlessSmoothBufferedImage(
243: BufferedImage source, int type, int width, int height) {
244: if (type == ImageUtils.IMAGE_PNG && hasAlpha(source)) {
245: type = BufferedImage.TYPE_INT_ARGB;
246: } else {
247: type = BufferedImage.TYPE_INT_RGB;
248: }
249:
250: BufferedImage dest = new BufferedImage(width, height, type);
251:
252: int sourcex;
253: int sourcey;
254:
255: double scalex = (double) width / source.getWidth();
256: double scaley = (double) height / source.getHeight();
257:
258: int x1;
259: int y1;
260:
261: double xdiff;
262: double ydiff;
263:
264: int rgb;
265: int rgb1;
266: int rgb2;
267:
268: for (int y = 0; y < height; y++) {
269: sourcey = y * source.getHeight() / dest.getHeight();
270: ydiff = scale(y, scaley) - sourcey;
271:
272: for (int x = 0; x < width; x++) {
273: sourcex = x * source.getWidth() / dest.getWidth();
274: xdiff = scale(x, scalex) - sourcex;
275:
276: x1 = Math.min(source.getWidth() - 1, sourcex + 1);
277: y1 = Math.min(source.getHeight() - 1, sourcey + 1);
278:
279: rgb1 = getRGBInterpolation(source.getRGB(sourcex,
280: sourcey), source.getRGB(x1, sourcey), xdiff);
281: rgb2 = getRGBInterpolation(source.getRGB(sourcex, y1),
282: source.getRGB(x1, y1), xdiff);
283:
284: rgb = getRGBInterpolation(rgb1, rgb2, ydiff);
285:
286: dest.setRGB(x, y, rgb);
287: }
288: }
289:
290: return dest;
291: }
292:
293: private static double scale(int point, double scale) {
294: return point / scale;
295: }
296:
297: private static int getRGBInterpolation(int value1, int value2,
298: double distance) {
299: int alpha1 = (value1 & 0xFF000000) >>> 24;
300: int red1 = (value1 & 0x00FF0000) >> 16;
301: int green1 = (value1 & 0x0000FF00) >> 8;
302: int blue1 = (value1 & 0x000000FF);
303:
304: int alpha2 = (value2 & 0xFF000000) >>> 24;
305: int red2 = (value2 & 0x00FF0000) >> 16;
306: int green2 = (value2 & 0x0000FF00) >> 8;
307: int blue2 = (value2 & 0x000000FF);
308:
309: int rgb = ((int) (alpha1 * (1.0 - distance) + alpha2 * distance) << 24)
310: | ((int) (red1 * (1.0 - distance) + red2 * distance) << 16)
311: | ((int) (green1 * (1.0 - distance) + green2 * distance) << 8)
312: | (int) (blue1 * (1.0 - distance) + blue2 * distance);
313:
314: return rgb;
315: }
316:
317: /**
318: * Determines if the image has transparent pixels.
319: *
320: * @param image The image to check for transparent pixel.s
321: * @return <code>true</code> of <code>false</code>, according to the result
322: */
323: public static boolean hasAlpha(Image image) {
324: try {
325: PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
326: pg.grabPixels();
327:
328: return pg.getColorModel().hasAlpha();
329: } catch (InterruptedException e) {
330: return false;
331: }
332: }
333: }
|