alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 * $Header: /cvsroot/mvnforum/myvietnam/src/net/myvietnam/mvncore/util/ImageUtil.java,v 1.14 2005/01/18 12:16:45 minhnn Exp $
 * $Author: minhnn $
 * $Revision: 1.14 $
 * $Date: 2005/01/18 12:16:45 $
 *
 * ====================================================================
 *
 * Copyright (C) 2002-2005 by MyVietnam.net
 *
 * 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 any later version.
 *
 * All copyright notices regarding MyVietnam and MyVietnam CoreLib
 * MUST remain intact in the scripts and source code.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Correspondence and Marketing Questions can be sent to:
 * info@MyVietnam.net
 *
 * @author: Minh Nguyen  minhnn@MyVietnam.net
 * @author: Mai  Nguyen  mai.nh@MyVietnam.net
 */
package net.myvietnam.mvncore.util;

import java.io.*;
import java.awt.image.BufferedImage;
import java.awt.geom.AffineTransform;
import java.awt.*;
import java.awt.image.*;
import javax.swing.ImageIcon;
import com.sun.image.codec.jpeg.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.myvietnam.mvncore.thirdparty.JpegEncoder;
import net.myvietnam.mvncore.exception.*;
import net.myvietnam.mvncore.util.FileUtil;

public final class ImageUtil {

    private static Log log = LogFactory.getLog(ImageUtil.class);

    private ImageUtil() {// prevent instantiation
    }

