001: /*
002: * $RCSfile: DFTOpImage.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:22 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.opimage;
013:
014: import java.awt.Image;
015: import java.awt.Rectangle;
016: import java.awt.geom.Point2D;
017: import java.awt.image.ColorModel;
018: import java.awt.image.DataBuffer;
019: import java.awt.image.Raster;
020: import java.awt.image.RenderedImage;
021: import java.awt.image.SampleModel;
022: import java.awt.image.WritableRaster;
023: import java.text.NumberFormat;
024: import java.util.Arrays;
025: import java.util.Locale;
026: import java.util.Map;
027: import javax.media.jai.EnumeratedParameter;
028: import javax.media.jai.ImageLayout;
029: import javax.media.jai.RasterAccessor;
030: import javax.media.jai.RasterFormatTag;
031: import javax.media.jai.RasterFactory;
032: import javax.media.jai.UntiledOpImage;
033: import javax.media.jai.operator.DFTDescriptor;
034: import com.sun.media.jai.util.JDKWorkarounds;
035: import com.sun.media.jai.util.MathJAI;
036:
037: /**
038: * An <code>OpImage</code> implementing the forward and inverse discrete
039: * Fourier transform (DFT) operations as described in
040: * <code>javax.media.jai.operator.DFTDescriptor</code> and
041: * <code>javax.media.jai.operator.IDFTDescriptor</code>.
042: *
043: * <p> The DFT operation is implemented using a one-dimensional decimation
044: * in time fast Fourier transform (FFT) which is applied successively to the
045: * rows and the columns of the image. All image dimensions are enlarged to the
046: * next positive power of 2 greater than or equal to the respective dimension
047: * unless the dimension is unity in which case it is not modified. Source
048: * image values are padded with zeros when the dimension is smaller than the
049: * output power-of-2 dimension.
050: *
051: * @since EA3
052: *
053: * @see javax.media.jai.UntiledOpImage
054: * @see javax.media.jai.operator.DFTDescriptor
055: * @see javax.media.jai.operator.IDFTDescriptor
056: *
057: */
058: public class DFTOpImage extends UntiledOpImage {
059: /** The Fast Fourier Transform object. */
060: FFT fft;
061:
062: /** Flag indicating whether the source image is complex. */
063: protected boolean complexSrc;
064:
065: /** Flag indicating whether the destination image is complex. */
066: protected boolean complexDst;
067:
068: /**
069: * Override the dimension specification for the destination such that it
070: * has width and height which are equal to non-negative powers of 2.
071: */
072: private static ImageLayout layoutHelper(ImageLayout layout,
073: RenderedImage source, EnumeratedParameter dataNature) {
074: // Create an ImageLayout or clone the one passed in.
075: ImageLayout il = layout == null ? new ImageLayout()
076: : (ImageLayout) layout.clone();
077:
078: // Force the origin to coincide with that of the source.
079: il.setMinX(source.getMinX());
080: il.setMinY(source.getMinY());
081:
082: // Recalculate the non-unity dimensions to be a positive power of 2.
083: // XXX This calculation should not be effected if an implementation
084: // of the FFT which supports arbitrary dimensions is used.
085: int currentWidth = il.getWidth(source);
086: int currentHeight = il.getHeight(source);
087: int newWidth;
088: int newHeight;
089: if (currentWidth == 1 && currentHeight == 1) {
090: newWidth = newHeight = 1;
091: } else if (currentWidth == 1 && currentHeight > 1) {
092: newWidth = 1;
093: newHeight = MathJAI.nextPositivePowerOf2(currentHeight);
094: } else if (currentWidth > 1 && currentHeight == 1) {
095: newWidth = MathJAI.nextPositivePowerOf2(currentWidth);
096: newHeight = 1;
097: } else { // Neither dimension equal to unity.
098: newWidth = MathJAI.nextPositivePowerOf2(currentWidth);
099: newHeight = MathJAI.nextPositivePowerOf2(currentHeight);
100: }
101: il.setWidth(newWidth);
102: il.setHeight(newHeight);
103:
104: // Set the complex flags for source and destination.
105: boolean isComplexSource = !dataNature
106: .equals(DFTDescriptor.REAL_TO_COMPLEX);
107: boolean isComplexDest = !dataNature
108: .equals(DFTDescriptor.COMPLEX_TO_REAL);
109:
110: // Initialize the SampleModel creation flag.
111: boolean createNewSampleModel = false;
112:
113: // Determine the number of required bands.
114: SampleModel srcSampleModel = source.getSampleModel();
115: int requiredNumBands = srcSampleModel.getNumBands();
116: if (isComplexSource && !isComplexDest) {
117: requiredNumBands /= 2;
118: } else if (!isComplexSource && isComplexDest) {
119: requiredNumBands *= 2;
120: }
121:
122: // Set the number of bands.
123: SampleModel sm = il.getSampleModel(source);
124: int numBands = sm.getNumBands();
125: if (numBands != requiredNumBands) {
126: numBands = requiredNumBands;
127: createNewSampleModel = true;
128: }
129:
130: // Force the image to contain floating point data.
131: int dataType = sm.getTransferType();
132: if (dataType != DataBuffer.TYPE_FLOAT
133: && dataType != DataBuffer.TYPE_DOUBLE) {
134: dataType = DataBuffer.TYPE_FLOAT;
135: createNewSampleModel = true;
136: }
137:
138: // Create a new SampleModel for the destination if necessary.
139: if (createNewSampleModel) {
140: sm = RasterFactory.createComponentSampleModel(sm, dataType,
141: newWidth, newHeight, numBands);
142: il.setSampleModel(sm);
143:
144: // Clear the ColorModel mask if needed.
145: ColorModel cm = il.getColorModel(null);
146: if (cm != null
147: && !JDKWorkarounds.areCompatibleDataModels(sm, cm)) {
148: // Clear the mask bit if incompatible.
149: il.unsetValid(ImageLayout.COLOR_MODEL_MASK);
150: }
151: }
152:
153: return il;
154: }
155:
156: /**
157: * Constructs a <code>DFTOpImage</code> object.
158: *
159: * <p>The image dimensions are the respective next positive powers of 2
160: * greater than or equal to the dimensions of the source image. The tile
161: * grid layout, SampleModel, and ColorModel may optionally be specified
162: * by an ImageLayout object.
163: *
164: * @param source A RenderedImage.
165: * @param layout An ImageLayout optionally containing the tile grid layout,
166: * SampleModel, and ColorModel, or null.
167: * @param fft The Fast Fourier Transform object.
168: *
169: * @see DFTDescriptor.
170: */
171: public DFTOpImage(RenderedImage source, Map config,
172: ImageLayout layout, EnumeratedParameter dataNature, FFT fft) {
173: super (source, config, layoutHelper(layout, source, dataNature));
174:
175: // Cache the FFT object.
176: this .fft = fft;
177:
178: // Set the complex flags for source and destination.
179: complexSrc = !dataNature.equals(DFTDescriptor.REAL_TO_COMPLEX);
180: complexDst = !dataNature.equals(DFTDescriptor.COMPLEX_TO_REAL);
181: }
182:
183: /**
184: * Computes the source point corresponding to the supplied point.
185: *
186: * @param destPt the position in destination image coordinates
187: * to map to source image coordinates.
188: *
189: * @return <code>null</code>.
190: *
191: * @throws IllegalArgumentException if <code>destPt</code> is
192: * <code>null</code>.
193: *
194: * @since JAI 1.1.2
195: */
196: public Point2D mapDestPoint(Point2D destPt) {
197: if (destPt == null) {
198: throw new IllegalArgumentException(JaiI18N
199: .getString("Generic0"));
200: }
201:
202: return null;
203: }
204:
205: /**
206: * Computes the destination point corresponding to the supplied point.
207: *
208: * @return <code>null</code>.
209: *
210: * @throws IllegalArgumentException if <code>sourcePt</code> is
211: * <code>null</code>.
212: *
213: * @since JAI 1.1.2
214: */
215: public Point2D mapSourcePoint(Point2D sourcePt) {
216: if (sourcePt == null) {
217: throw new IllegalArgumentException(JaiI18N
218: .getString("Generic0"));
219: }
220:
221: return null;
222: }
223:
224: /**
225: * Calculate the discrete Fourier transform of the source image.
226: *
227: * @param source The source Raster; should be the whole image.
228: * @param dest The destination WritableRaster; should be the whole image.
229: * @param destRect The destination Rectangle; should be the image bounds.
230: */
231: protected void computeImage(Raster[] sources, WritableRaster dest,
232: Rectangle destRect) {
233: Raster source = sources[0];
234:
235: // Degenerate case.
236: if (destRect.width == 1 && destRect.height == 1) {
237: int nDstBands = sampleModel.getNumBands();
238: double[] srcPixel = new double[source.getSampleModel()
239: .getNumBands()];
240: source.getPixel(destRect.x, destRect.y, srcPixel);
241: if (complexSrc && complexDst) { // Complex -> Complex
242: dest.setPixel(destRect.x, destRect.y, srcPixel);
243: } else if (complexSrc) { // Complex -> Real.
244: for (int i = 0; i < nDstBands; i++) {
245: // Set destination to real part.
246: dest.setSample(destRect.x, destRect.y, i,
247: srcPixel[2 * i]);
248: }
249: } else if (complexDst) { // Real -> Complex
250: for (int i = 0; i < nDstBands; i++) {
251: // Set destination real part to source.
252: dest.setSample(destRect.x, destRect.y, i,
253: i % 2 == 0 ? srcPixel[i / 2] : 0.0);
254: }
255: } else { // Real -> Real.
256: // NB This statement should be unreachable.
257: throw new RuntimeException(JaiI18N
258: .getString("DFTOpImage1"));
259: }
260: return;
261: }
262:
263: // Initialize to first non-unity length to be encountered.
264: fft.setLength(destRect.width > 1 ? getWidth() : getHeight());
265:
266: // Get some information about the source image.
267: int srcWidth = source.getWidth();
268: int srcHeight = source.getHeight();
269: int srcX = source.getMinX();
270: int srcY = source.getMinY();
271:
272: // Retrieve format tags.
273: RasterFormatTag[] formatTags = getFormatTags();
274:
275: RasterAccessor srcAccessor = new RasterAccessor(source,
276: new Rectangle(srcX, srcY, srcWidth, srcHeight),
277: formatTags[0], getSourceImage(0).getColorModel());
278: RasterAccessor dstAccessor = new RasterAccessor(dest, destRect,
279: formatTags[1], getColorModel());
280:
281: // Set data type flags.
282: int srcDataType = srcAccessor.getDataType();
283: int dstDataType = dstAccessor.getDataType();
284:
285: // Set pixel and line strides.
286: int srcPixelStride = srcAccessor.getPixelStride();
287: int srcScanlineStride = srcAccessor.getScanlineStride();
288: int dstPixelStride = dstAccessor.getPixelStride();
289: int dstScanlineStride = dstAccessor.getScanlineStride();
290: int dstPixelStrideImag = 1;
291: int dstLineStrideImag = destRect.width;
292: if (complexDst) {
293: dstPixelStrideImag = dstPixelStride;
294: dstLineStrideImag = dstScanlineStride;
295: }
296:
297: // Set indices and strides for image bands (real/imaginary).
298: int srcBandIndex = 0;
299: int srcBandStride = complexSrc ? 2 : 1;
300: int dstBandIndex = 0;
301: int dstBandStride = complexDst ? 2 : 1;
302:
303: // Get the number of components.
304: int numComponents = (complexDst ? dest.getSampleModel()
305: .getNumBands() / 2 : dest.getSampleModel()
306: .getNumBands());
307:
308: // Loop over the components.
309: for (int comp = 0; comp < numComponents; comp++) {
310: // Get the real source data for this component.
311: Object srcReal = srcAccessor.getDataArray(srcBandIndex);
312:
313: // Get the imaginary source data for this component if present.
314: Object srcImag = null;
315: if (complexSrc) {
316: srcImag = srcAccessor.getDataArray(srcBandIndex + 1);
317: }
318:
319: // Specify the destination components.
320: Object dstReal = dstAccessor.getDataArray(dstBandIndex);
321: Object dstImag = null;
322: if (complexDst) {
323: dstImag = dstAccessor.getDataArray(dstBandIndex + 1);
324: } else {
325: // Need to allocate an array for the entire band anyway
326: // even though the destination is real because it is needed
327: // for storage of the result of the row transforms.
328: if (dstDataType == DataBuffer.TYPE_FLOAT) {
329: dstImag = new float[destRect.width
330: * destRect.height];
331: } else {
332: dstImag = new double[destRect.width
333: * destRect.height];
334: }
335: }
336:
337: if (destRect.width > 1) {
338: // Set the FFT length.
339: fft.setLength(getWidth());
340:
341: // Initialize the source offsets for this component.
342: int srcOffsetReal = srcAccessor
343: .getBandOffset(srcBandIndex);
344: int srcOffsetImag = 0;
345: if (complexSrc) {
346: srcOffsetImag = srcAccessor
347: .getBandOffset(srcBandIndex + 1);
348: }
349:
350: // Initialize destination offsets and strides.
351: int dstOffsetReal = dstAccessor
352: .getBandOffset(dstBandIndex);
353: int dstOffsetImag = 0;
354: if (complexDst) {
355: dstOffsetImag = dstAccessor
356: .getBandOffset(dstBandIndex + 1);
357: }
358:
359: // Perform the row transforms.
360: for (int row = 0; row < srcHeight; row++) {
361: // Set the input data of the FFT.
362: fft.setData(srcDataType, srcReal, srcOffsetReal,
363: srcPixelStride, srcImag, srcOffsetImag,
364: srcPixelStride, srcWidth);
365:
366: // Calculate the DFT of the row.
367: fft.transform();
368:
369: // Get the output data of the FFT.
370: fft.getData(dstDataType, dstReal, dstOffsetReal,
371: dstPixelStride, dstImag, dstOffsetImag,
372: dstPixelStrideImag);
373:
374: // Increment the data offsets.
375: srcOffsetReal += srcScanlineStride;
376: srcOffsetImag += srcScanlineStride;
377: dstOffsetReal += dstScanlineStride;
378: dstOffsetImag += dstLineStrideImag;
379: }
380: }
381:
382: if (destRect.width == 1) { // destRect.height > 1
383: // NB 1) destRect.height has to be greater than one or this
384: // would be the degenerate case of a single point which is
385: // handled above. 2) There is no need to do setLength() on
386: // the FFT object here as the length will already have been
387: // set to the maximum of destRect.width amd destRect.height
388: // which must be destRect.height.
389:
390: // Initialize the source offsets for this component.
391: int srcOffsetReal = srcAccessor
392: .getBandOffset(srcBandIndex);
393: int srcOffsetImag = 0;
394: if (complexSrc) {
395: srcOffsetImag = srcAccessor
396: .getBandOffset(srcBandIndex + 1);
397: }
398:
399: // Initialize destination offsets and strides.
400: int dstOffsetReal = dstAccessor
401: .getBandOffset(dstBandIndex);
402: int dstOffsetImag = 0;
403: if (complexDst) {
404: dstOffsetImag = dstAccessor
405: .getBandOffset(dstBandIndex + 1);
406: }
407:
408: // Set the input data of the FFT.
409: fft.setData(srcDataType, srcReal, srcOffsetReal,
410: srcScanlineStride, srcImag, srcOffsetImag,
411: srcScanlineStride, srcHeight);
412:
413: // Calculate the DFT of the column.
414: fft.transform();
415:
416: // Get the output data of the FFT.
417: fft.getData(dstDataType, dstReal, dstOffsetReal,
418: dstScanlineStride, dstImag, dstOffsetImag,
419: dstLineStrideImag);
420: } else if (destRect.height > 1) { // destRect.width > 1
421: // Reset the FFT length.
422: fft.setLength(getHeight());
423:
424: // Initialize destination offsets and strides.
425: int dstOffsetReal = dstAccessor
426: .getBandOffset(dstBandIndex);
427: int dstOffsetImag = 0;
428: if (complexDst) {
429: dstOffsetImag = dstAccessor
430: .getBandOffset(dstBandIndex + 1);
431: }
432:
433: // Perform the column transforms.
434: for (int col = 0; col < destRect.width; col++) {
435: // Set the input data of the FFT.
436: fft.setData(dstDataType, dstReal, dstOffsetReal,
437: dstScanlineStride, dstImag, dstOffsetImag,
438: dstLineStrideImag, destRect.height);
439:
440: // Calculate the DFT of the column.
441: fft.transform();
442:
443: // Get the output data of the FFT.
444: fft.getData(dstDataType, dstReal, dstOffsetReal,
445: dstScanlineStride, complexDst ? dstImag
446: : null, dstOffsetImag,
447: dstLineStrideImag);
448:
449: // Increment the data offset.
450: dstOffsetReal += dstPixelStride;
451: dstOffsetImag += dstPixelStrideImag;
452: }
453: }
454:
455: // Increment the indices of the real bands in both images.
456: srcBandIndex += srcBandStride;
457: dstBandIndex += dstBandStride;
458: }
459:
460: if (dstAccessor.needsClamping()) {
461: dstAccessor.clampDataArrays();
462: }
463:
464: // Make sure that the output data is copied to the destination.
465: dstAccessor.copyDataToRaster();
466: }
467: }
|