001: /*
002: * $RCSfile: DilateBinaryOpImage.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.2 $
009: * $Date: 2005/12/08 20:27:58 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.opimage;
013:
014: import java.awt.Rectangle;
015: import java.awt.RenderingHints;
016: import java.awt.image.DataBuffer;
017: import java.awt.image.DataBufferByte;
018: import java.awt.image.DataBufferInt;
019: import java.awt.image.DataBufferUShort;
020: import java.awt.image.MultiPixelPackedSampleModel;
021: import java.awt.image.SampleModel;
022: import java.awt.image.Raster;
023: import java.awt.image.RenderedImage;
024: import java.awt.image.WritableRaster;
025: import java.awt.image.renderable.ParameterBlock;
026: import java.awt.image.renderable.RenderedImageFactory;
027: import javax.media.jai.AreaOpImage;
028: import javax.media.jai.BorderExtender;
029: import javax.media.jai.ImageLayout;
030: import javax.media.jai.JAI;
031: import javax.media.jai.KernelJAI;
032: import javax.media.jai.OpImage;
033: import java.util.Map;
034: import javax.media.jai.PixelAccessor;
035: import javax.media.jai.PackedImageData;
036:
037: /**
038: *
039: * An OpImage class to perform dilation on a source image.
040: *
041: * Dilation for grey scale images can be charaterized by "slide, add and max",
042: * while for binary images by "slide and set". As always, the kernel
043: * is expected to come with a key position.
044: *
045: * <p> <b> Grey scale dilation</b> is a spatial operation that computes
046: * each output sample by adding elements of a kernel to the samples
047: * surrounding a particular source sample and taking the maximum.
048: * A mathematical expression is:
049: *
050: * <p> For a kernel K with a key position (xKey,yKey), the dilation
051: * of image I at (x,y) is given by:
052: * <pre>
053: * max{ I(x-i, y-j) + K(xKey+i, yKey+j): some (i,j) restriction }
054: *
055: * where the (i,j) restriction means:
056: * all possible (i,j) so that both I(x-i,y-j) and K(xKey+i, yKey+j)
057: * are defined, that is, these indecies are in bounds.
058: *
059: * </pre>
060: * <p>Intuitively in 2D, the kernel is like
061: * an unbrella and the key point is the handle. When the handle moves
062: * all over the image surface, the upper outbounds of all the umbrella
063: * positions is the dilation. Thus if you want the image to dilate in
064: * the upper right direction, the following kernel would do with
065: * the bold face key position.
066: *
067: * <p><center>
068: * <table border=1>
069: * <tr align=center><td>0</td><td>0</td><td>50</td> </tr>
070: * <tr align=center><td>0</td><td>50</td><td>0</td> </tr>
071: * <tr align=center><td><b>0</b></td><td>0</td><td>0</td> </tr>
072: * </table></center>
073: *
074: * <p> Note also that zero kernel have effects on the dilation!
075: * That is because of the "max" in the add and max process. Thus
076: * a 3 x 1 zero kernel with the key persion at the bottom of the kernel
077: * dilates the image upwards.
078: *
079: * <p>
080: * After the kernel is rotated 180 degrees, Pseudo code for dilation operation
081: * is as follows. Of course, you should provide the kernel in its
082: * (unrotated) original form. Assuming the kernel K is of size M rows x N cols
083: * and the key position is (xKey, yKey).
084: *
085: * // dilation
086: * for every dst pixel location (x,y){
087: * dst[x][y] = -infinity;
088: * for (i = -xKey; i < M - xKey; i++){
089: * for (j = -yKey; j < N - yKey; j++){
090: * if((x+i, y+j) are in bounds of src &&
091: * (xKey+i, yKey+j) are in bounds of K){
092: * tmp = src[x + i][y + j]+ K[xKey + i][yKey + j];
093: * dst[x][y] = max{tmp, dst[x][y]};
094: * }
095: * }
096: * }
097: * }
098: * </pre>
099: *
100: * <p> Dilation, unlike convolution and most neighborhood operations,
101: * actually can grow the image region. But to conform with other
102: * image neighborhood operations, the border pixels are set to 0.
103: * For a 3 x 3 kernel with the key point at the center, there will
104: * be a pixel wide 0 stripe around the border.
105: *
106: * <p> The kernel cannot be bigger in any dimension than the image data.
107: *
108: * <p> <b>Binary Image Dilation</b>
109: * requires the kernel K to be binary.
110: * Intuitively, starting from dst image being a duplicate of src,
111: * binary dilation slides the kernel K to place the key position
112: * at every non-zero point (x,y) in src image and set dst positions
113: * under ones of K to 1.
114: *
115: * <p> After the kernel is rotated 180 degrees, the pseudo code for
116: * dilation operation is as follows. (Of course, you should provide
117: * the kernel in its original unrotated form.)
118: *
119: * <pre>
120: *
121: * // dilating
122: * for every dst pixel location (x,y){
123: * dst[x][y] = src[x][y];
124: * for (i = -xKey; i < M - xKey; i++){
125: * for (j = -yKey; j < N - yKey; j++){
126: * if(src[x+i,y+i]==1 && Key(xKey+i, yKey+j)==1){
127: * dst[x][y] = 1; break;
128: * }
129: * }
130: * }
131: * }
132: * </pre>
133:
134: * <p> Reference: An Introduction to Nonlinear Image Processing,
135: * by Edward R. Bougherty and Jaakko Astola,
136: * Spie Optical Engineering Press, 1994.
137: *
138: *
139: * @see KernelJAI
140: */
141: final class DilateBinaryOpImage extends AreaOpImage {
142:
143: /**
144: * The kernel with which to do the dilate operation.
145: */
146: protected KernelJAI kernel;
147:
148: /** Kernel variables. */
149: private int kw, kh, kx, ky;
150: private int[] kdataPack; // Pack kernel into int;
151: private int kwPack; // num of int needed to pack each row of the kernel
152:
153: // Since this operation deals with packed binary data, we do not need
154: // to expand the IndexColorModel
155: private static Map configHelper(Map configuration) {
156:
157: Map config;
158:
159: if (configuration == null) {
160: config = new RenderingHints(
161: JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
162: } else {
163:
164: config = configuration;
165:
166: if (!(config.containsKey(JAI.KEY_REPLACE_INDEX_COLOR_MODEL))) {
167: config.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
168: Boolean.FALSE);
169: RenderingHints hints = (RenderingHints) configuration;
170: config = (RenderingHints) hints.clone();
171: }
172: }
173:
174: return config;
175: }
176:
177: /**
178: * Creates a DilateBinaryOpImage given a ParameterBlock containing the
179: * image source and pre-rotated dilation kernel. The image dimensions
180: * are derived from the source image. The tile grid layout, SampleModel,
181: * and ColorModel may optionally be specified by an ImageLayout object.
182: *
183: * @param source a RenderedImage.
184: * @param extender a BorderExtender, or null.
185: * @param layout an ImageLayout optionally containing the tile grid layout,
186: * SampleModel, and ColorModel, or null.
187: * @param kernel the pre-rotated dilation KernelJAI.
188: */
189: public DilateBinaryOpImage(RenderedImage source,
190: BorderExtender extender, Map config, ImageLayout layout,
191: KernelJAI kernel) {
192: super (source, layout, configHelper(config), true, extender,
193: kernel.getLeftPadding(), kernel.getRightPadding(),
194: kernel.getTopPadding(), kernel.getBottomPadding());
195:
196: this .kernel = kernel;
197: kw = kernel.getWidth();
198: kh = kernel.getHeight();
199: kx = kernel.getXOrigin();
200: ky = kernel.getYOrigin();
201:
202: kwPack = (kw + 31) / 32;
203: kdataPack = packKernel(kernel);
204: }
205:
206: /**
207: * Performs dilation on a specified rectangle. The sources are
208: * cobbled.
209: *
210: * @param sources an array of source Rasters, guaranteed to provide all
211: * necessary source data for computing the output.
212: * @param dest a WritableRaster tile containing the area to be computed.
213: * @param destRect the rectangle within dest to be processed.
214: */
215: protected void computeRect(Raster[] sources, WritableRaster dest,
216: Rectangle destRect) {
217:
218: Raster source = sources[0];
219:
220: PixelAccessor pa = new PixelAccessor(source.getSampleModel(),
221: null);
222: PackedImageData srcIm = pa.getPackedPixels(source, source
223: .getBounds(), false, false);
224:
225: pa = new PixelAccessor(dest.getSampleModel(), null);
226: PackedImageData dstIm = pa.getPackedPixels(dest, destRect,
227: true, false);
228:
229: // src data under kernel, packed in int.
230: int[] srcUK = new int[kwPack * kh];
231:
232: // sliding the kernel row by row
233: // general the packed matrix under the row
234: int dheight = destRect.height;
235: int dwidth = destRect.width;
236:
237: int sOffset = srcIm.offset;
238: int dOffset = dstIm.offset;
239: for (int j = 0; j < dheight; j++) {
240: int selement, val, dindex, delement;
241:
242: // reset srcUK for each row beginning
243: // src[sOffset +[-kx:kw-kx, -ky:kh-ky]] placed in srcUK
244: //
245: for (int m = 0; m < srcUK.length; m++) {
246: srcUK[m] = 0;
247: }
248:
249: // initial srcUK
250: // first shift left the packed bits under the sliding kernel by 1 bit
251: // then fill (compute) in the last bit of each row
252: for (int i = 0; i < kw - 1; i++) {
253: bitShiftMatrixLeft(srcUK, kh, kwPack); // expand for speedup?
254: int lastCol = kwPack - 1;
255: int bitLoc = srcIm.bitOffset + i;
256: int byteLoc = bitLoc >> 3;
257: bitLoc = 7 - (bitLoc & 7);
258: for (int m = 0, sOffsetB = sOffset; m < kh; m++, sOffsetB += srcIm.lineStride) {
259:
260: selement = (int) srcIm.data[sOffsetB + byteLoc];
261: val = (selement >> bitLoc) & 0x1;
262: srcUK[lastCol] |= val;
263: lastCol += kwPack;
264: }
265: }
266:
267: // same as above
268: // also setting dest
269: for (int i = 0; i < dwidth; i++) {
270:
271: bitShiftMatrixLeft(srcUK, kh, kwPack); // expand for speedup?
272: int lastCol = kwPack - 1;
273: int bitLoc = srcIm.bitOffset + i + kw - 1;
274: int byteLoc = bitLoc >> 3;
275: bitLoc = 7 - (bitLoc & 7);
276: for (int m = 0, sOffsetB = sOffset; m < kh; m++, sOffsetB += srcIm.lineStride) {
277:
278: selement = (int) srcIm.data[sOffsetB + byteLoc];
279: val = (selement >> bitLoc) & 0x1;
280: srcUK[lastCol] |= val;
281: lastCol += kwPack;
282: }
283:
284: // set dest bits
285: for (int m = 0; m < srcUK.length; m++) {
286: if ((srcUK[m] & kdataPack[m]) != 0) {
287: int dBitLoc = dstIm.bitOffset + i;
288: int dshift = 7 - (dBitLoc & 7);
289: int dByteLoc = (dBitLoc >> 3) + dOffset;
290: delement = (int) dstIm.data[dByteLoc];
291: delement |= (0x1) << dshift;
292: dstIm.data[dByteLoc] = (byte) delement;
293: break;
294: }
295: }
296:
297: }
298: sOffset += srcIm.lineStride;
299: dOffset += dstIm.lineStride;
300: }
301: pa.setPackedPixels(dstIm);
302: }
303:
304: /** pack kernel into integers by row, aligned to the right;
305: * extra bits on the left are filled with 0 bits
306: * @params kernel - the given kernel (already rotated)
307: * @returns an integer array of ints from packed kernel data
308: */
309: private final int[] packKernel(KernelJAI kernel) {
310: int kw = kernel.getWidth();
311: int kh = kernel.getHeight();
312: int kwPack = (31 + kw) / 32;
313: int kerPacked[] = new int[kwPack * kh];
314: float[] kdata = kernel.getKernelData();
315: for (int j = 0; j < kw; j++) {
316: int m = j;
317: int lastCol = kwPack - 1;
318: bitShiftMatrixLeft(kerPacked, kh, kwPack);
319: for (int i = 0; i < kh; i++, lastCol += kwPack, m += kw) {
320: if (kdata[m] > .9F) { // same as == 1.0F
321: kerPacked[lastCol] |= 0x1;
322: }
323: }
324: }
325: return kerPacked;
326: }
327:
328: // to shift an integer matrix one bit left
329: // assuming that the matrix is row oriented
330: // each row is viewed as a long bit array
331: // rows and cols are the dimention after packing
332: private final static void bitShiftMatrixLeft(int[] mat, int rows,
333: int cols) {
334: int m = 0;
335: for (int i = 0; i < rows; i++) {
336: for (int j = 0; j < cols - 1; j++) {
337: mat[m] = (mat[m] << 1) | (mat[m + 1] >>> 31);
338: m++;
339: }
340: mat[m] <<= 1;
341: m++;
342: }
343: }
344:
345: }
|