001: /*
002: * $RCSfile: ErodeBinaryOpImage.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:59 $
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 javax.media.jai.RasterAccessor;
034: import javax.media.jai.RasterFormatTag;
035: import java.util.Map;
036: import javax.media.jai.PixelAccessor;
037: import javax.media.jai.PackedImageData;
038:
039: /**
040: *
041: * An OpImage class to perform erosion on a source image.
042: *
043: * <p> This class implements an erosion operation.
044: *
045: * <p> <b>Grey Scale Erosion</b>
046: * is a spatial operation that computes
047: * each output sample by subtract elements of a kernel to the samples
048: * surrounding a particular source sample with some care.
049: * A mathematical expression is:
050: *
051: * <p> For a kernel K with a key position (xKey, yKey), the erosion
052: * of image I at (x,y) is given by:
053: * <pre>
054: * max{a: a + K(xKey+i, yKey+j) <= I(x+i,y+j): all (i,j) }
055: *
056: * all possible (i,j) means that both I(x+i,y+j) and K(xKey+i, yKey+j)
057: * are in bounds. Otherwise, the value is set to 0.
058: *
059: * </pre>
060: * <p> Intuitively, the kernel is like an unbrella and the key point
061: * is the handle. At every point, you try to push the umbrella up as high
062: * as possible but still underneath the image surface. The final height
063: * of the handle is the value after erosion. Thus if you want the image
064: * to erode from the upper right to bottom left, the following would do.
065: *
066: * <p><center>
067: * <table border=1>
068: * <tr align=center><td>0</td><td>0</td><td>X</td> </tr>
069: * <tr align=center><td>0</td><td>X</td><td>0</td> </tr>
070: * <tr align=center><td><b>X</b></td><td>0</td><td>0</td> </tr>
071: * </table></center>
072: *
073: * <p> Note that zero kernel erosion has effects on the image, the
074: * location of the key position and size of kernel all matter.
075: *
076: * <p> Pseudo code for the erosion operation is as follows.
077: * Assuming the kernel K is of size M rows x N cols
078: * and the key position is (xKey, yKey).
079: *
080: * <pre>
081: *
082: * // erosion
083: * for every dst pixel location (x,y){
084: * tmp = infinity;
085: * for (i = -xKey; i < M - xKey; i++){
086: * for (j = -yKey; j < N - yKey; j++){
087: * if((x+i, y+j) are in bounds of src){
088: * tmp = min{tmp, src[x + i][y + j] - K[xKey + i][yKey + j]};
089: * }
090: * }
091: * }
092: * dst[x][y] = tmp;
093: * if (dst[x][y] == infinity)
094: * dst[x][y] = 0;
095: * }
096: * </pre>
097: *
098: * <p> The kernel cannot be bigger in any dimension than the image data.
099: *
100: * <p> <b>Binary Image Erosion</b>
101: * requires the kernel to be binary as well.
102: * Intuitively, binary erosion slides the kernel
103: * key position and place it at every non-zero point (x,y) in the src image.
104: * The dst value at this position is set to 1 if all the kernel
105: * are fully supported by the src image, and the src image value is 1
106: * whenever the kernel has value 1.
107: * Otherwise, the value after erosion at (x,y) is set to 0.
108: * Erosion usually shrinks images, but it can fill holes
109: * with kernels like
110: * <pre> [1 0 1] </pre>
111: * and the key position at the center.
112: *
113: * <p> Pseudo code for the erosion operation is as follows.
114: *
115: * <pre>
116: * // erosion
117: * for every dst pixel location (x,y){
118: * dst[x][y] = 1;
119: * for (i = -xKey; i < M - xKey; i++){
120: * for (j = -yKey; j < N - yKey; j++){
121: * if((x+i,y+j) is out of bounds of src ||
122: * src(x+i, y+j)==0 && Key(xKey+i, yKey+j)==1){
123: * dst[x][y] = 0; break;
124: * }
125: * }
126: * }
127: * }
128: * </pre>
129: *
130: * <p> Reference: An Introduction to Nonlinear Image Processing,
131: * by Edward R. Bougherty and Jaakko Astola,
132: * Spie Optical Engineering Press, 1994.
133: *
134: *
135: * @see KernelJAI
136: */
137:
138: final class ErodeBinaryOpImage extends AreaOpImage {
139:
140: /**
141: * The kernel with which to do the erode operation.
142: */
143: protected KernelJAI kernel;
144:
145: /** Kernel variables. */
146: private int kw, kh, kx, ky;
147: private int[] kdataPack; // Pack kernel into integers;
148: private int kwPack;
149:
150: // for factoring things out
151: private int dwidth, dheight;
152: private int dnumBands; // should be 1; gray images
153:
154: private int bits; // per packed unit, 8, 16 or 32
155:
156: //private int dstBandOffsets[];
157: private int dstDBOffset;
158: private int dstScanlineStride;
159: private int dstScanlineStrideBits;
160: private int dstMinX, dstMinY, dstTransX, dstTransY;
161: private int dstDataBitOffset;
162:
163: //private int srcBandOffsets[];
164: private int srcDBOffset;
165: private int srcScanlineStride;
166: private int srcScanlineStrideBits;
167: private int srcMinX, srcMinY, srcTransX, srcTransY;
168: private int srcDataBitOffset;
169:
170: // Since this operation deals with packed binary data, we do not need
171: // to expand the IndexColorModel
172: private static Map configHelper(Map configuration) {
173:
174: Map config;
175:
176: if (configuration == null) {
177: config = new RenderingHints(
178: JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
179: } else {
180:
181: config = configuration;
182:
183: if (!(config.containsKey(JAI.KEY_REPLACE_INDEX_COLOR_MODEL))) {
184: RenderingHints hints = (RenderingHints) configuration;
185: config = (RenderingHints) hints.clone();
186: config.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
187: Boolean.FALSE);
188: }
189: }
190:
191: return config;
192: }
193:
194: /**
195: * Creates a ErodeBinaryOpImage given a ParameterBlock containing the image
196: * source and pre-rotated erosion kernel. The image dimensions are
197: * derived
198: * from the source image. The tile grid layout, SampleModel, and
199: * ColorModel may optionally be specified by an ImageLayout
200: * object.
201: *
202: * @param source a RenderedImage.
203: * @param extender a BorderExtender, or null.
204: * @param layout an ImageLayout optionally containing the tile grid layout,
205: * SampleModel, and ColorModel, or null.
206: * @param kernel the pre-rotated erosion KernelJAI.
207: */
208: public ErodeBinaryOpImage(RenderedImage source,
209: BorderExtender extender, Map config, ImageLayout layout,
210: KernelJAI kernel) {
211: super (source, layout, configHelper(config), true, extender,
212: kernel.getLeftPadding(), kernel.getRightPadding(),
213: kernel.getTopPadding(), kernel.getBottomPadding());
214:
215: this .kernel = kernel;
216: kw = kernel.getWidth();
217: kh = kernel.getHeight();
218: kx = kernel.getXOrigin();
219: ky = kernel.getYOrigin();
220:
221: kwPack = (kw + 31) / 32;
222: kdataPack = packKernel(kernel);
223: }
224:
225: /**
226: * Performs erosion on a specified rectangle. The sources are
227: * cobbled.
228: *
229: * @param sources an array of source Rasters, guaranteed to provide all
230: * necessary source data for computing the output.
231: * @param dest a WritableRaster tile containing the area to be computed.
232: * @param destRect the rectangle within dest to be processed.
233: */
234: protected void computeRect(Raster[] sources, WritableRaster dest,
235: Rectangle destRect) {
236:
237: Raster source = sources[0];
238:
239: PixelAccessor pa = new PixelAccessor(source.getSampleModel(),
240: null);
241: PackedImageData srcIm = pa.getPackedPixels(source, source
242: .getBounds(), false, false);
243:
244: pa = new PixelAccessor(dest.getSampleModel(), null);
245: PackedImageData dstIm = pa.getPackedPixels(dest, destRect,
246: true, false);
247:
248: // src data under kernel, packed in int.
249: int[] srcUK = new int[kwPack * kh];
250:
251: // sliding the kernel row by row
252: // general the packed matrix under the row
253: int dheight = destRect.height;
254: int dwidth = destRect.width;
255:
256: int sOffset = srcIm.offset;
257: int dOffset = dstIm.offset;
258: for (int j = 0; j < dheight; j++) {
259: int selement, val, dindex, delement;
260:
261: // reset srcUK for each row beginning
262: // src[sOffset +[-kx:kw-kx, -ky:kh-ky]] placed in srcUK
263: //
264: for (int m = 0; m < srcUK.length; m++) {
265: srcUK[m] = 0;
266: }
267:
268: // initial srcUK
269: // first shift left the packed bits under the sliding kernel by 1 bit
270: // then fill (compute) in the last bit of each row
271: for (int i = 0; i < kw - 1; i++) {
272: bitShiftMatrixLeft(srcUK, kh, kwPack); // expand for speedup?
273: int lastCol = kwPack - 1;
274: int bitLoc = srcIm.bitOffset + i;
275: int byteLoc = bitLoc >> 3;
276: bitLoc = 7 - (bitLoc & 7);
277: for (int m = 0, sOffsetB = sOffset; m < kh; m++, sOffsetB += srcIm.lineStride) {
278:
279: selement = (int) srcIm.data[sOffsetB + byteLoc];
280: val = (selement >> bitLoc) & 0x1;
281: srcUK[lastCol] |= val;
282: lastCol += kwPack;
283: }
284: }
285:
286: // same as above
287: // also setting dest
288: for (int i = 0; i < dwidth; i++) {
289:
290: bitShiftMatrixLeft(srcUK, kh, kwPack); // expand for speedup?
291: int lastCol = kwPack - 1;
292: int bitLoc = srcIm.bitOffset + i + kw - 1;
293: int byteLoc = bitLoc >> 3;
294: bitLoc = 7 - (bitLoc & 7);
295: for (int m = 0, sOffsetB = sOffset; m < kh; m++, sOffsetB += srcIm.lineStride) {
296:
297: selement = (int) srcIm.data[sOffsetB + byteLoc];
298: val = (selement >> bitLoc) & 0x1;
299: srcUK[lastCol] |= val;
300: lastCol += kwPack;
301: }
302:
303: int dBitLoc = dstIm.bitOffset + i;
304: int dshift = 7 - (dBitLoc & 7);
305: int dByteLoc = (dBitLoc >> 3) + dOffset;
306: delement = (int) dstIm.data[dByteLoc];
307: delement |= (0x1) << dshift;
308:
309: for (int m = 0; m < srcUK.length; m++) {
310: if ((srcUK[m] & kdataPack[m]) != kdataPack[m]) {
311: delement &= ~((0x1) << dshift);
312: break;
313: }
314: }
315: dstIm.data[dByteLoc] = (byte) delement;
316: }
317: sOffset += srcIm.lineStride;
318: dOffset += dstIm.lineStride;
319: }
320: pa.setPackedPixels(dstIm);
321: }
322:
323: /** pack kernel into integers by row, aligned to the right;
324: * extra bits on the left are filled with 0 bits
325: * @params kernel - the given kernel (already rotated)
326: * @returns an integer array of ints from packed kernel data
327: */
328: private final int[] packKernel(KernelJAI kernel) {
329: int kw = kernel.getWidth();
330: int kh = kernel.getHeight();
331: int kwPack = (31 + kw) / 32;
332: int kerPacked[] = new int[kwPack * kh];
333: float[] kdata = kernel.getKernelData();
334: for (int j = 0; j < kw; j++) {
335: int m = j;
336: int lastCol = kwPack - 1;
337: bitShiftMatrixLeft(kerPacked, kh, kwPack);
338: for (int i = 0; i < kh; i++, lastCol += kwPack, m += kw) {
339: if (kdata[m] > .9F) { // same as == 1.0F
340: kerPacked[lastCol] |= 0x1;
341: }
342: }
343: }
344: return kerPacked;
345: }
346:
347: // to shift an integer matrix one bit left
348: // assuming that the matrix is row oriented
349: // each row is viewed as a long bit array
350: // rows and cols are the dimention after packing
351: private final static void bitShiftMatrixLeft(int[] mat, int rows,
352: int cols) {
353: int m = 0;
354: for (int i = 0; i < rows; i++) {
355: for (int j = 0; j < cols - 1; j++) {
356: mat[m] = (mat[m] << 1) | (mat[m + 1] >>> 31);
357: m++;
358: }
359: mat[m] <<= 1;
360: m++;
361: }
362: }
363:
364: }
|