001: /*
002: * $Header: /cvsroot/mvnforum/myvietnam/src/net/myvietnam/mvncore/util/ImageUtil.java,v 1.31 2007/09/22 03:28:01 minhnn Exp $
003: * $Author: minhnn $
004: * $Revision: 1.31 $
005: * $Date: 2007/09/22 03:28:01 $
006: *
007: * ====================================================================
008: *
009: * Copyright (C) 2002-2007 by MyVietnam.net
010: *
011: * All copyright notices regarding MyVietnam and MyVietnam CoreLib
012: * MUST remain intact in the scripts and source code.
013: *
014: * This library is free software; you can redistribute it and/or
015: * modify it under the terms of the GNU Lesser General Public
016: * License as published by the Free Software Foundation; either
017: * version 2.1 of the License, or (at your option) any later version.
018: *
019: * This library is distributed in the hope that it will be useful,
020: * but WITHOUT ANY WARRANTY; without even the implied warranty of
021: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
022: * Lesser General Public License for more details.
023: *
024: * You should have received a copy of the GNU Lesser General Public
025: * License along with this library; if not, write to the Free Software
026: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
027: *
028: * Correspondence and Marketing Questions can be sent to:
029: * info at MyVietnam net
030: *
031: * @author: Minh Nguyen
032: * @author: Mai Nguyen
033: */
034: package net.myvietnam.mvncore.util;
035:
036: import java.awt.*;
037: import java.awt.geom.AffineTransform;
038: import java.awt.image.*;
039: import java.io.*;
040:
041: import javax.imageio.ImageIO;
042: import javax.swing.ImageIcon;
043:
044: import net.myvietnam.mvncore.exception.BadInputException;
045: import net.myvietnam.mvncore.thirdparty.JpegEncoder;
046:
047: import org.apache.commons.logging.Log;
048: import org.apache.commons.logging.LogFactory;
049:
050: public final class ImageUtil {
051:
052: private static Log log = LogFactory.getLog(ImageUtil.class);
053:
054: private ImageUtil() {// prevent instantiation
055: }
056:
057: /**
058: * @todo: xem lai ham nay, neu kich thuoc nho hon max thi ta luu truc tiep
059: * inputStream xuong thumbnailFile luon
060: *
061: * This method create a thumbnail and reserve the ratio of the output image
062: * NOTE: This method closes the inputStream after it have done its work.
063: *
064: * @param inputStream the stream of a jpeg file
065: * @param thumbnailFile the output file, must have the ".jpg" extension
066: * @param maxWidth the maximum width of the image
067: * @param maxHeight the maximum height of the image
068: * @throws IOException
069: * @throws BadInputException
070: */
071: public static void createThumbnail(InputStream inputStream,
072: String thumbnailFile, int maxWidth, int maxHeight)
073: throws IOException {
074:
075: //boolean useSun = false;
076: if (thumbnailFile.toLowerCase().endsWith(".jpg") == false) {
077: throw new IllegalArgumentException(
078: "Cannot create a thumbnail with the extension other than '.jpg'.");
079: }
080:
081: OutputStream outputStream = new FileOutputStream(thumbnailFile);
082: createThumbnail(inputStream, outputStream, maxWidth, maxHeight);
083: }
084:
085: /**
086: * @todo: xem lai ham nay, neu kich thuoc nho hon max thi ta luu truc tiep
087: * inputStream xuong thumbnailFile luon
088: *
089: * This method create a thumbnail and reserve the ratio of the output image
090: * NOTE: This method closes the inputStream after it have done its work.
091: *
092: * @param inputStream the stream of a jpeg file
093: * @param outputStream the output stream
094: * @param maxWidth the maximum width of the image
095: * @param maxHeight the maximum height of the image
096: * @throws IOException
097: * @throws BadInputException
098: */
099: public static void createThumbnail(InputStream inputStream,
100: OutputStream outputStream, int maxWidth, int maxHeight)
101: throws IOException {
102:
103: if (inputStream == null) {
104: throw new IllegalArgumentException(
105: "inputStream must not be null.");
106: }
107: if (outputStream == null) {
108: throw new IllegalArgumentException(
109: "outputStream must not be null.");
110: }
111:
112: //boolean useSun = false;
113: if (maxWidth <= 0) {
114: throw new IllegalArgumentException("maxWidth must >= 0");
115: }
116: if (maxHeight <= 0) {
117: throw new IllegalArgumentException("maxHeight must >= 0");
118: }
119:
120: try {
121: //JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(inputStream);
122: //BufferedImage srcImage = decoder.decodeAsBufferedImage();
123: byte[] srcByte = FileUtil.getBytes(inputStream);
124: InputStream is = new ByteArrayInputStream(srcByte);
125: //ImageIcon imageIcon = new ImageIcon(srcByte);
126: //Image srcImage = imageIcon.getImage();
127:
128: BufferedImage srcImage = ImageIO.read(is);
129:
130: int imgWidth = srcImage.getWidth();
131: int imgHeight = srcImage.getHeight();
132: // imgWidth or imgHeight could be -1, which is considered as an assertion
133: AssertionUtil.doAssert((imgWidth > 0) && (imgHeight > 0),
134: "Assertion: ImageUtil: cannot get the image size.");
135: // Set the scale.
136: AffineTransform tx = new AffineTransform();
137: if ((imgWidth > maxWidth) || (imgHeight > maxHeight)) {
138: double scaleX = (double) maxWidth / imgWidth;
139: double scaleY = (double) maxHeight / imgHeight;
140: double scaleRatio = (scaleX < scaleY) ? scaleX : scaleY;
141: imgWidth = (int) (imgWidth * scaleRatio);
142: imgHeight = (int) (imgHeight * scaleRatio);
143: // scale as needed
144: tx.scale(scaleRatio, scaleRatio);
145: } else {// we dont need any transform here, just save it to file and return
146: outputStream.write(srcByte);
147: return;
148: }
149:
150: // create thumbnail image
151: BufferedImage bufferedImage = new BufferedImage(imgWidth,
152: imgHeight, BufferedImage.TYPE_INT_RGB);
153:
154: Graphics2D g = bufferedImage.createGraphics();
155: boolean useTransform = false;
156: if (useTransform) {// use transfrom to draw
157: //log.trace("use transform");
158: g.drawImage(srcImage, tx, null);
159: } else {// use java filter
160: //log.trace("use filter");
161: Image scaleImage = getScaledInstance(srcImage,
162: imgWidth, imgHeight);
163: g.drawImage(scaleImage, 0, 0, null);
164: }
165: g.dispose();// free resource
166:
167: // write it to outputStream
168: // JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(outputStream);
169: // encoder.encode(bufferedImage);
170:
171: ImageIO.write(bufferedImage, "jpeg", outputStream);
172: } catch (IOException e) {
173: log.error("Error", e);
174: throw e;
175: } finally {// this finally is very important
176: try {
177: inputStream.close();
178: inputStream = null;
179: } catch (IOException e) {
180: /* ignore */
181: e.printStackTrace();
182: }
183: try {
184: outputStream.close();
185: outputStream = null;
186: } catch (IOException e) {/* ignore */
187: e.printStackTrace();
188: }
189: }
190: }
191:
192: /**
193: * This method returns a fit-sized image for a source image,
194: * this method retains the ratio of the source image
195: */
196: public static Image getFitSizeImage(Image srcImage, int fitWidth,
197: int fitHeight) throws IOException {
198: if ((fitWidth < 100) || (fitHeight < 100))
199: throw new IllegalArgumentException(
200: "Cannot accept values < 100");
201:
202: int srcWidth = srcImage.getWidth(null);//xem lai cho nay vi neu dung BufferedImage thi khong can null
203: int srcHeight = srcImage.getHeight(null);
204: //log.trace("src w = " + srcWidth + " h = " + srcHeight);
205:
206: // dont need any transforms
207: if ((srcWidth == fitWidth) && (srcHeight == fitHeight))
208: return srcImage;
209:
210: int newWidth = srcWidth;
211: int newHeight = srcHeight;
212:
213: double fitRatio = (double) fitWidth / fitHeight;
214: double srcRatio = (double) srcWidth / srcHeight;
215: if (srcRatio > fitRatio) {// must cut the width of the source image
216: newWidth = (int) (srcHeight * fitRatio);
217: } else {// must cut the height of the source image
218: newHeight = (int) (srcWidth / fitRatio);
219: }
220: //log.trace("new w = " + newWidth + " h = " + newHeight);
221:
222: ImageFilter cropFilter = new CropImageFilter(
223: (srcWidth - newWidth) / 2, (srcHeight - newHeight) / 2,
224: newWidth, newHeight);
225: ImageProducer cropProd = new FilteredImageSource(srcImage
226: .getSource(), cropFilter);
227: Image cropImage = Toolkit.getDefaultToolkit().createImage(
228: cropProd);
229:
230: Image retImage = new ImageIcon(getScaledInstance(cropImage,
231: fitWidth, fitHeight)).getImage();
232:
233: return retImage;
234: }
235:
236: /**
237: * This method returns a fit-sized image for a source image,
238: * this method retains the ratio of the source image
239: */
240: public static Image getFitSizeImage(InputStream inputStream,
241: int fitWidth, int fitHeight) throws IOException {
242: try {
243: //JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(inputStream);
244: //BufferedImage srcImage = decoder.decodeAsBufferedImage();
245: BufferedImage srcImage = ImageIO.read(inputStream);
246:
247: return getFitSizeImage(srcImage, fitWidth, fitHeight);
248: } catch (IOException e) {
249: log.error("Cannot run getFitSizeImage", e);
250: throw e;
251: } finally {// this finally is very important
252: inputStream.close();
253: }
254: }
255:
256: /**
257: * This method write the image to a stream.
258: * It auto detect the image is Image or BufferedImage.
259: * This method close the output stream before it return.
260: *
261: * @param image Image
262: * @param outputStream OutputStream
263: * @throws IOException
264: */
265: public static void writeJpegImage_Sun(Image image,
266: OutputStream outputStream) throws IOException {
267:
268: if (outputStream == null) {
269: return;
270: }
271:
272: try {
273: BufferedImage bufferedImage = null;
274: if (image instanceof BufferedImage) {
275: bufferedImage = (BufferedImage) image;
276: } else {
277: // 30% cpu resource
278: bufferedImage = new BufferedImage(image.getWidth(null),
279: image.getHeight(null),
280: BufferedImage.TYPE_INT_RGB);
281:
282: // 7.5 cpu
283: Graphics2D g = bufferedImage.createGraphics();
284:
285: // 50% cpu
286: g.drawImage(image, 0, 0, null);
287: g.dispose(); // free resource
288: }
289:
290: // write it to disk
291: // 12% cpu
292: //JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(outputStream);
293: //encoder.encode(bufferedImage);
294: ImageIO.write(bufferedImage, "jpeg", outputStream);
295: } finally {
296: if (outputStream != null) {
297: try {
298: outputStream.close();
299: } catch (IOException ex) {
300: ex.printStackTrace();
301: }
302: }
303: }
304: }
305:
306: public static void writeJpegImage_Sun(Image image, String fileName)
307: throws IOException {
308: OutputStream outputStream = new FileOutputStream(fileName);
309: writeJpegImage_Sun(image, outputStream);
310: }
311:
312: /**
313: * This method write image to output stream, then it close the stream before return
314: *
315: * @param image Image
316: * @param outputStream OutputStream
317: * @throws IOException
318: */
319: public static void writeJpegImage_nonSun(Image image,
320: OutputStream outputStream) throws IOException {
321: if (outputStream == null) {
322: return;
323: }
324: try {
325: JpegEncoder encoder = new JpegEncoder(image, 80,
326: outputStream);
327: encoder.Compress();
328: } catch (Exception ex) {
329: ex.printStackTrace();
330: } finally {
331: try {
332: if (outputStream != null) {
333: outputStream.close();
334: }
335: } catch (IOException ex1) {
336: ex1.printStackTrace();
337: }
338: }
339: }
340:
341: public static void writeJpegImage_nonSun(Image image,
342: String fileName) throws IOException {
343: OutputStream outputStream = new FileOutputStream(fileName);
344:
345: //this method will close the stream before it returns so no need to close
346: writeJpegImage_nonSun(image, outputStream);
347: }
348:
349: public static Image getScaledInstance(Image srcImage, int width,
350: int height) {
351: boolean useSun = false;
352: ImageFilter filter;
353: if (useSun) {
354: //log.trace("use sun scalefilter");
355: filter = new java.awt.image.AreaAveragingScaleFilter(width,
356: height);
357: } else {
358: //log.trace("use nguoimau scalefilter");
359: filter = new net.myvietnam.mvncore.util.AreaAveragingScaleFilter(
360: width, height);
361: }
362: ImageProducer prod = new FilteredImageSource(srcImage
363: .getSource(), filter);
364: Image newImage = Toolkit.getDefaultToolkit().createImage(prod);
365: ImageIcon imageIcon = new ImageIcon(newImage);
366: return imageIcon.getImage();
367: }
368:
369: public static BufferedImage getProductionImage(
370: String productVersion, String productRealeaseDate) {
371:
372: String str = productVersion + " on " + productRealeaseDate;
373: int IMAGE_WIDTH = 250;
374: int IMAGE_HEIGHT = 30;
375:
376: BufferedImage bufferedImage = new BufferedImage(IMAGE_WIDTH,
377: IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
378: Graphics2D g = bufferedImage.createGraphics();
379: g.setBackground(Color.blue);
380: g.setColor(Color.white);
381: g.draw3DRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, false);
382: FontMetrics fontMetrics = g.getFontMetrics();
383: int strWidth = fontMetrics.stringWidth(str);
384: int strHeight = fontMetrics.getAscent()
385: + fontMetrics.getDescent();
386: g.drawString(str, (IMAGE_WIDTH - strWidth) / 2, IMAGE_HEIGHT
387: - ((IMAGE_HEIGHT - strHeight) / 2)
388: - fontMetrics.getDescent());
389: g.dispose(); // free resource
390: return bufferedImage;
391: }
392: /*
393: public static void main(String[] args) {
394: try {
395: FileInputStream is = new FileInputStream("c:\\PUTTY.RND");
396: createThumbnail(is, "c:\\out.jpg", 120, 120);
397: } catch (Exception ex) {
398: ex.printStackTrace();
399: }
400: log.debug("done");
401: }
402: */
403: }
404:
405: /**
406: * This class is taken from Pure Java AWT project
407: * and modified to exclude PJAGraphicsManager class
408: */
409: // From java.awt.image.AreaAveragingScaleFilter
410: // but without use of float type
411: class AreaAveragingScaleFilter extends ImageFilter {
412: private ColorModel rgbModel;
413: private long[] alphas;
414: private long[] reds;
415: private long[] greens;
416: private long[] blues;
417:
418: protected int srcWidth;
419: protected int srcHeight;
420: private int[] srcPixels;
421: protected int destWidth;
422: protected int destHeight;
423: protected int[] destPixels;
424:
425: {
426: // Test if the class java.awt.image.ColorModel can be loaded
427: //boolean classColorModelAccessible = PJAGraphicsManager.getDefaultGraphicsManager ().isClassAccessible ("java.awt.image.ColorModel");
428: // modified by minhnn
429: boolean classColorModelAccessible = isClassAccessible("java.awt.image.ColorModel");
430: if (classColorModelAccessible)
431: rgbModel = ColorModel.getRGBdefault();
432: }
433:
434: /**
435: * Constructs an AreaAveragingScaleFilter that scales the pixels from
436: * its source Image as specified by the width and height parameters.
437: * @param width the target width to scale the image
438: * @param height the target height to scale the image
439: */
440: public AreaAveragingScaleFilter(int width, int height) {
441: destWidth = width;
442: destHeight = height;
443: }
444:
445: public void setDimensions(int w, int h) {
446: srcWidth = w;
447: srcHeight = h;
448: if (destWidth < 0) {
449: if (destHeight < 0) {
450: destWidth = srcWidth;
451: destHeight = srcHeight;
452: } else
453: destWidth = srcWidth * destHeight / srcHeight;
454: } else if (destHeight < 0)
455: destHeight = srcHeight * destWidth / srcWidth;
456:
457: consumer.setDimensions(destWidth, destHeight);
458: }
459:
460: public void setHints(int hints) {
461: // Images are sent entire frame by entire frame
462: consumer.setHints((hints & (SINGLEPASS | SINGLEFRAME))
463: | TOPDOWNLEFTRIGHT);
464: }
465:
466: public void imageComplete(int status) {
467: if (status == STATICIMAGEDONE || status == SINGLEFRAMEDONE)
468: accumPixels(0, 0, srcWidth, srcHeight, rgbModel, srcPixels,
469: 0, srcWidth);
470: consumer.imageComplete(status);
471: }
472:
473: public void setPixels(int x, int y, int width, int height,
474: ColorModel model, byte pixels[], int offset, int scansize) {
475: // Store pixels in srcPixels array
476: if (srcPixels == null)
477: srcPixels = new int[srcWidth * srcHeight];
478: for (int row = 0, destRow = y * srcWidth; row < height; row++, destRow += srcWidth) {
479: int rowOff = offset + row * scansize;
480: for (int col = 0; col < width; col++)
481: // v1.2 : Added & 0xFF to disable sign bit
482: srcPixels[destRow + x + col] = model
483: .getRGB(pixels[rowOff + col] & 0xFF);
484: }
485: }
486:
487: public void setPixels(int x, int y, int width, int height,
488: ColorModel model, int pixels[], int offset, int scansize) {
489: // Store pixels in srcPixels array
490: if (srcPixels == null)
491: srcPixels = new int[srcWidth * srcHeight];
492: for (int row = 0, destRow = y * srcWidth; row < height; row++, destRow += srcWidth) {
493: int rowOff = offset + row * scansize;
494: for (int col = 0; col < width; col++)
495: // If model == null, consider it's the default RGB model
496: srcPixels[destRow + x + col] = model == null ? pixels[rowOff
497: + col]
498: : model.getRGB(pixels[rowOff + col]);
499: }
500: }
501:
502: private int[] calcRow() {
503: long mult = (srcWidth * srcHeight) << 32;
504: if (destPixels == null)
505: destPixels = new int[destWidth];
506:
507: for (int x = 0; x < destWidth; x++) {
508: int a = (int) roundDiv(alphas[x], mult);
509: int r = (int) roundDiv(reds[x], mult);
510: int g = (int) roundDiv(greens[x], mult);
511: int b = (int) roundDiv(blues[x], mult);
512: a = Math.max(Math.min(a, 255), 0);
513: r = Math.max(Math.min(r, 255), 0);
514: g = Math.max(Math.min(g, 255), 0);
515: b = Math.max(Math.min(b, 255), 0);
516: destPixels[x] = (a << 24 | r << 16 | g << 8 | b);
517: }
518:
519: return destPixels;
520: }
521:
522: private void accumPixels(int x, int y, int w, int h,
523: ColorModel model, int[] pixels, int off, int scansize) {
524: reds = new long[destWidth];
525: greens = new long[destWidth];
526: blues = new long[destWidth];
527: alphas = new long[destWidth];
528:
529: int sy = y;
530: int syrem = destHeight;
531: int dy = 0;
532: int dyrem = 0;
533: while (sy < y + h) {
534: if (dyrem == 0) {
535: for (int i = 0; i < destWidth; i++)
536: alphas[i] = reds[i] = greens[i] = blues[i] = 0;
537:
538: dyrem = srcHeight;
539: }
540:
541: int amty = Math.min(syrem, dyrem);
542: int sx = 0;
543: int dx = 0;
544: int sxrem = 0;
545: int dxrem = srcWidth;
546: int a = 0, r = 0, g = 0, b = 0;
547: while (sx < w) {
548: if (sxrem == 0) {
549: sxrem = destWidth;
550: int rgb = pixels[off + sx];
551: a = rgb >>> 24;
552: r = (rgb >> 16) & 0xFF;
553: g = (rgb >> 8) & 0xFF;
554: b = rgb & 0xFF;
555: }
556:
557: int amtx = Math.min(sxrem, dxrem);
558: long mult = (amtx * amty) << 32;
559: alphas[dx] += mult * a;
560: reds[dx] += mult * r;
561: greens[dx] += mult * g;
562: blues[dx] += mult * b;
563:
564: if ((sxrem -= amtx) == 0)
565: sx++;
566:
567: if ((dxrem -= amtx) == 0) {
568: dx++;
569: dxrem = srcWidth;
570: }
571: }
572:
573: if ((dyrem -= amty) == 0) {
574: int outpix[] = calcRow();
575: do {
576: consumer.setPixels(0, dy, destWidth, 1, rgbModel,
577: outpix, 0, destWidth);
578: dy++;
579: } while ((syrem -= amty) >= amty && amty == srcHeight);
580: } else
581: syrem -= amty;
582:
583: if (syrem == 0) {
584: syrem = destHeight;
585: sy++;
586: off += scansize;
587: }
588: }
589: }
590:
591: ////////////////
592: // util method
593: ////////////////
594: // util method
595: /**
596: * Returns the rounded result of <code>dividend / divisor</code>, avoiding the use of floating
597: * point operation (returns the same as <code>Math.round((float)dividend / divisor)</code>).
598: * @param dividend A <code>int</code> number to divide.
599: * @param divisor A <code>int</code> divisor.
600: * @return dividend / divisor rounded to the closest <code>int</code> integer.
601: */
602: public static int roundDiv(int dividend, int divisor) {
603: final int remainder = dividend % divisor;
604: if (Math.abs(remainder) * 2 <= Math.abs(divisor))
605: return dividend / divisor;
606: else if (dividend * divisor < 0)
607: return dividend / divisor - 1;
608: else
609: return dividend / divisor + 1;
610: }
611:
612: /**
613: * Returns the rounded result of <code>dividend / divisor</code>, avoiding the use of floating
614: * point operation (returns the same as <code>Math.round((double)dividend / divisor)</code>).
615: * @param dividend A <code>long</code> number to divide.
616: * @param divisor A <code>long</code> divisor.
617: * @return dividend / divisor rounded to the closest <code>long</code> integer.
618: */
619: public static long roundDiv(long dividend, long divisor) {
620: final long remainder = dividend % divisor;
621: if (Math.abs(remainder) * 2 <= Math.abs(divisor))
622: return dividend / divisor;
623: else if (dividend * divisor < 0)
624: return dividend / divisor - 1;
625: else
626: return dividend / divisor + 1;
627: }
628:
629: /**
630: * Returns <code>true</code> if it successes to load the class <code>className</code>.
631: * If security manager is too restictive, it is possible that the classes <code>java.awt.Color</code>,
632: * <code>java.awt.Rectangle</code>, <code>java.awt.Font</code>, <code>java.awt.FontMetrics</code>
633: * and <code>java.awt.image.ColorModel</code> (and also <code>java.awt.Dimension</code> and other classes
634: * not required by PJA classes) can't be loaded because they need either the class <code>java.awt.Toolkit</code>
635: * or the library awt to be accessible to call their <code>initIDs ()</code> native method.
636: * @param className the fully qualified class name.
637: * @return <code>true</code> if <code>java.awt.Toolkit</code> class could be loaded.
638: */
639: public static boolean isClassAccessible(String className) {
640: // Test if the class className can be loaded
641: try {
642: Class.forName(className);
643: // Class can be loaded
644: return true;
645: } catch (ClassNotFoundException e) {
646: } catch (LinkageError error) {
647: } // Thrown by some AWT classes which require awt library in static initializer.
648:
649: return false;
650: }
651:
652: }
|