Wednesday, 14 July 2010

Scaling large amounts of images in a directory structure

The following links helped me a lot!
import java.util.*;
import java.io.*;

import java.awt.image.*;
import java.awt.*;
import javax.imageio.ImageIO;
import java.io.FilenameFilter;

public class Autoboxing
{
    public static String DESTINATION_PATH = "C:\\destination";

    public static String SOURCE_PATH = "C:\\source";

    public static boolean verbose = false;

    public static int MAX_RETRIES = 20;
   
    /**
     * Saves a BufferedImage to the given file, pathname must not have any
     * periods "." in it except for the one before the format, i.e. C:/images/fooimage.png
     * @param img
     * @param ref a pathname
     */

    public static void saveImage(BufferedImage img, String ref)
    {
        try {
            String format = (ref.endsWith(".png")) ? "png" : "jpg";
            ImageIO.write(img, format, new File(ref));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static BufferedImage loadImage(File ref)
    {
        BufferedImage bimg = null;
        try {

            bimg = ImageIO.read(ref);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bimg;
    }

    /**
     * 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;
        }
    int count = 0;
        do {
        count ++;
            if (count > MAX_RETRIES)
            {
                throw new RuntimeException("Max retries reached.");
            }
            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;
    }

    /**
     * Recursively resizes images.
     * @param directory the source directory containing .jpg files
     * @param destination the destination directory where the resized .jpg files are to be put
     * @param filter the filter to use, for example *.jpg
     * @param recurse wether or not to traverse the directory tree
     * @param recreate wether or not to recreate the entire directory tree
     * in the destination or simply dump .jpg files.
     * @param overwrite true, it overwrites existing files, false, if destination file already exists, skips it.
     */

    public static void changeFiles(
            File directory,
            File destination,
            FilenameFilter filter,
            boolean recurse,
            boolean recreate,
            boolean overwrite)
    throws IOException
    {
        if (verbose) System.out.println("Checking directory : " + directory.getCanonicalPath() + " with " + destination.getCanonicalPath() );
        // List of files / directories
        Vector<File> files = new Vector<File>();
       
        // Get files / directories in the directory
        File[] entries = directory.listFiles();
       
        // Go over entries
        for (File entry : entries)
        {
            if (verbose) System.out.println(" File: " + entry.getName());
            // If there is no filter or the filter accepts the 
            // file / directory, resize the image found
            if (filter == null || filter.accept(directory, entry.getName()))
            {
                File newFile = new File(destination.getCanonicalPath() + File.separator + entry.getName());
                if (newFile.exists() && !overwrite)
                {
                    // skipping this one, already done apparently.
                    if (verbose) System.out.println(" File " + newFile .getCanonicalPath() + " exists, skipped.");
                    continue;
                }

                try
                {
                    BufferedImage theImage = loadImage(entry);
                    theImage = getScaledInstance(theImage, 50, 50,  RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
                    if (!recreate)
                    {
                        saveImage(theImage, destination.getCanonicalPath() + "\\" + entry.getName());
                    }
                    else
                    {
                        if (!newFile.exists())
                        {
                            // create the equivalent
                            newFile.createNewFile();
                        }
                        saveImage(theImage, newFile.getCanonicalPath());
                    }
                }
                catch (Exception e)
                {
                    System.out.println("Error resizing image " + entry.getCanonicalPath());
                    e.printStackTrace();
                }
            }
           
            // If the file is a directory and the recurse flag
            // is set, recurse into the directory
            if (recurse && entry.isDirectory())
            {
                if (recreate)
                {
                    File newFile = new File(destination.getCanonicalPath() + File.separator + entry.getName());
                    if (!newFile.exists())
                    {
                        // create the equivalent
                        newFile.mkdir();
                    }
                    changeFiles(entry, new File(destination.getCanonicalPath() + File.separator + entry.getName()), filter, recurse, recreate, overwrite);
                }
                else
                {
                    changeFiles(entry, new File(destination.getCanonicalPath()), filter, recurse, recreate, overwrite);
                }
            }
        }

    }

    public static class MyFilenameFilter implements FilenameFilter
    {
        public boolean accept(File dir,
                        String name)
        {
            return name.endsWith(".jpg");
        }
    }
   
   
    public static void main(String args[])
    throws IOException
    {
        boolean overwrite = false;
        boolean recursive = false;
        boolean recreate = false;
        boolean nextIsDestination = false;
        boolean nextIsSource = false;
        String destination = DESTINATION_PATH;
        String source = SOURCE_PATH;
        for (String argument : args)
        {
            if (nextIsDestination)
            {
                destination = argument;
                nextIsDestination = false;
            }
            if (nextIsSource)
            {
                source = argument;
                nextIsSource = false;
            }
            if ("-r".equals(argument))
            {
                recursive = true;
            }
            if ("-tree".equals(argument))
            {
                recreate = true;
            }
            if ("-s".equals(argument))
            {
                nextIsSource = true;
            }
            if ("-d".equals(argument))
            {
                nextIsDestination = true;
            }
            if ("-o".equals(argument))
            {
                overwrite = true;
            }
            if ("-v".equals(argument))
            {
                verbose = true;
            }
        }
       
        File src_file = new File(source);
        File dest_file = new File(destination);
        System.out.println("absolute path: " +  src_file.getAbsolutePath());
        System.out.println("canonical path: " +  src_file.getCanonicalPath());
        System.out.println("path: " +  src_file.getPath());
        System.out.println("name: " +  src_file.getName());
        changeFiles(src_file, dest_file, new MyFilenameFilter(), recursive, recreate, overwrite);
    }
}

No comments:

Post a Comment