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; } }