|
Sampled Static Visual Content
An Introduction with Examples in Java |
|
Prof. David Bernstein |
| Computer Science Department |
| bernstdh@jmu.edu |
Image image;
ResourceFinder finder;
String name;
// Get the file name
name = rootPaneContainer.getParameter("0");
if (name == null)
name = "/visual/statik/sampled/scribble.gif";
// Construct a ResourceFinder
finder = ResourceFinder.createInstance();
// Read the image
image = null;
try
{
is = finder.findInputStream(name);
if (is != null)
{
image = ImageIO.read(is);
is.close();
}
}
catch (IOException io)
{
// image will be null
}
package visual.statik.sampled;
import java.awt.*;
import javax.swing.*;
/**
* A concrete extension of a JComponent that illustrates
* the rendering of sampled static visual content
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class ImageCanvas extends JComponent
{
private Image image;
/**
* Explicit Value Constructor
*
* @param image The new Image
*/
public ImageCanvas(Image image)
{
this.image = image;
}
/**
* Render this ImageCanvas
*
* @param g The rendering engine to use
*/
public void paint(Graphics g)
{
Graphics2D g2;
// Cast the rendering engine appropriately
g2 = (Graphics2D)g;
// Render the image
g2.drawImage(image, // The Image to render
0, // The horizontal coordinate
0, // The vertical coordinate
null); // An ImageObserver
}
}
package visual.statik.sampled;
import java.awt.Image;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
import app.*;
import io.*;
/**
* An example that illustrates the rendering of
* sampled static visual content
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class ImageCanvasApp
extends AbstractMultimediaApp
{
/**
* Default Constructor
*
*/
public ImageCanvasApp()
{
super();
}
/**
* This method is called just before the main window
* is first made visible
*/
public void init()
{
Image image;
ImageCanvas canvas;
InputStream is;
JPanel contentPane;
ResourceFinder finder;
String name;
// Get the file name
name = rootPaneContainer.getParameter("0");
if (name == null)
name = "/visual/statik/sampled/scribble.gif";
// Construct a ResourceFinder
finder = ResourceFinder.createInstance();
// Read the image
image = null;
try
{
is = finder.findInputStream(name);
if (is != null)
{
image = ImageIO.read(is);
is.close();
}
}
catch (IOException io)
{
// image will be null
}
// Create the component that will render the image
canvas = new ImageCanvas(image);
canvas.setBounds(0,
0,
image.getWidth(null),
image.getHeight(null));
// Add the ImageCanvas to the main window
contentPane = (JPanel)rootPaneContainer.getContentPane();
contentPane.add(canvas);
}
}
BufferedImage Objects
/**
* Copy a BufferedImage into a compatible BufferedImage
*
* @param src The source image
* @param dst An empty image in which to store the result
*/
private void copy(BufferedImage src, BufferedImage dst)
{
ColorModel dstColorModel, srcColorModel;
int dstRGB, height, srcRGB, width;
int[] dstColor, srcColor;
Raster srcRaster;
width = src.getWidth();
height = src.getHeight();
srcColorModel = src.getColorModel();
srcRaster = src.getRaster();
srcColor = new int[4];
dstColorModel = dst.getColorModel();
for (int x=0; x<width; x++)
{
for (int y=0; y<height; y++)
{
srcRGB = src.getRGB(x, y);
srcColorModel.getComponents(srcRGB,srcColor,0);
dstRGB = dstColorModel.getDataElement(srcColor,0);
dst.setRGB(x, y, dstRGB);
}
}
}
BufferedImage Objects /**
* Create a BufferedImage from an Image
*
* @param image The original Image
* @param channels 3 for RGB; 4 for ARGB
* @return The BufferedImage
*/
public BufferedImage createBufferedImage(Image image,
int channels)
{
int type;
if (channels == 3) type = BufferedImage.TYPE_INT_RGB;
else type = BufferedImage.TYPE_INT_ARGB;
BufferedImage bi;
bi = null;
bi = new BufferedImage(image.getWidth(null),
image.getHeight(null),
type);
Graphics2D g2;
g2 = bi.createGraphics();
g2.drawImage(image, null, null);
return bi;
}
BufferedImage Objects (cont.) /**
* Create a BufferedImage from a file/resource
* containing an Image
*
* @param name The name of the file/resource
* @param channels 3 for RGB; 4 for ARGB
* @return The BufferedImage
*/
public BufferedImage createBufferedImage(String name,
int channels)
{
BufferedImage image, result;
InputStream is;
int imageType;
image = null;
is = finder.findInputStream(name);
if (is != null)
{
try
{
image = ImageIO.read(is);
is.close();
}
catch (IOException io)
{
image = null;
}
}
// Modify the type, if necessary
result = image;
if (image != null)
{
imageType = image.getType();
if (((channels == 3) &&
(imageType != BufferedImage.TYPE_INT_RGB)) ||
((channels == 4) &&
(imageType != BufferedImage.TYPE_INT_ARGB)) )
{
result = createBufferedImage(image, channels);
}
}
return result;
}
IdentityOp
/**
* Creates a zeroed destination image with the correct size
* and number of bands (required by BufferedImageOp)
*
* @param src The source image
* @param dstCM The ColorModel to be used in the created image
*/
public BufferedImage createCompatibleDestImage(
BufferedImage src,
ColorModel dstCM)
{
BufferedImage dst;
int height, width;
WritableRaster raster;
if (dstCM == null) dstCM = src.getColorModel();
height = src.getHeight();
width = src.getWidth();
raster = dstCM.createCompatibleWritableRaster(width,
height);
dst = new BufferedImage(dstCM, raster,
dstCM.isAlphaPremultiplied(),
null);
return dst;
}
/**
* In general, performs a single-input/single-output operation on a
* BufferedImage (required by BufferedImageOp).
*
* If the destination image is null, a BufferedImage with an
* appropriate ColorModel is created.
*
* In this case, this method simply "returns" a (copy of) the
* source.
*
* @param src The source image
* @param dst An empty image in which to srote the result (or null)
*/
public BufferedImage filter(BufferedImage src,
BufferedImage dst)
{
// Construct the destination image if one isn't provided
if (dst == null)
{
dst = createCompatibleDestImage(src,
src.getColorModel());
}
// Copy the source to the destination
copy(src, dst);
// Return the destination (in case it is new)
return dst;
}
/**
* Returns the bounding box of the filtered destination image
* (required by BufferedImageOp)
*
* @param src The source image
*/
public Rectangle2D getBounds2D(BufferedImage src)
{
Raster raster;
raster = src.getRaster();
return raster.getBounds();
}
/**
* Returns the location of the corresponding destination point
* given a point in the source image (required by BufferedImageOp).
*
* If dstPt is specified, it is used to hold the return value.
*
* @param srcPt The point in the source image
* @param dstPt The point in the destination image
*/
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt)
{
if (dstPt == null) dstPt = (Point2D)srcPt.clone();
dstPt.setLocation(srcPt);
return dstPt;
}
/**
* Return the rendering hints for this operation
* (required by BufferedImageOp).
*
* In this case, this method always returns null.
*/
public RenderingHints getRenderingHints()
{
return null;
}
GrayExceptOp
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import math.*;
/**
* A BufferedImageOp that returns a gray-scale version
* with one color (in a particular area) left unchanged
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class GrayExceptOp extends IdentityOp
{
private int[] highlightColor;
}
GrayExceptOp (cont.)
GrayExceptOp.areSimilar()
Metrics
package math;
/**
* A Metric is a function that satisfies the following properties
* for all a, b, c:
*
* distance(a,b) >= 0
* distance(a,b) == 0 iff a == b
* distance(a,b) == distance(b,a)
* distance(a,b) <= distance(a,c) + distance(b,c)
*
* (The last of these properties is called the triangle inequality.)
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public interface Metric
{
/**
* Calculate the distance between two n-dimensional points
*
* @param a One n-dimensional point
* @param b Another n-dimensional point
* @return The distance
*/
public abstract double distance(double[] a, double[] b);
}
package math;
/**
* The rectilinear metric (i.e., the sum of the absolute values of the
* differences between the elements). This is sometimes also
* called the Manhattan metric (because it is the distance you have to walk
* between two points in a city that is layed out on a grid).
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class RectilinearMetric
implements Metric
{
/**
* Calculate the distance between two n-dimensional points
* (required by Metric)
*
* Note: For simplicity, this method does not confirm that the
* two arrays are the same size. It uses the smaller size.
*
* @param a One n-dimensional point
* @param b Another n-dimensional point
* @return The distance
*/
public double distance(double[] a, double[] b)
{
double result;
int n;
result = 0.0;
n = Math.min(a.length, b.length);
for (int i=0; i<n; i++)
{
result += Math.abs(a[i]-b[i]);
}
return result;
}
}
GrayExceptOp.areSimilar() (cont.)
/**
* Determines if two colors are similar
*
* Note: This method only uses the red, green, and
* blue components. It does not use the alpha component.
*
* @param a The components of one color
* @param b The components of the other color
*/
private boolean areSimilar(int[] a, int[] b)
{
boolean result;
double distance;
for (int i=0; i<3; i++)
{
x[i] = a[i];
y[i] = b[i];
}
result = false;
distance = metric.distance(x, y);
if (distance <= TOLERANCE) result = true;
return result;
}
GrayExceptOp (cont.)
GrayExceptOp (cont.)
/**
* Perform the filtering operation
*
* @param source The source image
* @param destination An empty image in which to srote the result (or null)
*/
public BufferedImage filter(BufferedImage src,
BufferedImage dest)
{
ColorModel destColorModel, srcColorModel;
int grayRGB, highlightRGB;
int srcRGB, srcHeight, srcWidth;
int[] gray, srcColor;
Raster srcRaster;
srcWidth = src.getWidth();
srcHeight = src.getHeight();
srcColorModel = src.getColorModel();
srcRaster = src.getRaster();
srcColor = new int[4];
gray = new int[4];
if (dest == null)
dest = createCompatibleDestImage(src,
srcColorModel);
destColorModel = dest.getColorModel();
highlightRGB = destColorModel.getDataElement(
highlightColor,
0);
for (int x=0; x<srcWidth; x++)
{
for (int y=0; y<srcHeight; y++)
{
srcRGB = src.getRGB(x, y);
srcColorModel.getComponents(srcRGB, srcColor, 0);
if (areSimilar(srcColor, highlightColor))
{
dest.setRGB(x, y, highlightRGB);
}
else
{
gray[0]=(srcColor[0]+srcColor[1]+srcColor[2])/3;
gray[1]=gray[0];
gray[2]=gray[0];
grayRGB=destColorModel.getDataElement(gray,0);
dest.setRGB(x, y, grayRGB);
}
}
}
return dest;
}
The Kernel as a Grid/Matrix
Applying the Convolution to One Pixel
\( d_{i,j} = \sum_{r=-1}^{1} \sum_{c=-1}^{1} s_{i+r, j+c} k_{r,c} \)
Identity Kernel
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 0 | 0 | 0 |
Blurring Kernel
| 1/9 | 1/9 | 1/9 |
| 1/9 | 1/9 | 1/9 |
| 1/9 | 1/9 | 1/9 |
BufferedImageOp objects
package visual.statik.sampled;
import java.awt.color.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
/**
* A class that can be used to construct BufferedImageOp objects that
* can then be used to operate on static sampled visual content
*
* This (partial) implementation is difficult to maintain because it
* contains a lot of repetitive code
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class RepetitiveBufferedImageOpFactory
{
private Hashtable<Integer,ConvolveOp> blurOps, edgeOps;
private static RepetitiveBufferedImageOpFactory instance =
new RepetitiveBufferedImageOpFactory();
/**
* Default Constructor
*/
private RepetitiveBufferedImageOpFactory()
{
blurOps = new Hashtable<Integer,ConvolveOp>();
edgeOps = new Hashtable<Integer,ConvolveOp>();
}
/**
* Create a blur operation
*
* @param size The size of the convolution kernel
*/
public ConvolveOp createBlurOp(int size)
{
ConvolveOp op;
float denom;
float[] kernelValues;
Integer key;
key = new Integer(size);
op = blurOps.get(key);
if (op == null)
{
kernelValues = getBlurValues(size);
op = new ConvolveOp(new Kernel(size,size,kernelValues),
ConvolveOp.EDGE_NO_OP,
null);
blurOps.put(key, op);
}
return op;
}
/**
* Create an edge detection operation
*
* @param size The size of the convolution kernel
*/
public ConvolveOp createEdgeDetectionOp(int size)
{
ConvolveOp op;
float denom;
float[] kernelValues;
int center;
Integer key;
key = new Integer(size);
op = edgeOps.get(key);
if (op == null)
{
kernelValues = getEdgeValues(size);
op = new ConvolveOp(new Kernel(size,size,kernelValues),
ConvolveOp.EDGE_NO_OP,
null);
edgeOps.put(key, op);
}
return op;
}
/**
* Create a RepetitiveBufferedImageOpFactory object
*/
public static RepetitiveBufferedImageOpFactory createFactory()
{
return instance;
}
/**
* Get the kernel values for a blurring convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private float[] getBlurValues(int size)
{
float denom;
float[] result;
denom = (float)(size*size);
result = new float[size*size];
for (int row=0; row<size; row++)
for (int col=0; col<size; col++)
result[indexFor(row,col,size)] = 1.0f/denom;
return result;
}
/**
* Get the kernel values for an edge detecting convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private float[] getEdgeValues(int size)
{
float[] result;
int center;
center = size/2;
result = new float[size*size];
result[indexFor(center-1, center , size)] = -1.0f;
result[indexFor(center , center-1, size)] = -1.0f;
result[indexFor(center , center , size)] = 4.0f;
result[indexFor(center , center+1, size)] = -1.0f;
result[indexFor(center+1, center , size)] = -1.0f;
return result;
}
/**
* Convert row and column indexes (i.e., matrix indices)
* into a linear index (i.e., vector index)
*
* @param row The row index
* @param col The column index
* @param size The size of the square matrix
*/
private int indexFor(int row, int col, int size)
{
return row*size + col;
}
}
/**
* Create a ConvolveOp
*
* @param type The type of ConvolveOp
* @param size The size of the kernel
*/
private ConvolveOp createOp(Convolutions type, int size)
{
ConvolveOp op;
Hashtable<Integer, ConvolveOp> pool;
Integer key;
key = new Integer(size);
pool = convolutionPools.get(type);
op = pool.get(key);
if (op == null)
{
op = new ConvolveOp(new Kernel(size,size,
type.getKernelValues(size)),
ConvolveOp.EDGE_NO_OP,
null);
pool.put(key, op);
}
return op;
}
package visual.statik.sampled;
/**
* An enumeration of the different convolutions
* that are supported in the BufferedImageOpFactory
*
* This (partial) implementation is difficult to maintain
* because each time a value is added the switch
* statement must be changed.
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
enum UnmaintainableConvolutions
{
BLUR,
EDGE;
/**
* Get the kernel values
*
* @param size The size of the convolution kernel
*/
float[] getKernelValues(int size)
{
switch (this)
{
case BLUR: return getBlurValues(size);
case EDGE: return getEdgeValues(size);
default: return getIdentityValues(size);
}
}
/**
* Get the kernel values for a blurring convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private static float[] getBlurValues(int size)
{
float denom;
float[] result;
denom = (float)(size*size);
result = new float[size*size];
for (int row=0; row<size; row++)
for (int col=0; col<size; col++)
result[indexFor(row,col,size)] = 1.0f/denom;
return result;
}
/**
* Get the kernel values for an edge detecting convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private static float[] getEdgeValues(int size)
{
float[] result;
int center;
center = size/2;
result = new float[size*size];
result[indexFor(center-1, center , size)] = -1.0f;
result[indexFor(center , center-1, size)] = -1.0f;
result[indexFor(center , center , size)] = 4.0f;
result[indexFor(center , center+1, size)] = -1.0f;
result[indexFor(center+1, center , size)] = -1.0f;
return result;
}
/**
* Get the kernel values for an identity convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private static float[] getIdentityValues(int size)
{
float[] result;
int center;
center = size/2;
result = new float[size*size];
result[indexFor(center,center,size)] = 1.0f;
return result;
}
/**
* Convert row and column indexes (i.e., matrix indices)
* into a linear index (i.e., vector index)
*
* @param row The row index
* @param col The column index
* @param size The size of the square matrix
*/
private static int indexFor(int row, int col, int size)
{
return row*size + col;
}
}
package visual.statik.sampled;
/**
* An enumeration of the different convolutions
* that are supported in the BufferedImageOpFactory
*
* Note that this class has package visibility because it
* should only be used by classes in this package.
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
enum Convolutions
{
BLUR {float[] getKernelValues(int size)
{
return getBlurValues(size);
}
},
EDGE {float[] getKernelValues(int size)
{
return getEdgeValues(size);
}
},
EMBOSS {float[] getKernelValues(int size)
{
return getEmbossValues(size);
}
},
IDENTITY {float[] getKernelValues(int size)
{
return getIdentityValues(size);
}
},
SHARPEN {float[] getKernelValues(int size)
{
return getSharpenValues(size);
}
};
/**
* Note that this method has package visibility
*/
abstract float[] getKernelValues(int size);
/**
* Get the kernel values for a blurring convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private static float[] getBlurValues(int size)
{
float denom;
float[] result;
denom = (float)(size*size);
result = new float[size*size];
for (int row=0; row<size; row++)
for (int col=0; col<size; col++)
result[indexFor(row,col,size)] = 1.0f/denom;
return result;
}
/**
* Get the kernel values for an edge detecting convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private static float[] getEdgeValues(int size)
{
float[] result;
int center;
center = size/2;
result = new float[size*size];
result[indexFor(center-1, center , size)] = -1.0f;
result[indexFor(center , center-1, size)] = -1.0f;
result[indexFor(center , center , size)] = 4.0f;
result[indexFor(center , center+1, size)] = -1.0f;
result[indexFor(center+1, center , size)] = -1.0f;
return result;
}
/**
* Get the kernel values for an embossing convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private static float[] getEmbossValues(int size)
{
float[] result;
int center;
center = size/2;
result = new float[size*size];
result[indexFor(center-1, center-1, size)] = -2.0f;
result[indexFor(center , center , size)] = 1.0f;
result[indexFor(center+1, center+1, size)] = 2.0f;
return result;
}
/**
* Get the kernel values for an identity convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private static float[] getIdentityValues(int size)
{
float[] result;
int center;
center = size/2;
result = new float[size*size];
result[indexFor(center,center,size)] = 1.0f;
return result;
}
/**
* Get the kernel values for an sharpening convolution
*
* @param size The size of the kernel
* @return The array of kernel values
*/
private static float[] getSharpenValues(int size)
{
float[] result;
int center;
center = size/2;
result = getEdgeValues(size);
result[indexFor(center , center , size)] += 1.0f;
return result;
}
/**
* Convert row and column indexes (i.e., matrix indices)
* into a linear index (i.e., vector index)
*
* @param row The row index
* @param col The column index
* @param size The size of the square matrix
*/
private static int indexFor(int row, int col, int size)
{
return row*size + col;
}
}
package visual.statik.sampled;
import java.awt.*;
import java.awt.color.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
/**
* A class that can be used to construct BufferedImageOp objects that
* can then be used to operate on static sampled visual content
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class BufferedImageOpFactory
{
private ColorConvertOp grayOp;
private Hashtable<Convolutions,
Hashtable<Integer, ConvolveOp>> convolutionPools;
private Hashtable<Integer, GrayExceptOp> grayExceptPool;
private LookupOp negativeOp, nightVisionOp;
private RescaleOp brightenOp, darkenOp, metalOp;
private static BufferedImageOpFactory instance =
new BufferedImageOpFactory();
/**
* Default Constructor
*/
private BufferedImageOpFactory()
{
Hashtable<Integer, ConvolveOp> pool;
// Initialize the pool of ConvolveOp objects
convolutionPools = new Hashtable<Convolutions,
Hashtable<Integer, ConvolveOp>>();
for (Convolutions type : Convolutions.values())
{
pool = new Hashtable<Integer, ConvolveOp>();
convolutionPools.put(type, pool);
}
// Initialize the pool of grayExceptOp objects
grayExceptPool = new Hashtable<Integer, GrayExceptOp>();
}
/**
* Create a BufferedImageOpFactory object
*/
public static BufferedImageOpFactory createFactory()
{
return instance;
}
/**
* Create a ConvolveOp
*
* @param type The type of ConvolveOp
* @param size The size of the kernel
*/
private ConvolveOp createOp(Convolutions type, int size)
{
ConvolveOp op;
Hashtable<Integer, ConvolveOp> pool;
Integer key;
key = new Integer(size);
pool = convolutionPools.get(type);
op = pool.get(key);
if (op == null)
{
op = new ConvolveOp(new Kernel(size,size,
type.getKernelValues(size)),
ConvolveOp.EDGE_NO_OP,
null);
pool.put(key, op);
}
return op;
}
/**
* Create a blur operation
*
* @param size The size of the convolution kernel
*/
public ConvolveOp createBlurOp(int size)
{
return createOp(Convolutions.BLUR, size);
}
/**
* Create a brighten operation
*/
public RescaleOp createBrightenOp()
{
if (brightenOp == null)
{
brightenOp = new RescaleOp(1.5f, 0.0f, null);
}
return brightenOp;
}
/**
* Create a darken operation
*/
public RescaleOp createDarkenOp()
{
if (darkenOp == null)
{
darkenOp = new RescaleOp(0.5f, 0.0f, null);
}
return darkenOp;
}
/**
* Create an edge detection operation
*
* @param size The size of the convolution kernel
*/
public ConvolveOp createEdgeDetectionOp(int size)
{
return createOp(Convolutions.EDGE, size);
}
/**
* Create an embossing operation
*
* @param size The size of the convolution kernel
*/
public ConvolveOp createEmbossOp(int size)
{
return createOp(Convolutions.EMBOSS, size);
}
/**
* Create an operation that converts to a gray colorspace
*/
public ColorConvertOp createGrayOp()
{
if (grayOp == null)
{
ColorConvertOp op;
ColorSpace cs;
cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
op = new ColorConvertOp(cs, null);
grayOp = op;
}
return grayOp;
}
/**
* Create an operation that converts "all colors but one"
* to gray
*
* Note: This method actually leaves all colors that are
* close to the specified color
*/
public GrayExceptOp createGrayExceptOp(int r, int g, int b)
{
Color color;
GrayExceptOp op;
Integer key;
color = new Color(r,g,b);
key = new Integer(color.getRGB());
op = grayExceptPool.get(key);
if (op == null)
{
op = new GrayExceptOp(r, g, b);
}
grayExceptPool.put(key, op);
return op;
}
/**
* Create an operation that does not change the
* image (i.e., an identity)
*
* @param size The size of the convolution kernel
*/
public ConvolveOp createIdentityOp(int size)
{
return createOp(Convolutions.IDENTITY, size);
}
/**
* Create a "metal" operation
*/
public RescaleOp createMetalOp()
{
if (metalOp == null)
{
metalOp = new RescaleOp(1.0f, 128.0f, null);
}
return metalOp;
}
/**
* Create a photo-negative operation
*/
public LookupOp createNegativeOp()
{
if (negativeOp == null)
{
// Using a look-up operation to create a
// "color negative"
LookupTable lookupTable;
short[] lookup;
lookup = new short[256];
for (int i=0; i<lookup.length; i++)
{
lookup[i] = (short)(255 - i);
}
lookupTable = new ShortLookupTable(0, lookup);
negativeOp = new LookupOp(lookupTable, null);
}
return negativeOp;
}
/**
* Create a night-vision operation (i.e., an operation that
* makes eveything appear green)
*/
public LookupOp createNightVisionOp()
{
if (nightVisionOp == null)
{
// Using a look-up operation to create a
// "night vision" effect
LookupTable lookupTable;
short[] leave, remove;
short[][] lookupMatrix;
leave = new short[256];
remove = new short[256];
for (int i=0; i < leave.length; i++) {
leave[i] = (short)(i);
remove[i] = (short)(0);
}
lookupMatrix = new short[3][];
lookupMatrix[0] = remove;
lookupMatrix[1] = leave;
lookupMatrix[2] = remove;
lookupTable = new ShortLookupTable(0, lookupMatrix);
nightVisionOp = new LookupOp(lookupTable, null);
}
return nightVisionOp;
}
/**
* Create a scaling operation
*
* @param xScale The horizontal scaling factor
* @param yScale The vertical scaling factor
*/
public AffineTransformOp createScaleOp(double xScale, double yScale)
{
// We could use an object pool in which
// the AffineTransform is the key
AffineTransform at;
AffineTransformOp op;
at = AffineTransform.getScaleInstance(xScale, yScale);
op = new AffineTransformOp(
at,
AffineTransformOp.TYPE_BILINEAR);
return op;
}
/**
* Create a sharpen operation
*
* @param size The size of the convolution kernel
*/
public ConvolveOp createSharpenOp(int size)
{
return createOp(Convolutions.SHARPEN, size);
}
}
AffineTransform rotate;
rotate = AffineTransform.getRotateInstance(
theta,
before.getWidth() /2.0,
before.getHeight()/2.0);
RenderingHints hints;
// Value can be BILINEAR or NEAREST_NEIGHBOR
hints = new RenderingHints(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR
);
AffineTransformOp op;
op = new AffineTransformOp(rotate, hints);
BufferedImage after;
after = op.filter(before, null);
// Using a look-up operation to create a
// "color negative"
LookupTable lookupTable;
short[] lookup;
lookup = new short[256];
for (int i=0; i<lookup.length; i++)
{
lookup[i] = (short)(255 - i);
}
lookupTable = new ShortLookupTable(0, lookup);
negativeOp = new LookupOp(lookupTable, null);
// Using a look-up operation to create a
// "night vision" effect
LookupTable lookupTable;
short[] leave, remove;
short[][] lookupMatrix;
leave = new short[256];
remove = new short[256];
for (int i=0; i < leave.length; i++) {
leave[i] = (short)(i);
remove[i] = (short)(0);
}
lookupMatrix = new short[3][];
lookupMatrix[0] = remove;
lookupMatrix[1] = leave;
lookupMatrix[2] = remove;
lookupTable = new ShortLookupTable(0, lookupMatrix);
nightVisionOp = new LookupOp(lookupTable, null);
Image classBufferedImage class -- will lead to
code duplication when we consider described content
Image interface
package visual.statik.sampled;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
/**
* A BufferedImage that knows how to render itself
* (i.e., a BufferedImage along with all of
* the attributes necessary for rendering)
*
* @author Prof. David Bernstein, James Madison Univeristy
* @version 1.0
*/
public class Content
extends visual.statik.AbstractTransformableContent
implements TransformableContent
{
private boolean refiltered;
private BufferedImageOp imageOp;
private Composite composite;
private BufferedImage originalImage, transformedImage;
private Rectangle2D.Double originalBounds,transformedBounds;
private static final double DEFAULT_X = 0.0;
private static final double DEFAULT_Y = 0.0;
/**
* Default Constructor
*/
public Content()
{
this(null, DEFAULT_X, DEFAULT_Y);
}
/**
* Explicit Value Constructor
*
* @param image The BufferedImage
* @param x The coordinate of the left edge
* @param y The coordinate of the top edge
*/
public Content(BufferedImage image,
double x, double y)
{
this(image, x, y, true);
}
}
/**
* Set the BufferedImageOp to use when transforming
* the Image
*
* @param op The BufferedImageOp
*/
public void setBufferedImageOp(BufferedImageOp op)
{
imageOp = op;
refiltered = true;
}
/**
* Set the transparency/Composite
*
* @param c The Composite
*/
public void setComposite(Composite c)
{
composite = c;;
}
/**
* Set the location
*
* Note: This method overrides the version in the parent because
* the translation of sampled content does not require
* transformation
*
* @param x The x position
* @param y The y position
*/
public void setLocation(double x, double y)
{
if (!rotatable)
{
this.x = x;
this.y = y;
transformedBounds.x = this.x;
transformedBounds.y = this.y;
}
else
{
super.setLocation(x, y);
}
}
/**
* Set the rotation
*
* Note: This method overrides the version in the parent because
* efficiency can be improved when rotations are
* prevented
*
* @param angle The rotation angle
* @param x The x-coordinate of the point to rotate around
* @param y The y-coordinate of the point to rotate around
*/
public void setRotation(double angle, double x, double y)
{
if (rotatable) super.setRotation(angle, x, y);
}
/**
* Create the transformed version of the shape
*/
private void createTransformedContent()
{
createTransformedContent(getAffineTransform());
}
/**
* Create a transformed version of the BufferedImage
*
* @param at The AffineTransform to use
*/
private void createTransformedContent(AffineTransform at)
{
AffineTransformOp op;
op = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
createTransformedContent(op);
}
/**
* Create a transformed version of the BufferedImage
*
* @param op The BufferedImageOp to use
*/
private void createTransformedContent(BufferedImageOp op)
{
BufferedImage tempImage;
Rectangle2D temp;
try
{
// Apply the filter
tempImage = originalImage;
if (imageOp != null)
{
tempImage = imageOp.filter(originalImage, null);
}
// Create the transformed version
transformedImage = op.filter(tempImage, null);
temp = op.getBounds2D(originalImage);
transformedBounds.x = temp.getX();
transformedBounds.y = temp.getY();
transformedBounds.width = temp.getWidth();
transformedBounds.height = temp.getHeight();
if (!rotatable)
{
transformedBounds.x += x;
transformedBounds.y += y;
}
setTransformationRequired(false);
}
catch (RasterFormatException rfe)
{
// Unable to transform
transformedImage = null;
}
}
/**
* Render this sampled visual content
* (required by Content)
*
* @param g The rendering engine to use
*/
public void render(Graphics g)
{
Composite oldComposite;
Graphics2D g2;
g2 = (Graphics2D)g;
if (originalImage != null)
{
oldComposite = g2.getComposite();
if (composite != null) g2.setComposite(composite);
// Transform the Image (if necessary)
if (isTransformationRequired())
{
createTransformedContent();
}
// Render the image
if (!rotatable)
g2.drawImage(transformedImage,(int)x,(int)y,null);
else
g2.drawImage(transformedImage,0,0,null);
g2.setComposite(oldComposite);
}
}
package visual.statik.sampled;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.Arrays;
import io.ResourceFinder;
/**
* A utility class for constructing/creating
* visual.statik.sampled.Content objects
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class ContentFactory
{
private ImageFactory imageFactory;
private static final int DEFAULT_CHANNELS = 3;
private static final boolean ROTATABLE = true;
/**
* Default Constructor
*/
public ContentFactory()
{
super();
imageFactory = new ImageFactory();
}
/**
* Create a Content from a BufferedImage
*
* @param image The BufferedImage
* @param rotatable false to prevent rotations (and improve performance)
* @return The Content
*/
public Content createContent(BufferedImage image,
boolean rotatable)
{
return new Content(image, 0, 0, rotatable);
}
/**
* Create a Content from a BufferedImage
*
* @param image The BufferedImage
* @return The Content
*/
public Content createContent(BufferedImage image)
{
return new Content(image, 0, 0, ROTATABLE);
}
/**
* Create a Content from an Image
*
* @param image The original Image
* @param channels 3 for RGB; 4 for ARGB
* @param rotatable false to prevent rotations (and improve performance)
* @return The Content
*/
public Content createContent(Image image,
int channels,
boolean rotatable)
{
BufferedImage bi;
bi = imageFactory.createBufferedImage(image, channels);
return createContent(bi, rotatable);
}
/**
* Create a Content from an Image
*
* @param image The original Image
* @param channels 3 for RGB; 4 for ARGB
* @return The Content
*/
public Content createContent(Image image,
int channels)
{
return createContent(image, channels, ROTATABLE);
}
/**
* Create a Content (with the default number
* of channels) from an Image
*
* @param image The original Image
* @param rotatable false to prevent rotations (and improve performance)
* @return The Content
*/
public Content createContent(Image image,
boolean rotatable)
{
return createContent(image, DEFAULT_CHANNELS,
rotatable);
}
/**
* Create a Content (with the default number
* of channels) from an Image
*
* @param image The original Image
* @return The Content
*/
public Content createContent(Image image)
{
return createContent(image,
DEFAULT_CHANNELS,
ROTATABLE);
}
/**
* Create a Content from a file/resource
* containing an Image
*
* @param name The name of the file/resource
* @param channels 3 for RGB; 4 for ARGB
* @param rotatable false to prevent rotations (and improve performance)
* @return The Content
*/
public Content createContent(String name,
int channels,
boolean rotatable)
{
BufferedImage bi;
bi = imageFactory.createBufferedImage(name, channels);
return createContent(bi, rotatable);
}
/**
* Create a Content from a file/resource
* containing an Image
*
* @param name The name of the file
* @param channels 3 for RGB; 4 for ARGB
* @return The Content
*/
public Content createContent(String name,
int channels)
{
return createContent(name,channels, ROTATABLE);
}
/**
* Create a Content (with the default number of
* channels) from a file containing an Image
*
* @param name The name of the file
* @param rotatable false to prevent rotations (and improve performance)
* @return The Content
*/
public Content createContent(String name,
boolean rotatable)
{
return createContent(name,
DEFAULT_CHANNELS,
rotatable);
}
/**
* Create a Content (with the default number of
* channels) from a file/resource containing an Image
*
* @param name The name of the file
* @return The Content
*/
public Content createContent(String name)
{
return createContent(name,
DEFAULT_CHANNELS,
ROTATABLE);
}
/**
* Create an array of Content objects
* from an array of images in files/resources
*
* @param name The names of the file/resource
* @param channels 3 for RGB, 4 for ARGB
* @return The Content objects
*/
public Content[] createContents(String[] names,
int channels)
{
BufferedImage[] images;
Content[] result;
int n;
n = names.length;
images = imageFactory.createBufferedImages(names,
channels);
result = new Content[n];
for (int i=0; i<n; i++)
{
result[i] = createContent(images[i], ROTATABLE);
}
return result;
}
/**
* Create an array of Content objects from a
* group of files/resources containing images
*
* @param path The path to the directory containing the images
* @param filter The FilenameFilter to use
* @param channels 3 for RGB; 4 for ARGB
* @return The array of Content objects
*/
public Content[] createContents(String path,
FilenameFilter filter,
int channels)
{
File dir;
int length;
Content[] rbi;
String[] fileNames;
dir = new File(path);
fileNames = dir.list(filter);
Arrays.sort(fileNames);
length = fileNames.length;
rbi = new Content[length];
for (int i=0; i < length; i++)
{
rbi[i] = createContent(fileNames[i],
channels,
ROTATABLE);
}
return rbi;
}
/**
* Create an array Content (with the default
* number of channels) from a group of files containing images
*
* @param path The path to the directory containing the images
* @param filter The FilenameFilter to use
* @return The array of Content objects
*/
public Content[] createContents(String path,
FilenameFilter filter)
{
return createContents(path, filter, DEFAULT_CHANNELS);
}
/**
* Create an array of Content objects
* from an "array" of images in a file
*
* @param name The name of the file/resource
* @param n The number of images
* @param channels 3 for RGB, 4 for ARGB
* @return The Content objects or null if an Exception was thrown
*/
public Content[] createContents(String name,
int n,
int channels)
{
BufferedImage[] images;
Content[] result;
images = imageFactory.createBufferedImages(name, n,
channels);
result = new Content[n];
for (int i=0; i<n; i++)
{
result[i] = createContent(images[i], ROTATABLE);
}
return result;
}
/**
* Create an array of Content objects
* from a table-oriented Image in a file
*
* @param name The name of the file/resource
* @param rows The number of rows
* @param columns The number of columns
* @param channels 3 for RGB, 4 for ARGB
* @return The Content objects or null if an Exception was thrown
*/
public Content[][] createContents(String name,
int rows,
int columns,
int channels)
{
BufferedImage[][] images;
Content[][] result;
images = imageFactory.createBufferedImages(name,
rows,
columns,
channels);
result = new Content[rows][columns];
for (int r=0; r<rows; r++)
{
for (int c=0; c<columns; c++)
{
result[r][c] = createContent(images[r][c],
ROTATABLE);
}
}
return result;
}
}