001: /*
002: * $RCSfile: Convolve3x3OpImage.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.1 $
009: * $Date: 2005/02/11 04:56:19 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.opimage;
013:
014: import java.awt.Rectangle;
015: import java.awt.image.DataBuffer;
016: import java.awt.image.Raster;
017: import java.awt.image.RenderedImage;
018: import java.awt.image.WritableRaster;
019: import java.awt.image.renderable.ParameterBlock;
020: import java.awt.image.renderable.RenderedImageFactory;
021: import javax.media.jai.AreaOpImage;
022: import javax.media.jai.BorderExtender;
023: import javax.media.jai.ImageLayout;
024: import javax.media.jai.KernelJAI;
025: import javax.media.jai.OpImage;
026: import javax.media.jai.RasterAccessor;
027: import javax.media.jai.RasterFormatTag;
028: import java.util.Map;
029:
030: // import com.sun.media.jai.test.OpImageTester;
031:
032: /**
033: * An OpImage class to perform a 3x3 convolution on a source image.
034: *
035: * <p> This class implements a convolution operation. Convolution is a
036: * spatial operation that computes each output sample by multiplying
037: * elements of a kernel with the samples surrounding a particular
038: * source sample.
039: *
040: * <p> For each destination sample, the kernel is rotated 180 degrees
041: * and its "key element" is placed over the source pixel corresponding
042: * with the destination pixel. The kernel elements are multiplied
043: * with the source pixels under them, and the resulting products are
044: * summed together to produce the destination sample value.
045: *
046: * <p> Convolution, or any neighborhood operation, leaves a band of
047: * pixels around the edges undefined, i.e., for a 3x3 kernel, only
048: * four kernel elements and four source pixels contribute to the
049: * destination pixel located at (0,0). Such pixels are not includined
050: * in the destination image. A BorderOpImage may be used to add an
051: * appropriate border to the source image in order to avoid shrinkage
052: * of the image boundaries.
053: *
054: * <p> The Kernel cannot be bigger in any dimension than the image data.
055: *
056: *
057: * @see KernelJAI
058: */
059: final class Convolve3x3OpImage extends AreaOpImage {
060: /**
061: * The 3x3 kernel with which to do the convolve operation.
062: */
063: protected KernelJAI kernel;
064:
065: float tables[][] = new float[9][256];
066:
067: /**
068: * Creates a Convolve3x3OpImage given a ParameterBlock containing the image
069: * source and a pre-rotated convolution kernel. The image dimensions
070: * are derived
071: * from the source image. The tile grid layout, SampleModel, and
072: * ColorModel may optionally be specified by an ImageLayout
073: * object.
074: *
075: * @param source a RenderedImage.
076: * @param extender a BorderExtender, or null.
077: * @param layout an ImageLayout optionally containing the tile grid layout,
078: * SampleModel, and ColorModel, or null.
079: * @param kernel the pre-rotated convolution KernelJAI.
080: * @param cobbleSources a boolean indicating whether computeRect()
081: * expects contiguous sources.
082: */
083: public Convolve3x3OpImage(RenderedImage source,
084: BorderExtender extender, Map config, ImageLayout layout,
085: KernelJAI kernel) {
086: super (source, layout, config, true, extender, kernel
087: .getLeftPadding(), kernel.getRightPadding(), kernel
088: .getTopPadding(), kernel.getBottomPadding());
089:
090: this .kernel = kernel;
091: if ((kernel.getWidth() != 3) || (kernel.getHeight() != 3)
092: || (kernel.getXOrigin() != 1)
093: || (kernel.getYOrigin() != 1)) {
094: throw new RuntimeException(JaiI18N
095: .getString("Convolve3x3OpImage0"));
096: }
097:
098: if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE) {
099: float kdata[] = kernel.getKernelData();
100: float k0 = kdata[0], k1 = kdata[1], k2 = kdata[2], k3 = kdata[3], k4 = kdata[4], k5 = kdata[5], k6 = kdata[6], k7 = kdata[7], k8 = kdata[8];
101:
102: for (int j = 0; j < 256; j++) {
103: byte b = (byte) j;
104: float f = (float) j;
105: tables[0][b + 128] = k0 * f + 0.5f;
106: tables[1][b + 128] = k1 * f;
107: tables[2][b + 128] = k2 * f;
108: tables[3][b + 128] = k3 * f;
109: tables[4][b + 128] = k4 * f;
110: tables[5][b + 128] = k5 * f;
111: tables[6][b + 128] = k6 * f;
112: tables[7][b + 128] = k7 * f;
113: tables[8][b + 128] = k8 * f;
114: }
115: }
116: }
117:
118: /**
119: * Performs convolution on a specified rectangle. The sources are
120: * cobbled.
121: *
122: * @param sources an array of source Rasters, guaranteed to provide all
123: * necessary source data for computing the output.
124: * @param dest a WritableRaster tile containing the area to be computed.
125: * @param destRect the rectangle within dest to be processed.
126: */
127: protected void computeRect(Raster[] sources, WritableRaster dest,
128: Rectangle destRect) {
129: // Retrieve format tags.
130: RasterFormatTag[] formatTags = getFormatTags();
131:
132: Raster source = sources[0];
133: Rectangle srcRect = mapDestRect(destRect, 0);
134:
135: RasterAccessor srcAccessor = new RasterAccessor(source,
136: srcRect, formatTags[0], getSourceImage(0)
137: .getColorModel());
138: RasterAccessor dstAccessor = new RasterAccessor(dest, destRect,
139: formatTags[1], getColorModel());
140:
141: switch (dstAccessor.getDataType()) {
142: case DataBuffer.TYPE_BYTE:
143: byteLoop(srcAccessor, dstAccessor);
144: break;
145: case DataBuffer.TYPE_SHORT:
146: shortLoop(srcAccessor, dstAccessor);
147: break;
148: case DataBuffer.TYPE_INT:
149: intLoop(srcAccessor, dstAccessor);
150: break;
151: default:
152: String className = this .getClass().getName();
153: throw new RuntimeException(JaiI18N
154: .getString("Convolve3x3OpImage1"));
155: }
156:
157: // If the RasterAccessor object set up a temporary buffer for the
158: // op to write to, tell the RasterAccessor to write that data
159: // to the raster no that we're done with it.
160: if (dstAccessor.isDataCopy()) {
161: dstAccessor.clampDataArrays();
162: dstAccessor.copyDataToRaster();
163: }
164: }
165:
166: private void byteLoop(RasterAccessor src, RasterAccessor dst) {
167: int dwidth = dst.getWidth();
168: int dheight = dst.getHeight();
169: int dnumBands = dst.getNumBands();
170:
171: // cache these out to avoid an array access per kernel value
172: float t0[] = tables[0], t1[] = tables[1], t2[] = tables[2], t3[] = tables[3], t4[] = tables[4], t5[] = tables[5], t6[] = tables[6], t7[] = tables[7], t8[] = tables[8];
173:
174: float kdata[] = kernel.getKernelData();
175:
176: byte dstDataArrays[][] = dst.getByteDataArrays();
177: int dstBandOffsets[] = dst.getBandOffsets();
178: int dstPixelStride = dst.getPixelStride();
179: int dstScanlineStride = dst.getScanlineStride();
180:
181: byte srcDataArrays[][] = src.getByteDataArrays();
182: int srcBandOffsets[] = src.getBandOffsets();
183: int srcPixelStride = src.getPixelStride();
184: int srcScanlineStride = src.getScanlineStride();
185:
186: // precalcaculate offsets
187: int centerScanlineOffset = srcScanlineStride;
188: int bottomScanlineOffset = srcScanlineStride * 2;
189: int middlePixelOffset = dnumBands;
190: int rightPixelOffset = dnumBands * 2;
191:
192: for (int k = 0; k < dnumBands; k++) {
193: byte dstData[] = dstDataArrays[k];
194: byte srcData[] = srcDataArrays[k];
195: int srcScanlineOffset = srcBandOffsets[k];
196: int dstScanlineOffset = dstBandOffsets[k];
197: for (int j = 0; j < dheight; j++) {
198: int srcPixelOffset = srcScanlineOffset;
199: int dstPixelOffset = dstScanlineOffset;
200: for (int i = 0; i < dwidth; i++) {
201: float f = t0[128 + srcData[srcPixelOffset]]
202: + t1[128 + srcData[srcPixelOffset
203: + middlePixelOffset]]
204: + t2[128 + srcData[srcPixelOffset
205: + rightPixelOffset]]
206: + t3[128 + srcData[srcPixelOffset
207: + centerScanlineOffset]]
208: + t4[128 + srcData[srcPixelOffset
209: + centerScanlineOffset
210: + middlePixelOffset]]
211: + t5[128 + srcData[srcPixelOffset
212: + centerScanlineOffset
213: + rightPixelOffset]]
214: + t6[128 + srcData[srcPixelOffset
215: + bottomScanlineOffset]]
216: + t7[128 + srcData[srcPixelOffset
217: + bottomScanlineOffset
218: + middlePixelOffset]]
219: + t8[128 + srcData[srcPixelOffset
220: + bottomScanlineOffset
221: + rightPixelOffset]];
222:
223: // do the clamp
224: int val = (int) f;
225: if (val < 0) {
226: val = 0;
227: } else if (val > 255) {
228: val = 255;
229: }
230: dstData[dstPixelOffset] = (byte) (val);
231: srcPixelOffset += srcPixelStride;
232: dstPixelOffset += dstPixelStride;
233: }
234: srcScanlineOffset += srcScanlineStride;
235: dstScanlineOffset += dstScanlineStride;
236: }
237: }
238: }
239:
240: private void shortLoop(RasterAccessor src, RasterAccessor dst) {
241: int dwidth = dst.getWidth();
242: int dheight = dst.getHeight();
243: int dnumBands = dst.getNumBands();
244:
245: short dstDataArrays[][] = dst.getShortDataArrays();
246: int dstBandOffsets[] = dst.getBandOffsets();
247: int dstPixelStride = dst.getPixelStride();
248: int dstScanlineStride = dst.getScanlineStride();
249:
250: short srcDataArrays[][] = src.getShortDataArrays();
251: int srcBandOffsets[] = src.getBandOffsets();
252: int srcPixelStride = src.getPixelStride();
253: int srcScanlineStride = src.getScanlineStride();
254:
255: // precalcaculate offsets
256: int centerScanlineOffset = srcScanlineStride;
257: int bottomScanlineOffset = srcScanlineStride * 2;
258: int middlePixelOffset = dnumBands;
259: int rightPixelOffset = dnumBands * 2;
260:
261: float kdata[] = kernel.getKernelData();
262: float k0 = kdata[0], k1 = kdata[1], k2 = kdata[2], k3 = kdata[3], k4 = kdata[4], k5 = kdata[5], k6 = kdata[6], k7 = kdata[7], k8 = kdata[8];
263:
264: for (int k = 0; k < dnumBands; k++) {
265: short dstData[] = dstDataArrays[k];
266: short srcData[] = srcDataArrays[k];
267: int srcScanlineOffset = srcBandOffsets[k];
268: int dstScanlineOffset = dstBandOffsets[k];
269: for (int j = 0; j < dheight; j++) {
270: int srcPixelOffset = srcScanlineOffset;
271: int dstPixelOffset = dstScanlineOffset;
272: for (int i = 0; i < dwidth; i++) {
273: float f = k0
274: * srcData[srcPixelOffset]
275: + k1
276: * srcData[srcPixelOffset
277: + middlePixelOffset]
278: + k2
279: * srcData[srcPixelOffset + rightPixelOffset]
280: + k3
281: * srcData[srcPixelOffset
282: + centerScanlineOffset]
283: + k4
284: * srcData[srcPixelOffset
285: + centerScanlineOffset
286: + middlePixelOffset]
287: + k5
288: * srcData[srcPixelOffset
289: + centerScanlineOffset
290: + rightPixelOffset]
291: + k6
292: * srcData[srcPixelOffset
293: + bottomScanlineOffset]
294: + k7
295: * srcData[srcPixelOffset
296: + bottomScanlineOffset
297: + middlePixelOffset]
298: + k8
299: * srcData[srcPixelOffset
300: + bottomScanlineOffset
301: + rightPixelOffset];
302:
303: int val = (int) f;
304: if (val < Short.MIN_VALUE) {
305: val = Short.MIN_VALUE;
306: } else if (val > Short.MAX_VALUE) {
307: val = Short.MAX_VALUE;
308: }
309: dstData[dstPixelOffset] = (short) val;
310: srcPixelOffset += srcPixelStride;
311: dstPixelOffset += dstPixelStride;
312: }
313: srcScanlineOffset += srcScanlineStride;
314: dstScanlineOffset += dstScanlineStride;
315: }
316: }
317: }
318:
319: private void intLoop(RasterAccessor src, RasterAccessor dst) {
320: int dwidth = dst.getWidth();
321: int dheight = dst.getHeight();
322: int dnumBands = dst.getNumBands();
323:
324: int dstDataArrays[][] = dst.getIntDataArrays();
325: int dstBandOffsets[] = dst.getBandOffsets();
326: int dstPixelStride = dst.getPixelStride();
327: int dstScanlineStride = dst.getScanlineStride();
328:
329: int srcDataArrays[][] = src.getIntDataArrays();
330: int srcBandOffsets[] = src.getBandOffsets();
331: int srcPixelStride = src.getPixelStride();
332: int srcScanlineStride = src.getScanlineStride();
333:
334: // precalcaculate offsets
335: int centerScanlineOffset = srcScanlineStride;
336: int bottomScanlineOffset = srcScanlineStride * 2;
337: int middlePixelOffset = dnumBands;
338: int rightPixelOffset = dnumBands * 2;
339:
340: float kdata[] = kernel.getKernelData();
341: float k0 = kdata[0], k1 = kdata[1], k2 = kdata[2], k3 = kdata[3], k4 = kdata[4], k5 = kdata[5], k6 = kdata[6], k7 = kdata[7], k8 = kdata[8];
342:
343: for (int k = 0; k < dnumBands; k++) {
344: int dstData[] = dstDataArrays[k];
345: int srcData[] = srcDataArrays[k];
346: int srcScanlineOffset = srcBandOffsets[k];
347: int dstScanlineOffset = dstBandOffsets[k];
348: for (int j = 0; j < dheight; j++) {
349: int srcPixelOffset = srcScanlineOffset;
350: int dstPixelOffset = dstScanlineOffset;
351: for (int i = 0; i < dwidth; i++) {
352: float f = k0
353: * srcData[srcPixelOffset]
354: + k1
355: * srcData[srcPixelOffset
356: + middlePixelOffset]
357: + k2
358: * srcData[srcPixelOffset + rightPixelOffset]
359: + k3
360: * srcData[srcPixelOffset
361: + centerScanlineOffset]
362: + k4
363: * srcData[srcPixelOffset
364: + centerScanlineOffset
365: + middlePixelOffset]
366: + k5
367: * srcData[srcPixelOffset
368: + centerScanlineOffset
369: + rightPixelOffset]
370: + k6
371: * srcData[srcPixelOffset
372: + bottomScanlineOffset]
373: + k7
374: * srcData[srcPixelOffset
375: + bottomScanlineOffset
376: + middlePixelOffset]
377: + k8
378: * srcData[srcPixelOffset
379: + bottomScanlineOffset
380: + rightPixelOffset];
381:
382: dstData[dstPixelOffset] = (int) f;
383: srcPixelOffset += srcPixelStride;
384: dstPixelOffset += dstPixelStride;
385: }
386: srcScanlineOffset += srcScanlineStride;
387: dstScanlineOffset += dstScanlineStride;
388: }
389: }
390: }
391:
392: // public static OpImage createTestImage(OpImageTester oit) {
393: // float data[] = {0.05f,0.10f,0.05f,
394: // 0.10f,0.40f,0.10f,
395: // 0.05f,0.10f,0.05f};
396: // KernelJAI kJAI = new KernelJAI(3,3,1,1,data);
397: // return new Convolve3x3OpImage(oit.getSource(), null, null,
398: // new ImageLayout(oit.getSource()),
399: // kJAI);
400: // }
401:
402: // // Calls a method on OpImage that uses introspection, to make this
403: // // class, discover it's createTestImage() call, call it and then
404: // // benchmark the performance of the created OpImage chain.
405: // public static void main(String args[]) {
406: // String classname = "com.sun.media.jai.opimage.Convolve3x3OpImage";
407: // OpImageTester.performDiagnostics(classname,args);
408: // }
409: }
|