    /**
     * @todo: xem lai ham nay, neu kich thuoc nho hon max thi ta luu truc tiep
     *          inputStream xuong thumbnailFile luon
     *
     * This method create a thumbnail and reserve the ratio of the output image
     * NOTE: This method closes the inputStream after it have done its work.
     *
     * @param inputStream     the stream of a jpeg file
     * @param thumbnailFile   the output file, must have the ".jpg" extension
     * @param maxWidth        the maximun width of the image
     * @param maxHeight       the maximun height of the image
     * @throws IOException
     * @throws BadInputException
     * @throws AssertionException
     */
    public static void createThumbnail(InputStream inputStream, String thumbnailFile, int maxWidth, int maxHeight)
        throws IOException, BadInputException, AssertionException {
        //boolean useSun = false;
        String lowerName = thumbnailFile.toLowerCase();
        if (!lowerName.endsWith(".jpg")) {
            throw new BadInputException("Cannot create a thumbnail with the extension other than '.jpg'.");
        }

        OutputStream outputStream = null;
        try {
            //JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(inputStream);
            //BufferedImage srcImage = decoder.decodeAsBufferedImage();
            byte[] srcByte = FileUtil.getBytes(inputStream);
            ImageIcon imageIcon = new ImageIcon(srcByte);
            Image srcImage = imageIcon.getImage();

            int imgWidth  = srcImage.getWidth(null);
            int imgHeight = srcImage.getHeight(null);
            //log.trace("width = " + imgWidth + " height = " + imgHeight);
            if ( (imgWidth <= 0) || (imgHeight <= 0) ) {
                // imgWidth or imgHeight could be -1, which is considered as an assertion
                throw new AssertionException("Assertion: ImageUtil: cannot get the image size.");
            }

            // Set the scale.
            AffineTransform tx = new AffineTransform();
            if ((imgWidth > maxWidth) || (imgHeight > maxHeight)) {
                double scaleX = (double)maxWidth/imgWidth;
                double scaleY = (double)maxHeight/imgHeight;
                double scaleRatio = (scaleX < scaleY) ? scaleX : scaleY;
                imgWidth  = (int)(imgWidth  * scaleRatio);
                imgHeight = (int)(imgHeight * scaleRatio);
                // scale as needed
                tx.scale(scaleRatio, scaleRatio);
            } else {// we dont need any transform here, just save it to file and return
                outputStream = new FileOutputStream(thumbnailFile);
                outputStream.write(srcByte);
                return;
            }

            // create thumbnail image
            BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);

            Graphics2D g = bufferedImage.createGraphics();
            boolean useTransform = false;
            if (useTransform) {// use transfrom to draw
                //log.trace("use transform");
                g.drawImage(srcImage, tx, null);
            } else {// use java filter
                //log.trace("use filter");
                Image scaleImage = getScaledInstance(srcImage, imgWidth, imgHeight);
                g.drawImage(scaleImage, 0, 0, null);
            }
            g.dispose();// free resource

            // write it to disk
            outputStream = new FileOutputStream(thumbnailFile);
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(outputStream);
            encoder.encode(bufferedImage);
        } catch (IOException e) {
            log.error("Error", e);
            throw e;
        } finally {// this finally is very important
            inputStream.close();
            if (outputStream != null) outputStream.close();
        }
    }

    /**
     * This method returns a fit-sized image for a source image,
     * this method retains the ratio of the source image
     */
    public static Image getFitSizeImage(Image srcImage, int fitWidth, int fitHeight)
        throws IOException {
        if ((fitWidth < 100) || (fitHeight < 100)) throw new IllegalArgumentException("Cannot accept values < 100");

        int srcWidth  = srcImage.getWidth(null);//xem lai cho nay vi neu dung BufferedImage thi khong can null
        int srcHeight = srcImage.getHeight(null);
        //log.trace("src w = " + srcWidth + " h = " + srcHeight);

        // dont need any transforms
        if ((srcWidth == fitWidth) && (srcHeight == fitHeight)) return srcImage;

        int newWidth  = srcWidth;
        int newHeight = srcHeight;

        double fitRatio = (double)fitWidth / fitHeight;
        double srcRatio = (double)srcWidth / srcHeight;
        if (srcRatio > fitRatio) {// must cut the width of the source image
            newWidth = (int)(srcHeight * fitRatio);
        } else {// must cut the height of the source image
            newHeight = (int)(srcWidth / fitRatio);
        }
        //log.trace("new w = " + newWidth + " h = " + newHeight);

        ImageFilter cropFilter = new CropImageFilter((srcWidth-newWidth)/2, (srcHeight-newHeight)/2, newWidth, newHeight);
        ImageProducer cropProd = new FilteredImageSource(srcImage.getSource(), cropFilter);
        Image cropImage = Toolkit.getDefaultToolkit().createImage(cropProd);

        Image retImage = new ImageIcon(getScaledInstance(cropImage, fitWidth, fitHeight)).getImage();

        return retImage;
    }

    /**
     * This method returns a fit-sized image for a source image,
     * this method retains the ratio of the source image
     */
    public static Image getFitSizeImage(InputStream inputStream, int fitWidth, int fitHeight)
        throws IOException {
        try {
            JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(inputStream);
            BufferedImage srcImage = decoder.decodeAsBufferedImage();

            return getFitSizeImage(srcImage, fitWidth, fitHeight);
        } catch (IOException e) {
            log.error("Cannot run getFitSizeImage", e);
            throw e;
        } finally {// this finally is very important
            inputStream.close();
        }
    }

    /**
     * This method write the image to a stream.
     * It auto detect the image is Image or BufferedImage.
     * This method close the output stream before it return.
     *
     * @param image Image
     * @param outputStream OutputStream
     * @throws IOException
     */
    public static void writeJpegImage_Sun(Image image, OutputStream outputStream) throws IOException {

        if (outputStream == null) {
            return;
        }

        try {
            BufferedImage bufferedImage = null;
            if (image instanceof BufferedImage) {
                bufferedImage = (BufferedImage)image;
            } else {
                // 30% cpu resource
                bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null),
                                                  BufferedImage.TYPE_INT_RGB);

                // 7.5 cpu
                Graphics2D g = bufferedImage.createGraphics();

                // 50% cpu
                g.drawImage(image, 0, 0, null);
                g.dispose(); // free resource
            }

            // write it to disk
            // 12% cpu
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(outputStream);
            encoder.encode(bufferedImage);
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException ex1) {
            }
        }
    }

    public static void writeJpegImage_Sun(Image image, String fileName) throws IOException {
        OutputStream outputStream = new FileOutputStream(fileName);
        writeJpegImage_Sun(image, outputStream);
    }

    /**
     * This method write image to output stream, then it close the stream before return
     *
     * @param image Image
     * @param outputStream OutputStream
     * @throws IOException
     */
    public static void writeJpegImage_nonSun(Image image, OutputStream outputStream) throws IOException {
        if (outputStream == null) {
            return;
        }
        try {
            JpegEncoder encoder = new JpegEncoder(image, 80, outputStream);
            encoder.Compress();
        } catch (Exception ex) {
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException ex1) {
            }
        }
    }

    public static void writeJpegImage_nonSun(Image image, String fileName) throws IOException {
        OutputStream outputStream = new FileOutputStream(fileName);

        //this method will close the stream before it returns so no need to close
        writeJpegImage_nonSun(image, outputStream);
    }

    public static Image getScaledInstance(Image srcImage, int width, int height) {
        boolean useSun = false;
        ImageFilter filter;
        if (useSun){
            //log.trace("use sun scalefilter");
            filter = new java.awt.image.AreaAveragingScaleFilter(width, height);
        } else {
            //log.trace("use nguoimau scalefilter");
            filter = new net.myvietnam.mvncore.util.AreaAveragingScaleFilter(width, height);
        }
        ImageProducer prod = new FilteredImageSource(srcImage.getSource(), filter);
        Image newImage = Toolkit.getDefaultToolkit().createImage(prod);
        ImageIcon imageIcon = new ImageIcon(newImage);
        return imageIcon.getImage();
    }
