javaxt.io.Image Maven / Gradle / Ivy
package javaxt.io;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
//Imports for JPEG
//import com.sun.image.codec.jpeg.*; //<-- Not always available in newer versions
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
//Imports for JP2
//import javax.media.jai.RenderedOp;
//import com.sun.media.imageio.plugins.jpeg2000.J2KImageReadParam;
//******************************************************************************
//** Image Utilities - By Peter Borissow
//******************************************************************************
/**
* 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) {
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 < inputFormats.length; i++) {
String format = inputFormats[i].toUpperCase();
if (format.contains("JPEG") && format.contains("2000")) {
formats.add("JP2");
formats.add("J2C");
formats.add("J2K");
formats.add("JPX");
} else if (format.equals("JPEG") || format.equals("JPG")) {
formats.add("JPE");
formats.add("JFF");
formats.add(format);
} else {
formats.add(format);
}
}
// Sort and return the hashset as an array
inputFormats = formats.toArray(new String[formats.size()]);
java.util.Collections.sort(java.util.Arrays.asList(inputFormats));
return inputFormats;
}
// **************************************************************************
// ** getSunCodec
// **************************************************************************
/**
* Attempts to load classes from the com.sun.image.codec.jpeg package used
* to compress jpeg images. These classes are marked as deprecated in Java
* 1.7 and several distributions of Java no longer include these classes
* (e.g. "IcedTea" OpenJDK 7). Returns true of the classes are available.
*/
private static boolean getSunCodec() {
try {
JPEGCodec = Class.forName("com.sun.image.codec.jpeg.JPEGCodec");
JPEGEncodeParam = Class.forName("com.sun.image.codec.jpeg.JPEGEncodeParam");
return true;
} catch (Exception e) {
return false;
}
}
// **************************************************************************
// ** getWidth
// **************************************************************************
/** Returns the width of the image, in pixels. */
public int getWidth() {
return bufferedImage.getWidth();
}
// **************************************************************************
// ** getHeight
// **************************************************************************
/** Returns the height of the image, in pixels. */
public int getHeight() {
return bufferedImage.getHeight();
}
// **************************************************************************
// ** getGraphics
// **************************************************************************
private Graphics2D getGraphics() {
if (g2d == null) {
g2d = this.bufferedImage.createGraphics();
// Enable anti-alias
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
return g2d;
}
// **************************************************************************
// ** addText
// **************************************************************************
/**
* Used to add text to the image at a given position.
*
* @param x
* Lower left coordinate of the text
* @param y
* Lower left coordinate of the text
*/
public void addText(String text, int x, int y) {
addText(text, x, y, new Font("SansSerif", Font.TRUETYPE_FONT, 12), 0, 0, 0);
}
// **************************************************************************
// ** addText
// **************************************************************************
/**
* Used to add text to the image at a given position.
*
* @param x
* Lower left coordinate of the text
* @param y
* Lower left coordinate of the text
* @param fontName
* Name of the font face (e.g. "Tahoma", "Helvetica", etc.)
* @param fontSize
* Size of the font
* @param r
* Value for the red channel (0-255)
* @param g
* Value for the green channel (0-255)
* @param b
* Value for the blue channel (0-255)
*/
public void addText(String text, int x, int y, String fontName, int fontSize, int r, int g, int b) {
addText(text, x, y, new Font(fontName, Font.TRUETYPE_FONT, fontSize), r, g, b);
}
// **************************************************************************
// ** addText
// **************************************************************************
/**
* Used to add text to the image at a given position.
*
* @param x
* Lower left coordinate of the text
* @param y
* Lower left coordinate of the text
* @param font
* Font
* @param r
* Value for the red channel (0-255)
* @param g
* Value for the green channel (0-255)
* @param b
* Value for the blue channel (0-255)
*/
public void addText(String text, int x, int y, Font font, int r, int g, int b) {
g2d = getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(new Color(r, g, b));
g2d.setFont(font);
g2d.drawString(text, x, y);
}
// **************************************************************************
// ** addPoint
// **************************************************************************
/**
* Simple drawing function used to set color of a specific pixel in the
* image.
*/
public void addPoint(int x, int y, int r, int g, int b) {
setColor(x, y, new Color(r, g, b));
}
// **************************************************************************
// ** setColor
// **************************************************************************
/**
* Used to set the color (ARGB value) for a specific pixel in the image.
* Note that input x,y values are relative to the upper left corner of the
* image, starting at 0,0.
*/
public void setColor(int x, int y, Color color) {
g2d = getGraphics();
Color org = g2d.getColor();
g2d.setColor(color);
g2d.fillRect(x, y, 1, 1);
g2d.setColor(org);
}
// **************************************************************************
// ** getColor
// **************************************************************************
/**
* Used to retrieve the color (ARGB) values for a specific pixel in the
* image. Returns a java.awt.Color object. Note that input x,y values are
* relative to the upper left corner of the image, starting at 0,0.
*/
public Color getColor(int x, int y) {
// return new Color(bufferedImage.getRGB(x, y)); //<--This will return
// an incorrect alpha value
int pixel = bufferedImage.getRGB(x, y);
int alpha = (pixel >> 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 < red.length; i++)
red[i] = 0;
for (int i = 0; i < green.length; i++)
green[i] = 0;
for (int i = 0; i < blue.length; i++)
blue[i] = 0;
for (int i = 0; i < average.length; i++)
average[i] = 0;
// Populate the histograms
for (int i = 0; i < this.getWidth(); i++) {
for (int j = 0; j < this.getHeight(); j++) {
Color color = this.getColor(i, j);
int r = color.getRed();
int g = color.getGreen();
int b = color.getBlue();
red[r] = red[r] + 1;
green[g] = green[g] + 1;
blue[b] = blue[b] + 1;
int avg = Math.round((r + g + b) / 3);
average[avg] = average[avg] + 1;
}
}
java.util.ArrayList 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; i < corners.length; i += 2) {
// Rotates the given point theta radians around (cx,cy)
int x = (int) Math
.round(Math.cos(theta) * (corners[i] - cx) - Math.sin(theta) * (corners[i + 1] - cy) + cx);
int y = (int) Math
.round(Math.sin(theta) * (corners[i] - cx) + Math.cos(theta) * (corners[i + 1] - cy) + cy);
// Update our bounds
if (x > maxX)
maxX = x;
if (x < minX)
minX = x;
if (y > maxY)
maxY = y;
if (y < minY)
minY = y;
}
// Update Image Center Coordinates
cx = cx - minX;
cy = cy - minY;
// Create Buffered Image
BufferedImage result = new BufferedImage(maxX - minX, maxY - minY, BufferedImage.TYPE_INT_ARGB);
// Create Graphics
Graphics2D g2d = result.createGraphics();
// Enable anti-alias and Cubic Resampling
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// Rotate the image
AffineTransform xform = new AffineTransform();
xform.rotate(theta, cx, cy);
g2d.setTransform(xform);
g2d.drawImage(bufferedImage, -minX, -minY, null);
g2d.dispose();
// Update Class Variables
this.bufferedImage = result;
// Delete Heavy Objects
result = null;
xform = null;
}
// **************************************************************************
// ** Rotate Clockwise
// **************************************************************************
/** Rotates the image 90 degrees clockwise */
public void rotateClockwise() {
rotate(90);
}
// **************************************************************************
// ** Rotate Counter Clockwise
// **************************************************************************
/** Rotates the image -90 degrees */
public void rotateCounterClockwise() {
rotate(-90);
}
// **************************************************************************
// ** Auto-Rotate
// **************************************************************************
/**
* Used to automatically rotate the image based on the image metadata (EXIF
* Orientation tag).
*/
public void rotate() {
try {
Integer orientation = (Integer) getExifTags().get(0x0112);
switch (orientation) {
case 1:
return; // "Top, left side (Horizontal / normal)"
case 2:
flip();
break; // "Top, right side (Mirror horizontal)";
case 3:
rotate(180);
break; // "Bottom, right side (Rotate 180)";
case 4: {
flip();
rotate(180);
}
break; // "Bottom, left side (Mirror vertical)";
case 5: {
flip();
rotate(270);
}
break; // "Left side, top (Mirror horizontal and rotate 270
// CW)";
case 6:
rotate(90);
break; // "Right side, top (Rotate 90 CW)";
case 7: {
flip();
rotate(90);
}
break; // "Right side, bottom (Mirror horizontal and rotate 90
// CW)";
case 8:
rotate(270);
break; // "Left side, bottom (Rotate 270 CW)";
}
} catch (Exception e) {
// Failed to parse exif orientation.
}
}
// **************************************************************************
// ** setWidth
// **************************************************************************
/**
* Resizes the image to a given width. The original aspect ratio is
* maintained.
*/
public void setWidth(int Width) {
double ratio = (double) Width / (double) this.getWidth();
double dw = this.getWidth() * ratio;
double dh = this.getHeight() * ratio;
int outputWidth = (int) Math.round(dw);
int outputHeight = (int) Math.round(dh);
resize(outputWidth, outputHeight);
}
// **************************************************************************
// ** setHeight
// **************************************************************************
/**
* Resizes the image to a given height. The original aspect ratio is
* maintained.
*/
public void setHeight(int Height) {
double ratio = (double) Height / (double) this.getHeight();
double dw = this.getWidth() * ratio;
double dh = this.getHeight() * ratio;
int outputWidth = (int) Math.round(dw);
int outputHeight = (int) Math.round(dh);
resize(outputWidth, outputHeight);
}
// **************************************************************************
// ** Resize (Overloaded Member)
// **************************************************************************
/**
* Used to resize an image. Does NOT automatically retain the original
* aspect ratio.
*/
public void resize(int Width, int Height) {
resize(Width, Height, false);
}
// **************************************************************************
// ** Resize
// **************************************************************************
/**
* Used to resize an image. Provides the option to maintain the original
* aspect ratio (relative to the output width).
*/
public void resize(int Width, int Height, boolean maintainRatio) {
// long startTime = getStartTime();
int outputWidth = Width;
int outputHeight = Height;
int width = this.getWidth();
int height = this.getHeight();
if (maintainRatio) {
double ratio = 0;
if (width > height) {
ratio = (double) Width / (double) width;
} else {
ratio = (double) Height / (double) height;
}
double dw = width * ratio;
double dh = height * ratio;
outputWidth = (int) Math.round(dw);
outputHeight = (int) Math.round(dh);
if (outputWidth > width || outputHeight > height) {
outputWidth = width;
outputHeight = height;
}
}
// Resize the image (create new buffered image)
java.awt.Image outputImage = bufferedImage.getScaledInstance(outputWidth, outputHeight,
java.awt.Image.SCALE_AREA_AVERAGING);
BufferedImage bi = new BufferedImage(outputWidth, outputHeight, 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(x0);
corners.add(y0);
corners.add(x1);
corners.add(y1);
corners.add(x2);
corners.add(y2);
corners.add(x3);
corners.add(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(0f);
corners.add(0f);
corners.add(w);
corners.add(0f);
corners.add(w);
corners.add(h);
corners.add(0f);
corners.add(h);
}
Object[] arr = corners.toArray();
float[] ret = new float[arr.length];
for (int i = 0; i < arr.length; i++) {
Float f = (Float) arr[i];
ret[i] = f.floatValue();
}
return ret;
}
// **************************************************************************
// ** Sharpen
// **************************************************************************
/** Used to sharpen the image using a 3x3 kernal. */
public void sharpen() {
int width = this.getWidth();
int height = this.getHeight();
// define kernal
Kernel kernel = new Kernel(3, 3, new float[] { 0.0f, -0.2f, 0.0f, -0.2f, 1.8f, -0.2f, 0.0f, -0.2f, 0.0f });
// apply convolution
BufferedImage out = new BufferedImage(width, height, getImageType());
BufferedImageOp op = new ConvolveOp(kernel);
out = op.filter(bufferedImage, out);
// replace 2 pixel border created via convolution
java.awt.Image overlay = out.getSubimage(2, 2, width - 4, height - 4);
Graphics2D g2d = bufferedImage.createGraphics();
g2d.drawImage(overlay, 2, 2, null);
g2d.dispose();
}
// **************************************************************************
// ** 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
// **************************************************************************
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 subset or crop an 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() {
return new Image(bufferedImage);
}
// **************************************************************************
// ** 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 < bufferedImage.getHeight(); y++) {
for (int x = 0; x < bufferedImage.getWidth(); x++) {
if (hasColor(bufferedImage.getRGB(x, y), r, g, b)) {
bottom = y;
break;
}
}
}
for (int y = bufferedImage.getHeight() - 1; y > -1; y--) {
for (int x = 0; x < bufferedImage.getWidth(); x++) {
if (hasColor(bufferedImage.getRGB(x, y), r, g, b)) {
top = y;
break;
}
}
}
for (int x = 0; x < bufferedImage.getWidth(); x++) {
for (int y = 0; y < bufferedImage.getHeight(); y++) {
if (hasColor(bufferedImage.getRGB(x, y), r, g, b)) {
right = x;
break;
}
}
}
for (int x = bufferedImage.getWidth() - 1; x > -1; x--) {
for (int y = 0; y < bufferedImage.getHeight(); y++) {
if (hasColor(bufferedImage.getRGB(x, y), r, g, b)) {
left = x;
break;
}
}
}
if (left == right || top == bottom) {
bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
} else {
bufferedImage = bufferedImage.getSubimage(left, top, right - left, bottom - top);
}
}
// **************************************************************************
// ** getBufferedImage
// **************************************************************************
/**
* Returns the java.awt.image.BufferedImage represented by the current
* image.
*/
public BufferedImage getBufferedImage() {
return bufferedImage;
}
// **************************************************************************
// ** getImage
// **************************************************************************
/** Returns a java.awt.Image copy of the current image. */
public java.awt.Image getImage() {
return getBufferedImage();
}
// **************************************************************************
// ** getImage
// **************************************************************************
/** Returns a java.awt.image.RenderedImage copy of the current image. */
public java.awt.image.RenderedImage getRenderedImage() {
return getBufferedImage();
}
// **************************************************************************
// ** getBufferedImage
// **************************************************************************
/** Used to retrieve a scaled copy of the current image. */
public BufferedImage getBufferedImage(int width, int height, boolean maintainRatio) {
Image image = new Image(getBufferedImage());
image.resize(width, height, maintainRatio);
return image.getBufferedImage();
}
// **************************************************************************
// ** getByteArray
// **************************************************************************
/**
* Returns the image as a jpeg byte array. Output quality is set using the
* setOutputQuality method.
*/
public byte[] getByteArray() {
return getByteArray("jpeg");
}
// **************************************************************************
// ** getByteArray
// **************************************************************************
/** Returns the image as a byte array. */
public byte[] getByteArray(String format) {
byte[] rgb = null;
format = format.toLowerCase();
if (format.startsWith("image/")) {
format = format.substring(format.indexOf("/") + 1);
}
try {
if (isJPEG(format)) {
rgb = getJPEGByteArray(outputQuality);
} else {
ByteArrayOutputStream bas = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, format.toLowerCase(), bas);
rgb = bas.toByteArray();
}
} catch (Exception e) {
}
return rgb;
}
// **************************************************************************
// ** saveAs
// **************************************************************************
/**
* Exports the image to a file. Output format is determined by the output
* file extension.
*/
public void saveAs(String PathToImageFile) {
saveAs(new java.io.File(PathToImageFile));
}
// **************************************************************************
// ** saveAs
// **************************************************************************
/**
* Exports the image to a file. Output format is determined by the output
* file extension.
*/
public void saveAs(java.io.File OutputFile) {
try {
// Create output directory
OutputFile.getParentFile().mkdirs();
// Write buffered image to disk
String FileExtension = getExtension(OutputFile.getName()).toLowerCase();
if (isJPEG(FileExtension)) {
FileOutputStream output = new FileOutputStream(OutputFile);
output.write(getJPEGByteArray(outputQuality));
output.close();
} else {
RenderedImage rendImage = bufferedImage;
if (isJPEG2000(FileExtension)) {
ImageIO.write(rendImage, "JPEG 2000", OutputFile);
} else {
ImageIO.write(rendImage, FileExtension, OutputFile);
}
rendImage = null;
}
// System.out.println("Output image is " + width + "x" + height +
// "...");
} catch (Exception e) {
// printError(e);
}
}
/*
* public void setCacheDirectory(java.io.File cacheDirectory){ try{ if
* (cacheDirectory.isFile()){ cacheDirectory =
* cacheDirectory.getParentFile(); } cacheDirectory.mkdirs();
* ImageIO.setUseCache(true); this.cacheDirectory = cacheDirectory; }
* catch(Exception e){ this.cacheDirectory = null; } }
*
* public java.io.File getCacheDirectory(){ return cacheDirectory; }
*/
// **************************************************************************
// ** setOutputQuality
// **************************************************************************
/**
* Used to set the output quality/compression ratio. Only applies when
* creating JPEG images. Applied only when writing the image to a file or
* byte array.
*/
public void setOutputQuality(double percentage) {
if (percentage > 1 && percentage <= 100)
percentage = percentage / 100;
float q = (float) percentage;
if (q == 1f && useSunCodec)
q = 1.2f;
if (q >= 0f && q <= 1.2f)
outputQuality = q;
}
// **************************************************************************
// ** isJPEG
// **************************************************************************
/** Used to determine whether to create a custom 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
// **************************************************************************
/** Used to determine whether to create a custom jpeg 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 == Transparency.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(".")) {
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. If the ARGB values match, this
* method will return true.
*/
@Override
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 < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
if (!image.getColor(i, j).equals(this.getColor(i, j))) {
return false;
}
}
}
return true;
}
}
}
return false;
}
// **************************************************************************
// ** 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<Integer, String> 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<Integer, Object> 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 < childNodes.getLength(); i++) {
getElementsByTagName(tagName, childNodes.item(i), nodes);
}
}
}
// **************************************************************************
// ** getAttributeValue (Copied from javaxt.xml.DOM)
// **************************************************************************
/**
* Used to return the value of a given node attribute. The search is case
* insensitive. If no match is found, returns an empty string.
*/
public static String getAttributeValue(NamedNodeMap attrCollection, String attrName) {
if (attrCollection != null) {
for (int i = 0; i < attrCollection.getLength(); i++) {
Node node = attrCollection.item(i);
if (node.getNodeName().equalsIgnoreCase(attrName)) {
return node.getNodeValue();
}
}
}
return "";
}
// ******************************************************************************
// ** MetadataParser Class
// ******************************************************************************
/**
* Used to decode EXIF and IPTC metadata. Adapted from 2 classes developed
* by Norman Walsh and released under the W3C open source license. The
* original exif classes can be found in the W3C Jigsaw project in the
* org.w3c.tools.jpeg package.
*
* @author Norman Walsh
* @copyright Copyright (c) 2003 Norman Walsh
******************************************************************************/
private class MetadataParser {
// Implementation notes:
// (1) Merged Version 1.1 of the "Exif.java" and "ExifData.java"
// classes.
// (2) Added new IPTC metadata parser.
// (3) All unsigned integers are treated as signed ints (should be
// longs).
// (4) Added logic to parse GPS Info using the GPS IFD offset value (tag
// 34853,
// hex 0x8825).
// (5) Added logic to parse an array of rational numbers (e.g. GPS
// metadata).
// (6) Improved performance in the parseExif() method by serializing
// only the
// first 8 characters into a string (vs the entire EXIF byte array).
// (7) TODO: Need to come up with a clever scheme to parse MakerNotes.
private final int bytesPerFormat[] = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
private final int NUM_FORMATS = 12;
private final int FMT_BYTE = 1;
private final int FMT_STRING = 2;
private final int FMT_USHORT = 3;
private final int FMT_ULONG = 4;
private final int FMT_URATIONAL = 5;
private final int FMT_SBYTE = 6;
// private final int FMT_UNDEFINED = 7;
private final int FMT_SSHORT = 8;
private final int FMT_SLONG = 9;
private final int FMT_SRATIONAL = 10;
// private final int FMT_SINGLE = 11;
// private final int FMT_DOUBLE = 12;
private byte[] data = null;
private boolean intelOrder = false;
private final int TAG_EXIF_OFFSET = 0x8769;
private final int TAG_INTEROP_OFFSET = 0xa005;
private final int TAG_GPS_OFFSET = 0x8825;
private final int TAG_USERCOMMENT = 0x9286;
private HashMap> 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 < components; i++) {
str.append(getRational(valueOffset + (8 * i)));
if (i < components - 1)
str.append(",");
}
str.append("]");
tags.put(tag, str.toString());
} else {
tags.put(tag, getRational(valueOffset));
}
break;
default: // including FMT_UNDEFINED
byte[] result = getUndefined(valueOffset, byteCount);
if (result != null)
tags.put(tag, result);
break;
}
}
}
}
// **************************************************************************
// ** getRational
// **************************************************************************
/**
* Returns a string representation of a rational number (numerator and
* denominator separated with a "/" character).
*/
private String getRational(int offset) {
int num = get32s(offset);
int den = get32s(offset + 4);
String result = "";
// This is a bit silly, I really ought to find a real GCD algorithm
if (num % 10 == 0 && den % 10 == 0) {
num = num / 10;
den = den / 10;
}
if (num % 5 == 0 && den % 5 == 0) {
num = num / 5;
den = den / 5;
}
if (num % 3 == 0 && den % 3 == 0) {
num = num / 3;
den = den / 3;
}
if (num % 2 == 0 && den % 2 == 0) {
num = num / 2;
den = den / 2;
}
if (den == 0) {
result = "0";
} else if (den == 1) {
result = "" + num; // "" + int sure looks ugly...
} else {
result = "" + num + "/" + den;
}
return result;
}
private int get16s(int offset) {
int hi, lo;
if (intelOrder) {
hi = data[offset + 1];
lo = data[offset];
} else {
hi = data[offset];
lo = data[offset + 1];
}
lo = lo & 0xFF;
hi = hi & 0xFF;
return (hi << 8) + lo;
}
private int get16u(int offset) {
int value = get16s(offset);
return value & 0xFFFF;
}
private int get32s(int offset) {
int n1, n2, n3, n4;
if (intelOrder) {
n1 = data[offset + 3] & 0xFF;
n2 = data[offset + 2] & 0xFF;
n3 = data[offset + 1] & 0xFF;
n4 = data[offset] & 0xFF;
} else {
n1 = data[offset] & 0xFF;
n2 = data[offset + 1] & 0xFF;
n3 = data[offset + 2] & 0xFF;
n4 = data[offset + 3] & 0xFF;
}
return (n1 << 24) + (n2 << 16) + (n3 << 8) + n4;
}
private int get32u(int offset) {
return get32s(offset); // Should probably return a long instead...
}
private byte[] getUndefined(int offset, int length) {
return java.util.Arrays.copyOfRange(data, offset, offset + length);
}
private String getString(int offset, int length) {
try {
return new String(data, offset, length, "UTF-8").trim();
} catch (Exception e) {
return null;
}
}
// **************************************************************************
// ** getDouble
// **************************************************************************
/**
* Used convert a byte into a double. Note that this method used to be
* called convertAnyValue().
*/
private double getDouble(int format, int offset) {
switch (format) {
case FMT_SBYTE:
return data[offset];
case FMT_BYTE:
int iValue = data[offset];
return iValue & 0xFF;
case FMT_USHORT:
return get16u(offset);
case FMT_ULONG:
return get32u(offset);
case FMT_URATIONAL:
case FMT_SRATIONAL:
int num = get32s(offset);
int den = get32s(offset + 4);
if (den == 0)
return 0;
else
return (double) num / (double) den;
case FMT_SSHORT:
return get16s(offset);
case FMT_SLONG:
return get32s(offset);
default:
return 0.0;
}
}
}
// ***************************************************************************
// ** Skew Class
// ***************************************************************************
/**
* Used to skew an image. Adapted from 2 image processing classes developed
* by Jerry Huxtable (http://www.jhlabs.com) and released under the Apache
* License, Version 2.0.
*
***************************************************************************/
private class Skew {
public final static int ZERO = 0;
public final static int CLAMP = 1;
public final static int WRAP = 2;
public final static int NEAREST_NEIGHBOUR = 0;
public final static int BILINEAR = 1;
protected int edgeAction = ZERO;
protected int interpolation = BILINEAR;
protected Rectangle transformedSpace;
protected Rectangle originalSpace;
private float x0, y0, x1, y1, x2, y2, x3, y3;
private float dx1, dy1, dx2, dy2, dx3, dy3;
private float A, B, C, D, E, F, G, H, I;
private BufferedImage src;
private BufferedImage dst;
public Skew(BufferedImage src) {
this.src = src;
this.dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
}
public Skew(javaxt.io.Image src) {
this(src.getBufferedImage());
}
public BufferedImage setCorners(float x0, float y0, float x1, float y1, float x2, float y2, float x3,
float y3) {
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.x3 = x3;
this.y3 = y3;
dx1 = x1 - x2;
dy1 = y1 - y2;
dx2 = x3 - x2;
dy2 = y3 - y2;
dx3 = x0 - x1 + x2 - x3;
dy3 = y0 - y1 + y2 - y3;
float a11, a12, a13, a21, a22, a23, a31, a32;
if (dx3 == 0 && dy3 == 0) {
a11 = x1 - x0;
a21 = x2 - x1;
a31 = x0;
a12 = y1 - y0;
a22 = y2 - y1;
a32 = y0;
a13 = a23 = 0;
} else {
a13 = (dx3 * dy2 - dx2 * dy3) / (dx1 * dy2 - dy1 * dx2);
a23 = (dx1 * dy3 - dy1 * dx3) / (dx1 * dy2 - dy1 * dx2);
a11 = x1 - x0 + a13 * x1;
a21 = x3 - x0 + a23 * x3;
a31 = x0;
a12 = y1 - y0 + a13 * y1;
a22 = y3 - y0 + a23 * y3;
a32 = y0;
}
A = a22 - a32 * a23;
B = a31 * a23 - a21;
C = a21 * a32 - a31 * a22;
D = a32 * a13 - a12;
E = a11 - a31 * a13;
F = a31 * a12 - a11 * a32;
G = a12 * a23 - a22 * a13;
H = a21 * a13 - a11 * a23;
I = a11 * a22 - a21 * a12;
return filter(src, dst);
}
protected void transformSpace(Rectangle rect) {
rect.x = (int) Math.min(Math.min(x0, x1), Math.min(x2, x3));
rect.y = (int) Math.min(Math.min(y0, y1), Math.min(y2, y3));
rect.width = (int) Math.max(Math.max(x0, x1), Math.max(x2, x3)) - rect.x;
rect.height = (int) Math.max(Math.max(y0, y1), Math.max(y2, y3)) - rect.y;
}
public float getOriginX() {
return x0 - (int) Math.min(Math.min(x0, x1), Math.min(x2, x3));
}
public float getOriginY() {
return y0 - (int) Math.min(Math.min(y0, y1), Math.min(y2, y3));
}
private BufferedImage filter(BufferedImage src, BufferedImage dst) {
int width = src.getWidth();
int height = src.getHeight();
// int type = src.getType();
// WritableRaster srcRaster = src.getRaster();
originalSpace = new Rectangle(0, 0, width, height);
transformedSpace = new Rectangle(0, 0, width, height);
transformSpace(transformedSpace);
if (dst == null) {
ColorModel dstCM = src.getColorModel();
dst = new BufferedImage(dstCM,
dstCM.createCompatibleWritableRaster(transformedSpace.width, transformedSpace.height),
dstCM.isAlphaPremultiplied(), null);
}
// WritableRaster dstRaster = dst.getRaster();
int[] inPixels = getRGB(src, 0, 0, width, height, null);
if (interpolation == NEAREST_NEIGHBOUR)
return filterPixelsNN(dst, width, height, inPixels, transformedSpace);
int srcWidth = width;
int srcHeight = height;
int srcWidth1 = width - 1;
int srcHeight1 = height - 1;
int outWidth = transformedSpace.width;
int outHeight = transformedSpace.height;
int outX, outY;
// int index = 0;
int[] outPixels = new int[outWidth];
outX = transformedSpace.x;
outY = transformedSpace.y;
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);
int srcX = (int) Math.floor(out[0]);
int srcY = (int) Math.floor(out[1]);
float xWeight = out[0] - srcX;
float yWeight = out[1] - srcY;
int nw, ne, sw, se;
if (srcX >= 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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy