/*
* Copyright (c) 2008 Los Alamos National Security, LLC.
*
* 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.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Image Processing Utilities
*
* @author Ryan Chute
*/
public class ImageProcessingUtils {
private static final Logger LOGGER = LoggerFactory.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) {
int l = Math.max(w, h);
int m = 96;
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));
}
private static final String magic = "000c6a502020da87a";
/**
* Read first 12 bytes from File to determine if JP2 file.
*
* @param aFilename Path to JPEG 2000 image file
* @return true is JP2 compatible format
*/
public final static boolean checkIfJp2(String aFilename) {
boolean isJP2 = false;
try {
BufferedInputStream inStream = new BufferedInputStream(new FileInputStream(new File(aFilename)));
isJP2 = checkIfJp2(inStream);
inStream.close();
} catch (FileNotFoundException e) {
LOGGER.error("File not found: {}", aFilename);
} catch (IOException e) {
LOGGER.error("Could not read: {}", aFilename);
}
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;
}
}