package javaxt.io;
import java.io.*;
import javax.imageio.*;
import javax.imageio.metadata.*;
import java.awt.image.*;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.*;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
//Imports for JPEG
//import com.sun.image.codec.jpeg.*; //<-- Not always available in newer versions
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
//Imports for JP2
//import javax.media.jai.RenderedOp;
//import com.sun.media.imageio.plugins.jpeg2000.J2KImageReadParam;
//******************************************************************************
//** Image Class
//******************************************************************************
/**
* Used to open, resize, rotate, crop and save images.
*
******************************************************************************/
public class Image {
private BufferedImage bufferedImage = null;
private java.util.ArrayList corners = null;
private float outputQuality = 1f; //0.9f; //0.5f;
private static final boolean useSunCodec = getSunCodec();
private static Class JPEGCodec;
private static Class JPEGEncodeParam;
private Graphics2D g2d = null;
public static String[] InputFormats = getFormats(ImageIO.getReaderFormatNames());
public static String[] OutputFormats = getFormats(ImageIO.getWriterFormatNames());
private IIOMetadata metadata;
private HashMap exif;
private HashMap iptc;
private HashMap gps;
private boolean saveMetadata = false;
//**************************************************************************
//** Constructor
//**************************************************************************
/** Creates a new instance of this class using an existing image */
public Image(String PathToImageFile){
this(new java.io.File(PathToImageFile));
}
public Image(java.io.File file){
if (!file.exists()) throw new IllegalArgumentException("Input file not found.");
try{ createBufferedImage(new FileInputStream(file)); }
catch(Exception e){}
}
public Image(java.io.InputStream InputStream){
createBufferedImage(InputStream);
}
public Image(byte[] byteArray){
this(new ByteArrayInputStream(byteArray));
}
public Image(int width, int height){
this.bufferedImage =
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
this.g2d = getGraphics();
}
public Image(BufferedImage bufferedImage){
this.bufferedImage = bufferedImage;
}
public Image(RenderedImage img) {
if (img instanceof BufferedImage) {
this.bufferedImage = (BufferedImage) img;
}
else{
java.awt.image.ColorModel cm = img.getColorModel();
java.awt.image.WritableRaster raster =
cm.createCompatibleWritableRaster(img.getWidth(), img.getHeight());
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
java.util.Hashtable properties = new java.util.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);
this.bufferedImage = result;
}
}
//**************************************************************************
//** Constructor
//**************************************************************************
/** Creates a new instance of this class using a block of text.
* @param fontName Name of the font you with to use. Note that you can get
* a list of available fonts like this:
for (String fontName : GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()){
System.out.println(fontName);
}
*/
public Image(String text, String fontName, int fontSize, int r, int g, int b){
this(text, new Font(fontName, Font.TRUETYPE_FONT, fontSize), r,g,b);
}
public Image(String text, Font font, int r, int g, int b){
//Get Font Metrics
Graphics2D t = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).createGraphics();
t.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
FontMetrics fm = t.getFontMetrics(font);
int width = fm.stringWidth(text);
int height = fm.getHeight();
int descent = fm.getDescent();
t.dispose();
//Create Image
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
g2d = bufferedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//Add Text
float alpha = 1.0f; //Set alpha. 0.0f is 100% transparent and 1.0f is 100% opaque.
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g2d.setColor(new Color(r, g, b));
g2d.setFont(font);
g2d.drawString(text, 0, height-descent);
}
//**************************************************************************
//** setBackgroundColor
//**************************************************************************
/** Used to set the background color. Creates an image layer and inserts it
* under the existing graphic. This method should only be called once.
*/
public void setBackgroundColor(int r, int g, int b){
/*
Color org = g2d.getColor();
g2d.setColor(new Color(r,g,b));
g2d.fillRect(1,1,width-2,height-2); //g2d.fillRect(0,0,width,height);
g2d.setColor(org);
*/
int imageType = bufferedImage.getType();
if (imageType == 0) {
imageType = BufferedImage.TYPE_INT_ARGB;
}
int width = this.getWidth();
int height = this.getHeight();
BufferedImage bi = new BufferedImage(width, height, imageType);
Graphics2D g2d = bi.createGraphics();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
g2d.setColor(new Color(r,g,b));
g2d.fillRect(0,0,width,height);
java.awt.Image img = bufferedImage;
g2d.drawImage(img, 0, 0, null);
this.bufferedImage = bi;
g2d.dispose();
}
//**************************************************************************
//** getInputFormats
//**************************************************************************
/** Used to retrieve a list of supported input (read) formats. */
public String[] getInputFormats(){
return getFormats(ImageIO.getReaderFormatNames());
}
//**************************************************************************
//** getOutputFormats
//**************************************************************************
/** Used to retrieve a list of supported output (write) formats. */
public String[] getOutputFormats(){
return getFormats(ImageIO.getWriterFormatNames());
}
//**************************************************************************
//** getFormats
//**************************************************************************
/** Used to trim the list of formats. */
private static String[] getFormats(String[] inputFormats){
//Build a unique list of file formats
HashSet formats = new HashSet ();
for (int i=0; i> 24) & 0xff;
int red = (pixel >> 16) & 0xff;
int green = (pixel >> 8) & 0xff;
int blue = (pixel) & 0xff;
return new java.awt.Color(red, green, blue, alpha);
}
//**************************************************************************
//** getHistogram
//**************************************************************************
/** Returns an array with 4 histograms: red, green, blue, and average
ArrayList histogram = image.getHistogram();
int[] red = histogram.get(0);
int[] green = histogram.get(1);
int[] blue = histogram.get(2);
int[] average = histogram.get(3);
*/
public java.util.ArrayList getHistogram(){
//Create empty histograms
int[] red = new int[256];
int[] green = new int[256];
int[] blue = new int[256];
int[] average = new int[256];
for (int i=0; i hist = new java.util.ArrayList();
hist.add(red);
hist.add(green);
hist.add(blue);
hist.add(average);
return hist;
}
//**************************************************************************
//** addImage
//**************************************************************************
/** Used to add an image "overlay" to the existing image at a given
* position. This method can also be used to create image mosiacs.
*/
public void addImage(BufferedImage in, int x, int y, boolean expand){
int x2 = 0;
int y2 = 0;
int w = bufferedImage.getWidth();
int h = bufferedImage.getHeight();
if (expand){
//Update Width and Horizontal Position of the Original Image
if (x<0) {
w = w + -x;
if (in.getWidth()>w){
w = w + (in.getWidth()-w);
}
x2 = -x;
x = 0;
}
else if (x>w) {
w = (w + (x-w)) + in.getWidth();
}
else{
if ((x+in.getWidth())>w){
w = w + ((x+in.getWidth())-w);
}
}
//Update Height and Vertical Position of the Original Image
if (y<0){
h = h + -y;
if (in.getHeight()>h){
h = h + (in.getHeight()-h);
}
y2 = -y;
y = 0;
}
else if(y>h){
h = (h + (y-h)) + in.getHeight();
}
else{
if ((y+in.getHeight())>h){
h = h + ((y+in.getHeight())-h);
}
}
}
//Create new image "collage"
if (w>bufferedImage.getWidth() || h>bufferedImage.getHeight()){
BufferedImage bi = new BufferedImage(w, h, getImageType());
Graphics2D g2d = bi.createGraphics();
java.awt.Image img = bufferedImage;
g2d.drawImage(img, x2, y2, null);
img = in;
g2d.drawImage(img, x, y, null);
g2d.dispose();
bufferedImage = bi;
}
else{
Graphics2D g2d = bufferedImage.createGraphics();
java.awt.Image img = in;
g2d.drawImage(img, x, y, null);
g2d.dispose();
}
}
//**************************************************************************
//** addImage
//**************************************************************************
/** Used to add an image "overlay" to the existing image at a given
* position. This method can also be used to create image mosiacs.
*/
public void addImage(javaxt.io.Image in, int x, int y, boolean expand){
addImage(in.getBufferedImage(),x,y,expand);
}
//**************************************************************************
//** createBufferedImage
//**************************************************************************
/** Used to create a BufferedImage from a InputStream
*/
private void createBufferedImage(java.io.InputStream input) {
try{
//bufferedImage = ImageIO.read(input);
javax.imageio.stream.ImageInputStream stream = ImageIO.createImageInputStream(input);
Iterator iter = ImageIO.getImageReaders(stream);
if (!iter.hasNext()) {
return;
}
ImageReader reader = (ImageReader)iter.next();
ImageReadParam param = reader.getDefaultReadParam();
reader.setInput(stream, true, true);
try {
bufferedImage = reader.read(0, param);
metadata = reader.getImageMetadata(0);
}
finally {
reader.dispose();
stream.close();
}
input.close();
}
catch(Exception e){
//e.printStackTrace();
}
}
//**************************************************************************
//** Rotate
//**************************************************************************
/** Used to rotate the image (clockwise). Rotation angle is specified in
* degrees relative to the top of the image.
*/
public void rotate(double Degrees){
//Define Image Center (Axis of Rotation)
int width = this.getWidth();
int height = this.getHeight();
int cx = width/2;
int cy = height/2;
//create an array containing the corners of the image (TL,TR,BR,BL)
int[] corners = { 0, 0, width, 0, width, height, 0, height };
//Define bounds of the image
int minX, minY, maxX, maxY;
minX = maxX = cx;
minY = maxY = cy;
double theta = Math.toRadians(Degrees);
for (int i=0; imaxX) maxX = x;
if(xmaxY) maxY = y;
if(ymaxWidth){
//Set width
ratio = (double)maxWidth/(double)width;
width = (int)Math.round(width * ratio);
height = (int)Math.round(height * ratio);
}
}
else{
//Set width
double ratio = (double)maxWidth/(double)this.getWidth();
width = (int)Math.round(this.getWidth() * ratio);
height = (int)Math.round(this.getHeight() * ratio);
if (height>maxHeight){
//Set height
ratio = (double)maxHeight/(double)height;
width = (int)Math.round(width * ratio);
height = (int)Math.round(height * ratio);
}
}
}
//Resize the image (create new buffered image)
java.awt.Image outputImage = bufferedImage.getScaledInstance(width, height, BufferedImage.SCALE_AREA_AVERAGING);
BufferedImage bi = new BufferedImage(width, height, getImageType());
Graphics2D g2d = bi.createGraphics( );
g2d.drawImage(outputImage, 0, 0, null);
g2d.dispose();
this.bufferedImage = bi;
outputImage = null;
bi = null;
g2d = null;
}
//**************************************************************************
//** Set/Update Corners (Skew)
//**************************************************************************
/** Used to skew an image by updating the corner coordinates. Coordinates
* are supplied in clockwise order starting from the upper left corner.
*/
public void setCorners(float x0, float y0, //UL
float x1, float y1, //UR
float x2, float y2, //LR
float x3, float y3){ //LL
Skew skew = new Skew(this.bufferedImage);
this.bufferedImage = skew.setCorners(x0,y0,x1,y1,x2,y2,x3,y3);
if (corners==null) corners = new java.util.ArrayList();
else corners.clear();
corners.add((Float)x0); corners.add((Float)y0);
corners.add((Float)x1); corners.add((Float)y1);
corners.add((Float)x2); corners.add((Float)y2);
corners.add((Float)x3); corners.add((Float)y3);
}
//**************************************************************************
//** Get Corners
//**************************************************************************
/** Used to retrieve the corner coordinates of the image. Coordinates are
* supplied in clockwise order starting from the upper left corner. This
* information is particularly useful for generating drop shadows, inner
* and outer glow, and reflections.
* NOTE: Coordinates are not updated after resize(), rotate(), or addImage()
*/
public float[] getCorners(){
if (corners==null){
float w = getWidth();
float h = getHeight();
corners = new java.util.ArrayList();
corners.add((Float)0f); corners.add((Float)0f);
corners.add((Float)w); corners.add((Float)0f);
corners.add((Float)w); corners.add((Float)h);
corners.add((Float)0f); corners.add((Float)h);
}
Object[] arr = corners.toArray();
float[] ret = new float[arr.length];
for (int i=0; i radius2)
matrix[index] = 0;
else
matrix[index] = (float)Math.exp(-(distance)/sigma22) / sqrtSigmaPi2;
total += matrix[index];
index++;
}
for (int i = 0; i < rows; i++)
matrix[i] /= total;
Kernel kernel = new Kernel(rows, 1, matrix);
//
int width = this.getWidth();
int height = this.getHeight();
int[] inPixels = new int[width*height];
int[] outPixels = new int[width*height];
bufferedImage.getRGB( 0, 0, width, height, inPixels, 0, width );
convolveAndTranspose(kernel, inPixels, outPixels, width, height, true, CLAMP_EDGES);
convolveAndTranspose(kernel, outPixels, inPixels, height, width, true, CLAMP_EDGES);
bufferedImage.setRGB( 0, 0, width, height, inPixels, 0, width );
}
private static int CLAMP_EDGES = 1;
private static int WRAP_EDGES = 2;
//**************************************************************************
//** convolveAndTranspose
//**************************************************************************
/** Applies 1D Gaussian kernel used to blur an image. This filter should be
* applied twice, once horizontally and once vertically.
* @author Jerry Huxtable
*/
private static void convolveAndTranspose(Kernel kernel, int[] inPixels, int[] outPixels,
int width, int height, boolean alpha, int edgeAction) {
float[] matrix = kernel.getKernelData( null );
int cols = kernel.getWidth();
int cols2 = cols/2;
for (int y = 0; y < height; y++) {
int index = y;
int ioffset = y*width;
for (int x = 0; x < width; x++) {
float r = 0, g = 0, b = 0, a = 0;
int moffset = cols2;
for (int col = -cols2; col <= cols2; col++) {
float f = matrix[moffset+col];
if (f != 0) {
int ix = x+col;
if ( ix < 0 ) {
if ( edgeAction == CLAMP_EDGES )
ix = 0;
else if ( edgeAction == WRAP_EDGES )
ix = (x+width) % width;
} else if ( ix >= width) {
if ( edgeAction == CLAMP_EDGES )
ix = width-1;
else if ( edgeAction == WRAP_EDGES )
ix = (x+width) % width;
}
int rgb = inPixels[ioffset+ix];
a += f * ((rgb >> 24) & 0xff);
r += f * ((rgb >> 16) & 0xff);
g += f * ((rgb >> 8) & 0xff);
b += f * (rgb & 0xff);
}
}
int ia = alpha ? clamp((int)(a+0.5)) : 0xff;
int ir = clamp((int)(r+0.5));
int ig = clamp((int)(g+0.5));
int ib = clamp((int)(b+0.5));
outPixels[index] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
index += height;
}
}
}
//**************************************************************************
//** clamp
//**************************************************************************
/** Clamp a value to the range 0..255
*/
private static int clamp(int c) {
if (c < 0) return 0;
if (c > 255) return 255;
return c;
}
//**************************************************************************
//** Desaturate
//**************************************************************************
/** Used to completely desaturate an image (creates a gray-scale image).
*/
public void desaturate(){
bufferedImage = desaturate(bufferedImage);
}
//**************************************************************************
//** Desaturate
//**************************************************************************
/** Used to desaturate an image by a specified percentage (expressed as
* a double or float). The larger the percentage, the greater the
* desaturation and the "grayer" the image. Valid ranges are from 0-1.
*/
public void desaturate(double percent){
float alpha = (float) (percent);
java.awt.Image overlay = desaturate(bufferedImage);
Graphics2D g2d = bufferedImage.createGraphics();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g2d.drawImage(overlay,0,0,null);
g2d.dispose();
}
//**************************************************************************
//** Desaturate (Private Function)
//**************************************************************************
/** Convenience function called by the other 2 desaturation methods.
*/
private BufferedImage desaturate(BufferedImage in){
BufferedImage out = new BufferedImage(in.getWidth(), in.getHeight(), getImageType(in) );
BufferedImageOp op = new ColorConvertOp(
ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
return op.filter(in, out);
}
//**************************************************************************
//** setOpacity
//**************************************************************************
/** Used to adjust the opacity of the image.
*/
public void setOpacity(double percent){
if (percent>1) percent=percent/100;
float alpha = (float) (percent);
int imageType = bufferedImage.getType();
if (imageType == 0) {
imageType = BufferedImage.TYPE_INT_ARGB;
}
BufferedImage out = new BufferedImage(getWidth(),getHeight(),imageType);
Graphics2D g2d = out.createGraphics();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g2d.drawImage(bufferedImage,0,0,null);
g2d.dispose();
bufferedImage = out;
}
//**************************************************************************
//** Flip (Horizonal)
//**************************************************************************
/** Used to flip an image along it's y-axis (horizontal). Vertical flipping
* is supported via the rotate method (i.e. rotate +/-180).
*/
public void flip(){
BufferedImage out = new BufferedImage(getWidth(), getHeight(), getImageType());
AffineTransform tx = AffineTransform.getScaleInstance(-1, 1);
tx.translate(-bufferedImage.getWidth(), 0);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);
bufferedImage = op.filter(bufferedImage, out);
}
//**************************************************************************
//** Crop
//**************************************************************************
/** Used to crop/subset the current image.
*/
public void crop(int x, int y, int width, int height){
bufferedImage = getSubimage(x,y,width,height);
}
//**************************************************************************
//** copy
//**************************************************************************
/** Returns a copy of the current image.
*/
public Image copy(){
ColorModel cm = bufferedImage.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = bufferedImage.copyData(null);
BufferedImage bi = new BufferedImage(cm, raster, isAlphaPremultiplied, null);
bi = bi.getSubimage(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
return new Image(bi);
}
//**************************************************************************
//** copyRect
//**************************************************************************
/** Returns a copy of the image at a given rectangle.
*/
public Image copyRect(int x, int y, int width, int height){
return new Image(getSubimage(x,y,width,height));
}
//**************************************************************************
//** getSubimage
//**************************************************************************
/** Returns a copy of the image at a given rectangle. In Java 1.6, the
* BufferedImage.getSubimage() throws an exception if the rectangle falls
* outside the image bounds. This method was written to overcome this
* limitation.
*/
private BufferedImage getSubimage(int x, int y, int width, int height){
Rectangle rect1 = new Rectangle(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
Rectangle rect2 = new Rectangle(x, y, width, height);
//If the requested rectangle falls inside the image bounds, simply return
//the subimage
if (rect1.contains(rect2)){
return (bufferedImage.getSubimage(x,y,width,height));
}
else{ //requested rectangle falls outside the image bounds!
//Create buffered image
BufferedImage bi = new BufferedImage(width, height, getImageType());
//If the requested rectangle intersects the image bounds, crop the
//the source image and insert it into the BufferedImage
if (rect1.intersects(rect2)){
Graphics2D g2d = bi.createGraphics();
BufferedImage subImage = null;
int X;
int Y;
if (x<0){
int x1 = 0;
int y1;
int h;
int w;
if (y<0){
y1 = 0;
h = y+height;
Y = height - h;
}
else{
y1 = y;
h = height;
Y = 0;
}
if (h+y1>bufferedImage.getHeight()) h = bufferedImage.getHeight()-y1;
w = x+width;
if (w>bufferedImage.getWidth()) w = bufferedImage.getWidth();
subImage = bufferedImage.getSubimage(x1,y1,w,h);
X = width - w;
}
else{
int x1 = x;
int y1;
int h;
int w;
if (y<0){
y1 = 0;
h = y+height;
Y = height - h;
}
else{
y1 = y;
h = height;
Y = 0;
}
if (h+y1>bufferedImage.getHeight()) h = bufferedImage.getHeight()-y1;
w = width;
if (w+x1>bufferedImage.getWidth()) w = bufferedImage.getWidth()-x1;
X = 0;
subImage = bufferedImage.getSubimage(x1,y1,w,h);
}
g2d.drawImage(subImage, X, Y, null);
g2d.dispose();
}
return bi;
}
}
//**************************************************************************
//** trim
//**************************************************************************
/** Used to remove excess pixels around an image by cropping the image to its
* "true" extents. Crop bounds are determined by finding the first non-null
* or non-black pixel on each side of the image.
*/
public void trim(){
trim(0,0,0);
}
//**************************************************************************
//** trim
//**************************************************************************
/** Used to remove excess pixels around an image by cropping the image to its
* "true" extents. Crop bounds are determined by finding pixels that *don't*
* match the input color. For example, you can trim off excess black pixels
* around an image by specifying an rgb value of 0,0,0. Similarly, you can
* trim off pure white pixels around an image by specifying an rgb value of
* 255,255,255. Note that transparent pixels are considered as null values
* and will be automatically trimmed from the edges.
*/
public void trim(int r, int g, int b){
int top = 0;
int bottom = 0;
int left = 0;
int right = 0;
for (int y=0; y-1; y--){
for (int x=0; x-1; x--){
for (int y=0; y1&&quality<=100) quality=quality/100;
if (quality==1f && useSunCodec) quality = 1.2f;
if (quality>=0f && quality<=1.2f) outputQuality = quality;
}
//**************************************************************************
//** isJPEG
//**************************************************************************
/** Returns true if the given file extension is associated with a jpeg
* compressed image.
*/
private boolean isJPEG(String FileExtension){
FileExtension = FileExtension.trim().toLowerCase();
if (FileExtension.equals("jpg") ||
FileExtension.equals("jpeg") ||
FileExtension.equals("jpe") ||
FileExtension.equals("jff") ){
return true;
}
return false;
}
//**************************************************************************
//** isJPEG2000
//**************************************************************************
/** Returns true if the given file extension is associated with a jpeg2000
* compressed image.
*/
private boolean isJPEG2000(String FileExtension){
FileExtension = FileExtension.trim().toLowerCase();
if (FileExtension.equals("jp2") ||
FileExtension.equals("jpc") ||
FileExtension.equals("j2k") ||
FileExtension.equals("jpx") ){
return true;
}
return false;
}
//**************************************************************************
//** getJPEGByteArray
//**************************************************************************
/** Returns a JPEG compressed byte array. */
private byte[] getJPEGByteArray(float outputQuality) throws IOException {
if (outputQuality>=0f && outputQuality<=1.2f) {
ByteArrayOutputStream bas = new ByteArrayOutputStream();
BufferedImage bi = bufferedImage;
int t = bufferedImage.getTransparency();
//if (t==BufferedImage.BITMASK) System.out.println("BITMASK");
//if (t==BufferedImage.OPAQUE) System.out.println("OPAQUE");
if (t==BufferedImage.TRANSLUCENT){
bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D biContext = bi.createGraphics();
biContext.drawImage ( bufferedImage, 0, 0, null );
}
//First we will try to compress the image using the com.sun.image.codec.jpeg
//package. These classes are marked as deprecated in JDK 1.7 and several
//users have reported problems with this method. Instead, we are
//supposed to use the JPEGImageWriteParam class. However, I have not
//been able to adequatly test the compression quality or find an
//anology to the setHorizontalSubsampling and setVerticalSubsampling
//methods. Therefore, we will attempt to compress the image using the
//com.sun.image.codec.jpeg package. If the compression fails, we will
//use the JPEGImageWriteParam.
if (useSunCodec){
try{
//For Java 1.7 users, we will try to invoke the Sun JPEG Codec using reflection
Object encoder = JPEGCodec.getMethod("createJPEGEncoder", java.io.OutputStream.class).invoke(JPEGCodec, bas);
Object params = JPEGCodec.getMethod("getDefaultJPEGEncodeParam", BufferedImage.class).invoke(JPEGCodec, bi);
params.getClass().getMethod("setQuality", float.class, boolean.class).invoke(params, outputQuality, true);
params.getClass().getMethod("setHorizontalSubsampling", int.class, int.class).invoke(params, 0, 2);
params.getClass().getMethod("setVerticalSubsampling", int.class, int.class).invoke(params, 0, 2);
//Here's the original compression code without reflection
/*
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bas);
JPEGEncodeParam params = JPEGCodec.getDefaultJPEGEncodeParam(bi);
params.setQuality(outputQuality, true); //true
params.setHorizontalSubsampling(0,2);
params.setVerticalSubsampling(0,2);
params.setMarkerData(...);
encoder.encode(bi, params);
*/
//Save metadata as needed
if (saveMetadata && metadata!=null){
java.lang.reflect.Method setMarkerData = params.getClass().getMethod("setMarkerData", int.class, byte[][].class);
//Parse unknown markers (similar logic to the getUnknownTags method)
java.util.HashSet markers = new java.util.HashSet();
for (String name : metadata.getMetadataFormatNames()) {
IIOMetadataNode node = (IIOMetadataNode) metadata.getAsTree(name);
for (Node unknownNode : getElementsByTagName("unknown", node)){
String markerTag = getAttributeValue(unknownNode.getAttributes(), "MarkerTag");
try{
int marker = Integer.parseInt(markerTag);
if (!markers.contains(marker)){
markers.add(marker);
byte[] data = (byte[]) ((IIOMetadataNode) unknownNode).getUserObject();
if (data!=null){
byte[][] app = new byte[1][data.length];
app[0] = data;
setMarkerData.invoke(params, marker, app);
}
}
}
catch(Exception e){
//e.printStackTrace();
}
}
}
}
encoder.getClass().getMethod("encode", BufferedImage.class, JPEGEncodeParam).invoke(encoder, bi, params);
}
catch(Exception e){
bas.reset();
}
}
//If the com.sun.image.codec.jpeg package is not found or if the
//compression failed, we will use the JPEGImageWriteParam class.
if (bas.size()==0){
if (outputQuality>1f) outputQuality = 1f;
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
JPEGImageWriteParam params = (JPEGImageWriteParam) writer.getDefaultWriteParam();
params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
params.setCompressionQuality(outputQuality);
writer.setOutput(ImageIO.createImageOutputStream(bas));
if (saveMetadata){
writer.write(metadata, new IIOImage(bi, null, metadata), params);
}
else{
writer.write(null, new IIOImage(bi, null, null), params);
}
}
bas.flush();
return bas.toByteArray();
}
else{
return getByteArray();
}
}
//**************************************************************************
//** getImageType
//**************************************************************************
private int getImageType(){
return getImageType(this.bufferedImage);
}
private int getImageType(BufferedImage bufferedImage){
int imageType = bufferedImage.getType();
if (imageType <= 0 || imageType == 12) {
imageType = BufferedImage.TYPE_INT_ARGB;
}
return imageType;
}
//**************************************************************************
//** getExtension
//**************************************************************************
/** Returns the file extension for a given file name, if one exists. */
private String getExtension(String FileName){
if (FileName.contains((CharSequence) ".")){
return FileName.substring(FileName.lastIndexOf(".")+1,FileName.length());
}
else{
return "";
}
}
//**************************************************************************
//** hasColor
//**************************************************************************
/** Used to determine whether a given pixel has a color value. Returns false
* if the pixel matches the input color or is transparent.
*/
private boolean hasColor(int pixel, int red, int green, int blue){
int a = (pixel >> 24) & 0xff;
int r = (pixel >> 16) & 0xff;
int g = (pixel >> 8) & 0xff;
int b = (pixel) & 0xff;
if ((r==red && g==green && b==blue) || a==0 ){
return false;
}
return true;
}
//**************************************************************************
//** equals
//**************************************************************************
/** Used to compare this image to another. Returns true if the all the
* pixels (ARGB values) match. See isSimilarTo() to compare similar images.
*/
public boolean equals(Object obj){
if (obj!=null){
if (obj instanceof javaxt.io.Image){
javaxt.io.Image image = (javaxt.io.Image) obj;
if (image.getWidth()==this.getWidth() &&
image.getHeight()==this.getHeight())
{
//Iterate through all the pixels in the image and compare RGB values
for (int i=0; i avg ? (hashBits << 1) | 0x01 : (hashBits << 1) & 0xFFFFFFFFFFFFFFFEl);
}
}
return hashBits;
}
//**************************************************************************
//** getIIOMetadata
//**************************************************************************
/** Returns the raw, javax.imageio.metadata.IIOMetadata associated with this
* image. You can iterate through the metadata using an xml parser like this:
IIOMetadata metadata = image.getIIOMetadata();
for (String name : metadata.getMetadataFormatNames()) {
System.out.println( "Format name: " + name );
org.w3c.dom.Node metadataNode = metadata.getAsTree(name);
System.out.println(javaxt.xml.DOM.getNodeValue(metadataNode));
}
*/
public IIOMetadata getIIOMetadata(){
return metadata;
}
//**************************************************************************
//** setIIOMetadata
//**************************************************************************
/** Used to set/update the raw javax.imageio.metadata.IIOMetadata associated
* with this image.
*/
public void setIIOMetadata(IIOMetadata metadata){
this.metadata = metadata;
iptc = null;
exif = null;
gps = null;
saveMetadata = true;
}
//**************************************************************************
//** getIptcData
//**************************************************************************
/** Returns the raw IPTC byte array (marker 0xED).
*/
public byte[] getIptcData(){
IIOMetadataNode[] tags = getUnknownTags(0xED);
if (tags.length==0) return null;
return (byte[]) tags[0].getUserObject();
}
//**************************************************************************
//** getIptcTags
//**************************************************************************
/** Used to parse IPTC metadata and return a list of key/value pairs found
* in the metadata. You can retrieve specific IPTC metadata values like
* this:
javaxt.io.Image image = new javaxt.io.Image("/temp/image.jpg");
java.util.HashMap iptc = image.getIptcTags();
System.out.println("Date: " + iptc.get(0x0237));
System.out.println("Caption: " + iptc.get(0x0278));
System.out.println("Copyright: " + iptc.get(0x0274));
*/
public HashMap getIptcTags(){
if (iptc==null){
iptc = new HashMap();
for (IIOMetadataNode marker : getUnknownTags(0xED)){
byte[] iptcData = (byte[]) marker.getUserObject();
HashMap tags = new MetadataParser(iptcData, 0xED).getTags("IPTC");
iptc.putAll(tags);
}
}
return iptc;
}
//**************************************************************************
//** getExifData
//**************************************************************************
/** Returns the raw EXIF byte array (marker 0xE1).
*/
public byte[] getExifData(){
IIOMetadataNode[] tags = getUnknownTags(0xE1);
if (tags.length==0) return null;
return (byte[]) tags[0].getUserObject();
}
//**************************************************************************
//** getExifTags
//**************************************************************************
/** Used to parse EXIF metadata and return a list of key/value pairs found
* in the metadata. Values can be Strings, Integers, or raw Byte Arrays.
* You can retrieve specific EXIF metadata values like this:
javaxt.io.Image image = new javaxt.io.Image("/temp/image.jpg");
java.util.HashMap exif = image.getExifTags();
System.out.println("Date: " + exif.get(0x0132));
System.out.println("Camera: " + exif.get(0x0110));
System.out.println("Focal Length: " + exif.get(0x920A));
System.out.println("F-Stop: " + exif.get(0x829D));
System.out.println("Shutter Speed: " + exif.get(0x829A));
* Note that the EXIF MakerNote is not parsed.
*/
public HashMap getExifTags(){
if (exif==null) parseExif();
return exif;
}
//**************************************************************************
//** getGpsTags
//**************************************************************************
/** Used to parse EXIF metadata and return a list of key/value pairs
* associated with GPS metadata. Values can be Strings, Integers, or raw
* Byte Arrays.
*/
public HashMap getGpsTags(){
if (gps==null) parseExif();
return gps;
}
/** Private method used to initialize the exif and gps hashmaps */
private void parseExif(){
exif = new HashMap();
gps = new HashMap();
for (IIOMetadataNode marker : getUnknownTags(0xE1)){
byte[] exifData = (byte[]) marker.getUserObject();
MetadataParser metadataParser = new MetadataParser(exifData, 0xE1);
HashMap exif = metadataParser.getTags("EXIF");
HashMap gps = metadataParser.getTags("GPS");
if (exif!=null) this.exif.putAll(exif);
if (gps!=null) this.gps.putAll(gps);
metadataParser = null;
}
}
//**************************************************************************
//** getGPSCoordinate
//**************************************************************************
/** Returns the x/y (lon/lat) coordinate tuple for the image. Value is
* derived from EXIF GPS metadata (tags 0x0001, 0x0002, 0x0003, 0x0004).
*/
public double[] getGPSCoordinate(){
getExifTags();
try{
Double lat = getCoordinate((String) gps.get(0x0002));
Double lon = getCoordinate((String) gps.get(0x0004));
String latRef = (String) gps.get(0x0001); //N
String lonRef = (String) gps.get(0x0003); //W
if (!latRef.equalsIgnoreCase("N")) lat = -lat;
if (!lonRef.equalsIgnoreCase("E")) lon = -lon;
return new double[]{lon, lat};
}
catch(Exception e){
return null;
}
}
private double getCoordinate(String RationalArray) {
//num + "/" + den
String[] arr = RationalArray.substring(1, RationalArray.length()-1).split(",");
String[] deg = arr[0].trim().split("/");
String[] min = arr[1].trim().split("/");
String[] sec = arr[2].trim().split("/");
double degNumerator = Double.parseDouble(deg[0]);
double degDenominator = 1D; try{degDenominator = Double.parseDouble(deg[1]);} catch(Exception e){}
double minNumerator = Double.parseDouble(min[0]);
double minDenominator = 1D; try{minDenominator = Double.parseDouble(min[1]);} catch(Exception e){}
double secNumerator = Double.parseDouble(sec[0]);
double secDenominator = 1D; try{secDenominator = Double.parseDouble(sec[1]);} catch(Exception e){}
double m = 0;
if (degDenominator != 0 || degNumerator != 0){
m = (degNumerator / degDenominator);
}
if (minDenominator != 0 || minNumerator != 0){
m += (minNumerator / minDenominator) / 60D;
}
if (secDenominator != 0 || secNumerator != 0){
m += (secNumerator / secDenominator / 3600D);
}
return m;
}
//**************************************************************************
//** getGPSDatum
//**************************************************************************
/** Returns the datum associated with the GPS coordinate. Value is
* derived from EXIF GPS metadata (tag 0x0012).
*/
public String getGPSDatum(){
getExifTags();
return (String) gps.get(0x0012);
}
//**************************************************************************
//** getUnknownTags
//**************************************************************************
/** Returns a list of "unknown" IIOMetadataNodes for a given MarkerTag. You
* can use this method to retrieve EXIF, IPTC, XPM, and other format
* specific metadata. Example:
byte[] IptcData = (byte[]) metadata.getUnknownTags(0xED)[0].getUserObject();
byte[] ExifData = (byte[]) metadata.getUnknownTags(0xE1)[0].getUserObject();
*/
public IIOMetadataNode[] getUnknownTags(int MarkerTag){
java.util.ArrayList markers = new java.util.ArrayList();
if (metadata!=null)
for (String name : metadata.getMetadataFormatNames()) {
IIOMetadataNode node=(IIOMetadataNode) metadata.getAsTree(name);
Node[] unknownNodes = getElementsByTagName("unknown", node);
for (Node unknownNode : unknownNodes){
try{
int marker = Integer.parseInt(getAttributeValue(unknownNode.getAttributes(), "MarkerTag"));
if (marker==MarkerTag) markers.add((IIOMetadataNode) unknownNode);
}
catch(Exception e){
//e.printStackTrace();
}
}
}
return markers.toArray(new IIOMetadataNode[markers.size()]);
}
//**************************************************************************
//** getMetadataByTagName
//**************************************************************************
/** Returns a list of IIOMetadataNodes for a given tag name (e.g. "Chroma",
* "Compression", "Data", "Dimension", "Transparency", etc).
//Print unknown tags
for (IIOMetadataNode unknownNode : metadata.getMetadataByTagName("unknown")){
int marker = Integer.parseInt(javaxt.xml.DOM.getAttributeValue(unknownNode, "MarkerTag"));
System.out.println(marker + "\t" + "0x" + Integer.toHexString(marker));
}
*/
public IIOMetadataNode[] getMetadataByTagName(String tagName){
java.util.ArrayList tags = new java.util.ArrayList();
if (metadata!=null)
for (String name : metadata.getMetadataFormatNames()) {
IIOMetadataNode node=(IIOMetadataNode) metadata.getAsTree(name);
Node[] unknownNodes = getElementsByTagName(tagName, node);
for (Node unknownNode : unknownNodes){
tags.add((IIOMetadataNode) unknownNode);
}
}
return tags.toArray(new IIOMetadataNode[tags.size()]);
}
//**************************************************************************
//** getElementsByTagName (Copied from javaxt.xml.DOM)
//**************************************************************************
/** Returns an array of nodes that match a given tagName (node name). The
* results will include all nodes that match, regardless of namespace. To
* narrow the results to a specific namespace, simply include the namespace
* prefix in the tag name (e.g. "t:Contact"). Returns an empty array if
* no nodes are found.
*/
private static Node[] getElementsByTagName(String tagName, Node node){
java.util.ArrayList nodes = new java.util.ArrayList();
getElementsByTagName(tagName, node, nodes);
return nodes.toArray(new Node[nodes.size()]);
}
private static void getElementsByTagName(String tagName, Node node, java.util.ArrayList nodes){
if (node!=null && node.getNodeType()==1){
String nodeName = node.getNodeName().trim();
if (nodeName.contains(":") && !tagName.contains(":")){
nodeName = nodeName.substring(nodeName.indexOf(":")+1);
}
if (nodeName.equalsIgnoreCase(tagName)){
nodes.add(node);
}
NodeList childNodes = node.getChildNodes();
for (int i=0; i> tags =
new HashMap>();
public MetadataParser(byte[] data, int marker) {
switch (marker) {
case 0xED: parseIptc(data); break;
case 0xE1: parseExif(data); break;
}
data = null;
}
//**************************************************************************
//** parseIptc
//**************************************************************************
/** Used to parse IPTC metadata
*/
private void parseIptc(byte[] iptcData) {
HashMap tags = new HashMap();
this.tags.put("IPTC", tags);
data = iptcData;
int offset = 0;
while (offset < data.length) {
if (data[offset] == 0x1c) {
offset++;
int directoryType;
int tagType;
int tagByteCount;
try {
directoryType = data[offset++];
tagType = data[offset++];
tagByteCount = get16u(offset);
offset += 2;
}
catch (Exception e) {
return;
}
int tagIdentifier = tagType | (directoryType << 8);
String str = "";
if (tagByteCount < 1 || tagByteCount>(data.length-offset)) {
}
else {
try {
str = new String(data, offset, tagByteCount, "UTF-8");
offset += tagByteCount;
}
catch (Exception e) {
}
}
tags.put(tagIdentifier, str);
}
else{
offset++;
}
}
}
//**************************************************************************
//** parseExif
//**************************************************************************
/** Used to parse EXIF metadata
*/
public void parseExif(byte[] exifData) {
HashMap tags = new HashMap();
this.tags.put("EXIF", tags);
try{
String dataStr = new String(exifData, 0, 8, "UTF-8"); //new String(exifData);
if (exifData.length <= 4 || !"Exif".equals(dataStr.substring(0, 4))) {
//System.err.println("Not really EXIF data");
return;
}
String byteOrderMarker = dataStr.substring(6, 8);
if ("II".equals(byteOrderMarker)) {
intelOrder = true;
} else if ("MM".equals(byteOrderMarker)) {
intelOrder = false;
} else {
//System.err.println("Incorrect byte order in EXIF data.");
return;
}
}
catch(Exception e){
return;
}
data = exifData;
int checkValue = get16u(8);
if (checkValue != 0x2a) {
data = null;
//System.err.println("Check value fails: 0x"+ Integer.toHexString(checkValue));
return;
}
if (data==null) return;
int firstOffset = get32u(10);
processExifDir(6 + firstOffset, 6, tags);
}
//**************************************************************************
//** getTags
//**************************************************************************
/** Returns key/value pairs representing the EXIF or IPTC data.
*/
public HashMap getTags(String dir) {
return tags.get(dir);
}
private void processExifDir(int dirStart, int offsetBase, HashMap tags) {
if (dirStart>=data.length) return;
int numEntries = get16u(dirStart);
for (int de = 0; de < numEntries; de++) {
int dirOffset = dirStart + 2 + (12 * de);
int tag = get16u(dirOffset);
int format = get16u(dirOffset + 2);
int components = get32u(dirOffset + 4);
//System.err.println("EXIF: entry: 0x" + Integer.toHexString(tag)
// + " " + format
// + " " + components);
if (format < 0 || format > NUM_FORMATS) {
//System.err.println("Bad number of formats in EXIF dir: " + format);
return;
}
int byteCount = components * bytesPerFormat[format];
int valueOffset = dirOffset + 8;
if (byteCount > 4) {
int offsetVal = get32u(dirOffset + 8);
valueOffset = offsetBase + offsetVal;
}
if (tag == TAG_EXIF_OFFSET || tag == TAG_INTEROP_OFFSET || tag == TAG_GPS_OFFSET) {
String dirName = "";
switch (tag) {
case TAG_EXIF_OFFSET:
dirName = "EXIF";
break;
case TAG_INTEROP_OFFSET:
dirName = "EXIF";
break;
case TAG_GPS_OFFSET:
dirName = "GPS";
break;
}
tags = this.tags.get(dirName);
if (tags==null){
tags = new HashMap();
this.tags.put(dirName, tags);
}
int subdirOffset = get32u(valueOffset);
processExifDir(offsetBase + subdirOffset, offsetBase, tags);
}
//else if (tag==0x927c){ //Maker Note
//TODO: Come up with a clever way to process the Maker Note
//data = java.util.Arrays.copyOfRange(data, valueOffset, byteCount);
//tags = new HashMap();
//processExifDir(0, 6);
//}
else {
switch (format) {
case FMT_STRING:
String value = getString(valueOffset, byteCount);
if (value!=null) tags.put(tag, value);
break;
case FMT_SBYTE:
case FMT_BYTE:
case FMT_USHORT:
case FMT_SSHORT:
case FMT_ULONG:
case FMT_SLONG:
tags.put(tag, (int) getDouble(format, valueOffset));
break;
case FMT_URATIONAL:
case FMT_SRATIONAL:
if (components>1) {
//Create a string representing an array of rational numbers
StringBuffer str = new StringBuffer();
str.append("[");
for (int i=0; i= 0 && srcX < srcWidth1 && srcY >= 0 && srcY < srcHeight1) {
// Easy case, all corners are in the image
int i = srcWidth*srcY + srcX;
nw = inPixels[i];
ne = inPixels[i+1];
sw = inPixels[i+srcWidth];
se = inPixels[i+srcWidth+1];
} else {
// Some of the corners are off the image
nw = getPixel( inPixels, srcX, srcY, srcWidth, srcHeight );
ne = getPixel( inPixels, srcX+1, srcY, srcWidth, srcHeight );
sw = getPixel( inPixels, srcX, srcY+1, srcWidth, srcHeight );
se = getPixel( inPixels, srcX+1, srcY+1, srcWidth, srcHeight );
}
outPixels[x] = bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se);
}
setRGB( dst, 0, y, transformedSpace.width, 1, outPixels );
}
return dst;
}
final private int getPixel( int[] pixels, int x, int y, int width, int height ) {
if (x < 0 || x >= width || y < 0 || y >= height) {
switch (edgeAction) {
case ZERO:
default:
return 0;
case WRAP:
return pixels[(mod(y, height) * width) + mod(x, width)];
case CLAMP:
return pixels[(clamp(y, 0, height-1) * width) + clamp(x, 0, width-1)];
}
}
return pixels[ y*width+x ];
}
protected BufferedImage filterPixelsNN( BufferedImage dst, int width,
int height, int[] inPixels, Rectangle transformedSpace )
{
int srcWidth = width;
int srcHeight = height;
int outWidth = transformedSpace.width;
int outHeight = transformedSpace.height;
int outX, outY, srcX, srcY;
int[] outPixels = new int[outWidth];
outX = transformedSpace.x;
outY = transformedSpace.y;
int[] rgb = new int[4];
float[] out = new float[2];
for (int y = 0; y < outHeight; y++) {
for (int x = 0; x < outWidth; x++) {
transformInverse(outX+x, outY+y, out);
srcX = (int)out[0];
srcY = (int)out[1];
// int casting rounds towards zero, so we check out[0] < 0, not srcX < 0
if (out[0] < 0 || srcX >= srcWidth || out[1] < 0 || srcY >= srcHeight) {
int p;
switch (edgeAction) {
case ZERO:
default:
p = 0;
break;
case WRAP:
p = inPixels[(mod(srcY, srcHeight) * srcWidth) + mod(srcX, srcWidth)];
break;
case CLAMP:
p = inPixels[(clamp(srcY, 0, srcHeight-1) * srcWidth) + clamp(srcX, 0, srcWidth-1)];
break;
}
outPixels[x] = p;
} else {
int i = srcWidth*srcY + srcX;
rgb[0] = inPixels[i];
outPixels[x] = inPixels[i];
}
}
setRGB( dst, 0, y, transformedSpace.width, 1, outPixels );
}
return dst;
}
protected void transformInverse(int x, int y, float[] out) {
out[0] = originalSpace.width * (A*x+B*y+C)/(G*x+H*y+I);
out[1] = originalSpace.height * (D*x+E*y+F)/(G*x+H*y+I);
}
/*
public Rectangle2D getBounds2D( BufferedImage src ) {
return new Rectangle(0, 0, src.getWidth(), src.getHeight());
}
public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
if ( dstPt == null )
dstPt = new Point2D.Double();
dstPt.setLocation( srcPt.getX(), srcPt.getY() );
return dstPt;
}
*/
/**
* A convenience method for getting ARGB pixels from an image. This tries to avoid the performance
* penalty of BufferedImage.getRGB unmanaging the image.
*/
public int[] getRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) {
int type = image.getType();
if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB )
return (int [])image.getRaster().getDataElements( x, y, width, height, pixels );
return image.getRGB( x, y, width, height, pixels, 0, width );
}
/**
* A convenience method for setting ARGB pixels in an image. This tries to avoid the performance
* penalty of BufferedImage.setRGB unmanaging the image.
*/
public void setRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) {
int type = image.getType();
if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB)
image.getRaster().setDataElements( x, y, width, height, pixels );
else
image.setRGB( x, y, width, height, pixels, 0, width );
}
/**
* Clamp a value to an interval.
* @param a the lower clamp threshold
* @param b the upper clamp threshold
* @param x the input parameter
* @return the clamped value
*/
private float clamp(float x, float a, float b) {
return (x < a) ? a : (x > b) ? b : x;
}
/**
* Clamp a value to an interval.
* @param a the lower clamp threshold
* @param b the upper clamp threshold
* @param x the input parameter
* @return the clamped value
*/
private int clamp(int x, int a, int b) {
return (x < a) ? a : (x > b) ? b : x;
}
/**
* Return a mod b. This differs from the % operator with respect to negative numbers.
* @param a the dividend
* @param b the divisor
* @return a mod b
*/
private double mod(double a, double b) {
int n = (int)(a/b);
a -= n*b;
if (a < 0)
return a + b;
return a;
}
/**
* Return a mod b. This differs from the % operator with respect to negative numbers.
* @param a the dividend
* @param b the divisor
* @return a mod b
*/
private float mod(float a, float b) {
int n = (int)(a/b);
a -= n*b;
if (a < 0)
return a + b;
return a;
}
/**
* Return a mod b. This differs from the % operator with respect to negative numbers.
* @param a the dividend
* @param b the divisor
* @return a mod b
*/
private int mod(int a, int b) {
int n = a/b;
a -= n*b;
if (a < 0)
return a + b;
return a;
}
/**
* Bilinear interpolation of ARGB values.
* @param x the X interpolation parameter 0..1
* @param y the y interpolation parameter 0..1
* @param rgb array of four ARGB values in the order NW, NE, SW, SE
* @return the interpolated value
*/
private int bilinearInterpolate(float x, float y, int nw, int ne, int sw, int se) {
float m0, m1;
int a0 = (nw >> 24) & 0xff;
int r0 = (nw >> 16) & 0xff;
int g0 = (nw >> 8) & 0xff;
int b0 = nw & 0xff;
int a1 = (ne >> 24) & 0xff;
int r1 = (ne >> 16) & 0xff;
int g1 = (ne >> 8) & 0xff;
int b1 = ne & 0xff;
int a2 = (sw >> 24) & 0xff;
int r2 = (sw >> 16) & 0xff;
int g2 = (sw >> 8) & 0xff;
int b2 = sw & 0xff;
int a3 = (se >> 24) & 0xff;
int r3 = (se >> 16) & 0xff;
int g3 = (se >> 8) & 0xff;
int b3 = se & 0xff;
float cx = 1.0f-x;
float cy = 1.0f-y;
m0 = cx * a0 + x * a1;
m1 = cx * a2 + x * a3;
int a = (int)(cy * m0 + y * m1);
m0 = cx * r0 + x * r1;
m1 = cx * r2 + x * r3;
int r = (int)(cy * m0 + y * m1);
m0 = cx * g0 + x * g1;
m1 = cx * g2 + x * g3;
int g = (int)(cy * m0 + y * m1);
m0 = cx * b0 + x * b1;
m1 = cx * b2 + x * b3;
int b = (int)(cy * m0 + y * m1);
return (a << 24) | (r << 16) | (g << 8) | b;
}
} //end skew class
} //end image class