/*
    public static void main(String[] args) {
        try {
            FileInputStream is = new FileInputStream("c:\\PUTTY.RND");
            createThumbnail(is, "c:\\out.jpg", 120, 120);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        log.debug("done");
    }
*/
}





    /**
     * This class is taken from Pure Java AWT project
     * and modified to exclude PJAGraphicsManager class
     */
  // From java.awt.image.AreaAveragingScaleFilter
  // but without use of float type
  class AreaAveragingScaleFilter extends ImageFilter {
    private ColorModel rgbModel;
    private long []    alphas;
    private long []    reds;
    private long []    greens;
    private long []    blues;

    protected int      srcWidth;
    protected int      srcHeight;
    private   int []   srcPixels;
    protected int      destWidth;
    protected int      destHeight;
    protected int []   destPixels;

    {
      // Test if the class java.awt.image.ColorModel can be loaded
      //boolean classColorModelAccessible = PJAGraphicsManager.getDefaultGraphicsManager ().isClassAccessible ("java.awt.image.ColorModel");
      // modified by minhnn
      boolean classColorModelAccessible = isClassAccessible ("java.awt.image.ColorModel");
      if (classColorModelAccessible)
        rgbModel = ColorModel.getRGBdefault ();
    }

    /**
     * Constructs an AreaAveragingScaleFilter that scales the pixels from
     * its source Image as specified by the width and height parameters.
     * @param width  the target width to scale the image
     * @param height the target height to scale the image
     */
    public AreaAveragingScaleFilter(int width, int height) {
        destWidth = width;
        destHeight = height;
    }

    public void setDimensions (int w, int h)
    {
      srcWidth = w;
      srcHeight = h;
      if (destWidth < 0)
      {
        if (destHeight < 0)
        {
          destWidth = srcWidth;
          destHeight = srcHeight;
        }
        else
          destWidth = srcWidth * destHeight / srcHeight;
      }
      else if (destHeight < 0)
        destHeight = srcHeight * destWidth / srcWidth;

      consumer.setDimensions (destWidth, destHeight);
    }

    public void setHints (int hints)
    {
      // Images are sent entire frame by entire frame
      consumer.setHints (  (hints & (SINGLEPASS | SINGLEFRAME))
                         | TOPDOWNLEFTRIGHT);
    }

    public void imageComplete (int status)
    {
      if (   status == STATICIMAGEDONE
          || status == SINGLEFRAMEDONE)
        accumPixels (0, 0, srcWidth, srcHeight, rgbModel, srcPixels, 0, srcWidth);
      consumer.imageComplete (status);
    }

    public void setPixels (int x, int y, int width, int height,
                           ColorModel model, byte pixels [], int offset, int scansize)
    {
      // Store pixels in srcPixels array
      if (srcPixels == null)
        srcPixels = new int [srcWidth * srcHeight];
      for (int row = 0, destRow = y * srcWidth;
           row < height;
           row++, destRow += srcWidth)
      {
        int rowOff = offset + row * scansize;
        for (int col = 0; col < width; col++)
          // v1.2 : Added & 0xFF to disable sign bit
          srcPixels [destRow + x + col] = model.getRGB (pixels [rowOff + col] & 0xFF);
      }
    }

    public void setPixels (int x, int y, int width, int height,
                   ColorModel model, int pixels[], int offset, int scansize)
    {
      // Store pixels in srcPixels array
      if (srcPixels == null)
        srcPixels = new int [srcWidth * srcHeight];
      for (int row = 0, destRow = y * srcWidth;
           row < height;
           row++, destRow += srcWidth)
      {
        int rowOff = offset + row * scansize;
        for (int col = 0; col < width; col++)
          // If model == null, consider it's the default RGB model
          srcPixels [destRow + x + col] = model == null
                                                     ? pixels [rowOff + col]
                                                     : model.getRGB (pixels [rowOff + col]);
      }
    }

    private int [] calcRow ()
    {
      long mult = (srcWidth * srcHeight) << 32;
      if (destPixels == null)
        destPixels = new int [destWidth];

      for (int x = 0; x < destWidth; x++)
      {
        int a = (int)roundDiv (alphas [x], mult);
        int r = (int)roundDiv (reds   [x], mult);
        int g = (int)roundDiv (greens [x], mult);
        int b = (int)roundDiv (blues  [x], mult);
        a = Math.max (Math.min (a, 255), 0);
        r = Math.max (Math.min (r, 255), 0);
        g = Math.max (Math.min (g, 255), 0);
        b = Math.max (Math.min (b, 255), 0);
        destPixels [x] = (a << 24 | r << 16 | g << 8 | b);
      }

      return destPixels;
    }

    private void accumPixels (int x, int y, int w, int h,
                              ColorModel model, int [] pixels, int off,
                              int scansize)
    {
      reds   = new long [destWidth];
      greens = new long [destWidth];
      blues  = new long [destWidth];
      alphas = new long [destWidth];

      int sy = y;
      int syrem = destHeight;
      int dy = 0;
      int dyrem = 0;
      while (sy < y + h)
      {
        if (dyrem == 0)
        {
          for (int i = 0; i < destWidth; i++)
            alphas [i] =
            reds   [i] =
            greens [i] =
            blues  [i] = 0;

          dyrem = srcHeight;
        }

        int amty = Math.min (syrem, dyrem);
        int sx = 0;
        int dx = 0;
        int sxrem = 0;
        int dxrem = srcWidth;
        int a = 0,
            r = 0,
            g = 0,
            b = 0;
        while (sx < w)
        {
          if (sxrem == 0)
          {
            sxrem = destWidth;
            int rgb = pixels [off + sx];
            a = rgb >>> 24;
            r = (rgb >> 16) & 0xFF;
            g = (rgb >>  8) & 0xFF;
            b = rgb & 0xFF;
          }

          int  amtx = Math.min (sxrem, dxrem);
          long mult = (amtx * amty) << 32;
          alphas [dx] += mult * a;
          reds   [dx] += mult * r;
          greens [dx] += mult * g;
          blues  [dx] += mult * b;

          if ((sxrem -= amtx) == 0)
            sx++;

          if ((dxrem -= amtx) == 0)
          {
            dx++;
            dxrem = srcWidth;
          }
        }

        if ((dyrem -= amty) == 0)
        {
          int outpix [] = calcRow ();
          do
          {
            consumer.setPixels (0, dy, destWidth, 1,
                                rgbModel, outpix, 0, destWidth);
            dy++;
          }
          while ((syrem -= amty) >= amty && amty == srcHeight);
        }
        else
          syrem -= amty;

        if (syrem == 0)
        {
          syrem = destHeight;
          sy++;
          off += scansize;
        }
      }
    }
    ////////////////
    // util method
    ////////////////
    // util method
    /**
     * Returns the rounded result of <code>dividend / divisor, avoiding the use of floating
     * point operation (returns the same as <code>Math.round((float)dividend / divisor)).
     * @param dividend A <code>int number to divide.
     * @param divisor  A <code>int divisor.
     * @return dividend / divisor rounded to the closest <code>int integer.
     */
    public static int roundDiv(int dividend, int divisor) {
        final int remainder = dividend % divisor;
        if (Math.abs(remainder) * 2 <= Math.abs(divisor))
            return dividend / divisor;
        else
        if (dividend * divisor < 0)
            return dividend / divisor - 1;
        else
            return dividend / divisor + 1;
    }

    /**
     * Returns the rounded result of <code>dividend / divisor, avoiding the use of floating
     * point operation (returns the same as <code>Math.round((double)dividend / divisor)).
     * @param dividend A <code>long number to divide.
     * @param divisor  A <code>long divisor.
     * @return dividend / divisor rounded to the closest <code>long integer.
     */
    public static long roundDiv (long dividend, long divisor) {
        final long remainder = dividend % divisor;
        if (Math.abs (remainder) * 2 <= Math.abs (divisor))
          return dividend / divisor;
        else
          if (dividend * divisor < 0)
            return dividend / divisor - 1;
          else
            return dividend / divisor + 1;
    }

    /**
     * Returns <code>true if it successes to load the class className.
     * If security manager is too restictive, it is possible that the classes <code>java.awt.Color,
     * <code>java.awt.Rectangle, java.awt.Font, java.awt.FontMetrics
     * and <code>java.awt.image.ColorModel (and also java.awt.Dimension and other classes
     * not required by PJA classes) can't be loaded because they need either the class <code>java.awt.Toolkit
     * or the library awt to be accessible to call their <code>initIDs () native method.
     * @param  className  the fully qualified class name.
     * @return <code>true if java.awt.Toolkit class could be loaded.
     */
   public static boolean isClassAccessible(String className) {
       // Test if the class className can be loaded
       try {
           Class.forName(className);
           // Class can be loaded
           return true;
       } catch (ClassNotFoundException e) {} catch (LinkageError error) {} // Thrown by some AWT classes which require awt library in static initializer.

       return false;
   }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.