package gr.iti.mklab.visual.extraction; import gr.iti.mklab.visual.utilities.ImageIOGreyScale; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import javax.imageio.ImageIO; /** * This class performs image scaling * * @author Eleftherios Spyromitros-Xioufis * */ public class ImageScaling { /** * Depending on the method that will be called for scaling the image, the targetSize corresponds to:<br/> * * <pre> * a. The total number of pixels. * b. The dimension of a square thumbnail in pixels. * c. The larger dimension of a rectangular thumbnail in pixels. * </pre> * */ private int targetSize; /** * If true, this class will use a multi-step scaling technique that provides higher quality than the usual * one-step technique (only useful in downscaling cases and generally only when the {@code BILINEAR} hint * is specified) */ private boolean higherQuality; /** * 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} * ) */ private Object hint; /** * Constructor with no arguments, all fields take the default values */ public ImageScaling() { targetSize = 1024 * 768; higherQuality = true; hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR; } public void setTargetSize(int targetSize) { this.targetSize = targetSize; } public ImageScaling(int targetSize) { this.targetSize = targetSize; higherQuality = true; hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR; } public ImageScaling(int targetSize, boolean highQuality, Object hint) { this.targetSize = targetSize; this.higherQuality = highQuality; this.hint = hint; } public ImageScaling(String scalingTypeParameter, String scalingSizeParameter) { higherQuality = false; targetSize = 1024 * 768; if (scalingSizeParameter != null) { targetSize = Integer.parseInt(scalingSizeParameter); } hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR; if (scalingTypeParameter.equals("nn")) { hint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; } } /** * This method returns a scaled instance of the provided {@code BufferedImage}. The image is scaled to a * maximum of {@link #targetSize} pixels in total by keeping the aspect ratio. * * @param img * the original image to be scaled * @return a scaled version of the original {@code BufferedImage} or the original image if no scaling was * applied */ public BufferedImage maxPixelsScaling(BufferedImage img) { int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; // get dimensions of original image int originalWidth = img.getWidth(); int originalHeight = img.getHeight(); long originalSize = originalWidth * originalHeight; if (originalSize <= targetSize) { return img; } double scalingRatio = (double) targetSize / originalSize; // scaling ratio per dimension scalingRatio = Math.sqrt(scalingRatio); int targetWidth = (int) (originalWidth * scalingRatio); int targetHeight = (int) (originalHeight * scalingRatio); 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; } do { if (higherQuality && w > targetWidth) { w /= 2; if (w < targetWidth) { w = targetWidth; } } if (higherQuality && h > targetHeight) { h /= 2; if (h < targetHeight) { h = targetHeight; } } // long start = System.currentTimeMillis(); 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); // long end = System.currentTimeMillis(); g2.dispose(); // System.out.println(end - start); ret = tmp; } while (w != targetWidth || h != targetHeight); return ret; } /** * This method returns a scaled instance of the provided {@code BufferedImage}. The image is scaled so * that its minimum dimension becomes {@link #targetSize} pixels and then the larger dimension is cropped, * again to {@link #targetSize} pixels to create a square thumbnail. * * @param img * the original image to be scaled * @return a scaled version of the original {@code BufferedImage} or the original image if no scaling was * applied */ public BufferedImage squareScaling(BufferedImage img) { int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; // get dimensions of original image int originalWidth = img.getWidth(); int originalHeight = img.getHeight(); int targetWidth = 0; int targetHeight = 0; int maxDimension = Math.max(originalWidth, originalHeight); int minDimension = Math.min(originalWidth, originalHeight); // no processing is required if (maxDimension <= targetSize) { return img; } BufferedImage ret = (BufferedImage) img; // scaling is required if (minDimension > targetSize) { double scalingRatio = (double) targetSize / minDimension; if (minDimension == originalWidth) { targetWidth = targetSize; targetHeight = (int) Math.round(originalHeight * scalingRatio); } else { targetHeight = targetSize; targetWidth = (int) Math.round(originalWidth * scalingRatio); } 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; } do { if (higherQuality && w > targetWidth) { w /= 2; if (w < targetWidth) { w = targetWidth; } } if (higherQuality && h > targetHeight) { h /= 2; if (h < targetHeight) { h = targetHeight; } } // long start = System.currentTimeMillis(); 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); // long end = System.currentTimeMillis(); g2.dispose(); // System.out.println(end - start); ret = tmp; } while (w != targetWidth || h != targetHeight); } // now crop the image ret = ret .getSubimage(0, 0, Math.min(targetSize, originalWidth), Math.min(targetSize, originalHeight)); return ret; } /** * Same as {@link #squareScaling(BufferedImage)} but takes the imageFileName instead of a * {@code BufferedImage}. * * @param imageFilename * @return * @throws IOException */ public BufferedImage squareScaling(String imageFilename) throws IOException { // read the image BufferedImage image; try { // first try reading with the default class image = ImageIO.read(new File(imageFilename)); } catch (IllegalArgumentException e) { // if it fails retry with the corrected class // This exception is probably because of a grayscale jpeg image. System.out.println("Exception: " + e.getMessage() + " | Image: " + imageFilename); image = ImageIOGreyScale.read(new File(imageFilename)); } return squareScaling(image); } /** * This method returns a scaled instance of the provided {@code BufferedImage}. The image is scaled so * that its maximum dimension becomes {@link #targetSize} pixels. * * @param img * the original image to be scaled * @return a scaled version of the original {@code BufferedImage} or the original image if no scaling was * applied */ public BufferedImage rectScaling(BufferedImage img) { int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; // get dimensions of original image int originalWidth = img.getWidth(); int originalHeight = img.getHeight(); int maxDimension = Math.max(originalWidth, originalHeight); // no processing is required if (maxDimension <= targetSize) { return img; } // scaling is required BufferedImage ret = (BufferedImage) img; double scalingRatio = (double) targetSize / maxDimension; int targetWidth = (int) (originalWidth * scalingRatio); int targetHeight = (int) (originalHeight * scalingRatio); 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 = originalWidth; h = originalHeight; } else { // Use one-step technique: scale directly from original // size to target size with a single drawImage() call w = targetWidth; h = targetHeight; } do { if (higherQuality && w > targetWidth) { w /= 2; if (w < targetWidth) { w = targetWidth; } } if (higherQuality && h > targetHeight) { h /= 2; if (h < targetHeight) { h = targetHeight; } } // long start = System.currentTimeMillis(); 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); // long end = System.currentTimeMillis(); g2.dispose(); // System.out.println(end - start); ret = tmp; } while (w != targetWidth || h != targetHeight); return ret; } /** * Same as {@link #rectScaling(BufferedImage)} but takes the imageFileName instead of a * {@code BufferedImage}. * * @param imageFilename * @return * @throws IOException */ public BufferedImage rectScaling(String imageFilename) throws IOException { // read the image BufferedImage image; try { // first try reading with the default class image = ImageIO.read(new File(imageFilename)); } catch (IllegalArgumentException e) { // if it fails retry with the corrected class // This exception is probably because of a grayscale jpeg image. System.out.println("Exception: " + e.getMessage() + " | Image: " + imageFilename); image = ImageIOGreyScale.read(new File(imageFilename)); } return rectScaling(image); } public static void main(String args[]) throws IOException { int targetDimension = 250; ImageScaling scale = new ImageScaling(targetDimension); String image = "C:/Users/lef/Desktop/BB2F-nbCQAAD-YL.jpg"; BufferedImage squareThumb = scale.squareScaling(image); BufferedImage rectThumb = scale.rectScaling(image); ImageIO.write(squareThumb, "jpeg", new File(image.replace(".jpg", "_square.jpg"))); ImageIO.write(rectThumb, "jpeg", new File(image.replace(".jpg", "_rect.jpg"))); } public static void advancedUse() throws IOException { String targetFolder = "thumbs/"; String path = "C:/Users/lef/Desktop/thumb_testing_folder/"; int targetSize = 200; boolean highQuality = true; Object hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR; ImageScaling scale = new ImageScaling(targetSize, highQuality, hint); // --------------Load the image files------------- File dir = new File(path); // This example does not return any files that do not end with `.jpg'. FilenameFilter filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".jpg"); } }; String[] files = dir.list(filter); // --------------Create thumbnail for each image------------- for (int i = 0; i < files.length; i++) { long start = System.currentTimeMillis(); BufferedImage image; try { image = ImageIO.read(new File(path + files[i])); } catch (Exception e) { image = ImageIOGreyScale.read(new File(path + files[i])); } long end = System.currentTimeMillis(); System.out.println("Image reading took :" + (end - start) + "ms"); start = System.currentTimeMillis(); // image = scale.getScaledInstance(image); image = scale.squareScaling(image); end = System.currentTimeMillis(); System.out.println("Down scaling took : " + (end - start) + "ms"); File outputfile = new File(path + targetFolder + "t" + files[i]); ImageIO.write(image, "jpg", outputfile); } } }