/* * Copyright (c) 2008 Los Alamos National Security, LLC. * With modifications by Brasiliana Digital Library (http://brasiliana.usp.br), 2010. * * Los Alamos National Laboratory * Research Library * Digital Library Research & Prototyping Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ package gov.lanl.adore.djatoka.util; import gov.lanl.adore.djatoka.io.FormatConstants; import ij.io.FileInfo; import ij.io.Opener; import ij.io.TiffDecoder; import java.awt.Graphics; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Hashtable; import org.apache.log4j.Logger; /** * Image Processing Utilities * @author Ryan Chute * */ public class ImageProcessingUtils { static Logger logger = Logger.getLogger(ImageProcessingUtils.class); /** * Perform a rotation of the provided BufferedImage using degrees of * 90, 180, or 270. * @param bi BufferedImage to be rotated * @param degree * @return rotated BufferedImage instance */ public static BufferedImage rotate(BufferedImage bi, int degree) { int width = bi.getWidth(); int height = bi.getHeight(); BufferedImage biFlip; if (degree == 90 || degree == 270) biFlip = new BufferedImage(height, width, bi.getType()); else if (degree == 180) biFlip = new BufferedImage(width, height, bi.getType()); else return bi; if (degree == 90) { for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) biFlip.setRGB(height- j - 1, i, bi.getRGB(i, j)); } if (degree == 180) { for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) biFlip.setRGB(width - i - 1, height - j - 1, bi.getRGB(i, j)); } if (degree == 270) { for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) biFlip.setRGB(j, width - i - 1, bi.getRGB(i, j)); } bi.flush(); bi = null; return biFlip; } /** * Return the number of resolution levels the djatoka API will generate * based on the provided pixel dimensions. * @param w max pixel width * @param h max pixel height * @return number of resolution levels */ public static int getLevelCount(int w, int h) { return getLevelCount(w, h, 96); } public static int getLevelCount(int w, int h, int tileSize) { int l = Math.max(w, h); int m = tileSize; int r = 0; int i; if (l > 0) { for (i = 1; l >= m; i++) { l = l / 2; r = i; } } return r; } /** * Return the resolution level the djatoka API will use to extract * an image for scaling. * @param w max pixel width * @param h max pixel height * @param out_w max pixel width * @param out_h max pixel height */ public static int getScalingLevel(int w, int h, int out_w, int out_h) { int levels = getLevelCount(w, h); int max_source = Math.max(w, h); int max_out = Math.max(out_w, out_h); int r = levels + 2; int i = max_source; while (i >= max_out) { i = i / 2; r--; } return r; } /** * Scale provided BufferedImage by the provided factor. * A scaling factor value should be greater than 0 and less than 2. * Note that scaling will impact performance and image quality. * @param bi BufferedImage to be scaled. * @param scale positive scaling factor * @return scaled instance of provided BufferedImage */ public static BufferedImage scale(BufferedImage bi, double scale) { AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(scale, scale), null); return op.filter(bi, null); } /** * Scale provided BufferedImage to the specified width and height dimensions. * If a provided dimension is 0, the aspect ratio is used to calculate a value. * Also, if either contains -1, the positive value will be used as for the long * side. * @param bi BufferedImage to be scaled. * @param w width the image is to be scaled to. * @param h height the image is to be scaled to. * @return scaled instance of provided BufferedImage */ public static BufferedImage scale(BufferedImage bi, int w, int h) { // If either w,h are -1, then calculate based on long side. if (w == -1 || h == -1) { int tl = Math.max(w, h); if (bi.getWidth() > bi.getHeight()) { w = tl; h = 0; } else { h = tl; w = 0; } } // Calculate dim. based on aspect ratio if (w == 0 || h == 0) { if (w == 0 && h == 0) return bi; if (w == 0) { double n = new Double(h) / new Double(bi.getHeight()); w = (int)Math.ceil(bi.getWidth() * n); } if (h == 0) { double n = new Double(w) / new Double(bi.getWidth()); h = (int)Math.ceil(bi.getHeight() * n); } } double scaleH = new Double(h) / new Double(bi.getHeight()); double scaleW = new Double(w) / new Double(bi.getWidth()); return scale(bi, Math.min(scaleH, scaleW)); } /** * Scale provided BufferedImage to the specified width and height dimensions. * If a provided dimension is 0, the aspect ratio is used to calculate a value. * Also, if either contains -1, the positive value will be used as for the long * side. * @param bi BufferedImage to be scaled. * @param x,y,w,h proportion * @return clipped instance of provided BufferedImage */ public static BufferedImage clipRegion(BufferedImage bi, double x, double y, double w, double h) { int ix = (int) Math.ceil(x * bi.getWidth()); int iy = (int) Math.ceil(y * bi.getHeight()); int iw = (int) Math.ceil(w * bi.getWidth()); int ih = (int) Math.ceil(h * bi.getHeight()); ix = ix < 0 ? 0 : ix; iy = iy < 0 ? 0 : iy; iw = Math.min(bi.getWidth() - ix, ix + iw); ih = Math.min(bi.getHeight() - iy, iy + ih); BufferedImage bi2 = bi.getSubimage( ix, iy, iw, ih); BufferedImage newbi = new BufferedImage(iw, ih, bi.getType()); Graphics g = newbi.getGraphics(); g.drawImage(bi2, 0, 0, null); return newbi; } private static final String magic = "000c6a502020da87a"; /** * Read first 12 bytes from File to determine if JP2 file. * @param f Path to JPEG 2000 image file * @return true is JP2 compatible format */ public final static boolean checkIfJp2(String f) { boolean isJP2 = false; try { BufferedInputStream bi = new BufferedInputStream(new FileInputStream(new File(f))); isJP2 = checkIfJp2(bi); bi.close(); } catch (FileNotFoundException e) { logger.error(e + " attempting to access: " + f); } catch (IOException e) { logger.error(e + " attempting to access: " + f); } return isJP2; } /** * Read first 12 bytes from InputStream to determine if JP2 file. * Note: Be sure to reset your stream after calling this method. * @param in InputStream of possible JP2 codestream * @return true is JP2 compatible format */ public final static boolean checkIfJp2(InputStream in) { byte[] buf = new byte[12]; try { in.read(buf, 0, 12); } catch (IOException e) { e.printStackTrace(); return false; } StringBuffer sb = new StringBuffer(buf.length * 2); for(int x = 0 ; x < buf.length ; x++) { sb.append((Integer.toHexString(0xff & buf[x]))); } String hexString = sb.toString(); return hexString.equals(magic); } /** * Given a mimetype, indicates if mimetype is JP2 compatible. * @param mimetype mimetype to check if JP2 compatible * @return true is JP2 compatible */ public final static boolean isJp2Type(String mimetype) { if (mimetype == null) return false; mimetype = mimetype.toLowerCase(); if (mimetype.equals(FormatConstants.FORMAT_MIMEYPE_JP2) || mimetype.equals(FormatConstants.FORMAT_MIMEYPE_JPX) || mimetype.equals(FormatConstants.FORMAT_MIMEYPE_JPM)) return true; else return false; } /** * Attempt to determine if file is a TIFF using the file header. * @param file * @return true if the file is a TIFF */ public final static boolean checkIfTiff(String file) { return new Opener().getFileType(file) == Opener.TIFF; } /** * Attempt to determine is file is an uncompressed TIFF * @param file File path for image to check * @return true if file is an uncompressed TIFF */ public static boolean isUncompressedTiff(String file) { File f = new File(file); FileInfo[] fi = null; TiffDecoder ti = new TiffDecoder(f.getParent() + "/", f.getName()); try { fi = ti.getTiffInfo(); } catch (IOException e) { return false; } if (fi[0].compression == 1) return true; else return false; } /** * Populates a BufferedImage from a RenderedImage * Source: http://www.jguru.com/faq/view.jsp?EID=114602 * @param img RenderedImage to be converted to BufferedImage * @return BufferedImage with complete raster data */ public static BufferedImage convertRenderedImage(RenderedImage img) { if (img instanceof BufferedImage) { return (BufferedImage)img; } ColorModel cm = img.getColorModel(); int width = img.getWidth(); int height = img.getHeight(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); Hashtable properties = new Hashtable(); String[] keys = img.getPropertyNames(); if (keys!=null) { for (int i = 0; i < keys.length; i++) { properties.put(keys[i], img.getProperty(keys[i])); } } BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties); img.copyData(raster); return result; } }