/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.jai.opimage; import com.sun.media.jai.opimage.RIFUtil; import javax.media.jai.BorderExtender; import javax.media.jai.ComponentSampleModelJAI; import javax.media.jai.KernelJAI; import javax.media.jai.PlanarImage; import javax.media.jai.RenderedOp; import javax.media.jai.TiledImage; import javax.media.jai.operator.BorderDescriptor; import javax.media.jai.operator.CropDescriptor; import javax.media.jai.operator.DFTDescriptor; import javax.media.jai.operator.FormatDescriptor; import javax.media.jai.operator.IDFTDescriptor; import javax.media.jai.operator.MultiplyComplexDescriptor; import javax.media.jai.operator.RescaleDescriptor; import java.awt.RenderingHints; import java.awt.image.DataBuffer; import java.awt.image.RenderedImage; import java.awt.image.renderable.ParameterBlock; import java.awt.image.renderable.RenderedImageFactory; public class DFTConvolveRIF implements RenderedImageFactory { boolean trace = false; public DFTConvolveRIF() { } /** * Create a new instance of ConvolveOpImage in the rendered layer. * This method satisfies the implementation of RIF. * * @param paramBlock The source image and the convolution kernel. */ public RenderedImage create(ParameterBlock paramBlock, RenderingHints renderHints) { BorderExtender extender = RIFUtil.getBorderExtenderHint(renderHints); KernelJAI kernel = (KernelJAI) paramBlock.getObjectParameter(0); RenderedImage kernelFT = (RenderedImage) paramBlock.getObjectParameter(1); RenderedImage sourceImage = (RenderedImage) paramBlock.getSource(0); printImageInfo(sourceImage, "sourceImage"); int iw = sourceImage.getWidth(); int ih = sourceImage.getHeight(); int kw = kernel.getWidth(); int kh = kernel.getHeight(); int iw2 = getNextBase2Size(iw + 2 * kw); int ih2 = getNextBase2Size(ih + 2 * kh); int leftPad = (iw2 - iw) / 2; int rightPad = (iw2 - iw) - leftPad; int topPad = (ih2 - ih) / 2; int bottomPad = (ih2 - ih) - topPad; RenderedImage extendedImage = BorderDescriptor.create(toFloat(sourceImage, renderHints), leftPad, rightPad, topPad, bottomPad, extender, null); printImageInfo(extendedImage, "extendedImage"); if (kernelFT == null) { TiledImage kernelImage = createKernelImage(extendedImage, extendedImage.getSampleModel().getDataType(), kernel); printImageInfo(kernelImage, "kernelImage"); kernelFT = DFTDescriptor.create(kernelImage, DFTDescriptor.SCALING_NONE, DFTDescriptor.REAL_TO_COMPLEX, null); } printImageInfo(kernelFT, "kernelFT"); RenderedOp sourceFT = DFTDescriptor.create(extendedImage, DFTDescriptor.SCALING_NONE, DFTDescriptor.REAL_TO_COMPLEX, null); printImageInfo(sourceFT, "sourceFT"); RenderedImage productFT = MultiplyComplexDescriptor.create(sourceFT, kernelFT, null); printImageInfo(productFT, "productFT"); RenderedImage convolvedImage = IDFTDescriptor.create(productFT, DFTDescriptor.SCALING_DIMENSIONS, DFTDescriptor.COMPLEX_TO_REAL, null); printImageInfo(convolvedImage, "convolvedImage"); RenderedOp croppedImage = CropDescriptor.create(convolvedImage, 0.0f, 0.0f, (float) iw, (float) ih, null); croppedImage.setProperty("kernelFT", kernelFT); printImageInfo(croppedImage, "croppedImage"); return croppedImage; } public static TiledImage createKernelImage(RenderedImage sourceImage, int dataType, KernelJAI kernel) { float[] kernelData = kernel.getKernelData(); if (dataType != DataBuffer.TYPE_FLOAT && dataType != DataBuffer.TYPE_DOUBLE) { throw new IllegalArgumentException("dataType"); } ComponentSampleModelJAI sm = new ComponentSampleModelJAI(dataType, sourceImage.getTileWidth(), sourceImage.getTileHeight(), 1, sourceImage.getTileWidth(), new int[]{0}); int iw = sourceImage.getWidth(); int ih = sourceImage.getHeight(); int ix0 = sourceImage.getMinX(); int iy0 = sourceImage.getMinY(); TiledImage kernelImage = new TiledImage(ix0, iy0, iw, ih, sourceImage.getTileGridXOffset(), sourceImage.getTileGridYOffset(), sm, PlanarImage.createColorModel(sm)); kernelData = normalizeKernelData(kernelData); int kw = kernel.getWidth(); int kx0 = kernel.getXOrigin(); int ky0 = kernel.getYOrigin(); // Upper for (int y = 0; y <= ky0; y++) { int ky = y + ky0; // Left for (int x = 0; x <= kx0; x++) { int kx = x + kx0; kernelImage.setSample(ix0 + x, iy0 + y, 0, kernelData[ky * kw + kx]); } // Right for (int x = iw - kx0; x < iw; x++) { int kx = x - (iw - kx0); kernelImage.setSample(ix0 + x, iy0 + y, 0, kernelData[ky * kw + kx]); } } // Lower for (int y = ih - ky0; y < ih; y++) { int ky = y - (ih - ky0); // Left for (int x = 0; x <= kx0; x++) { int kx = x + kx0; kernelImage.setSample(ix0 + x, iy0 + y, 0, kernelData[ky * kw + kx]); } // Right for (int x = iw - kx0; x < iw; x++) { int kx = x - (iw - kx0); kernelImage.setSample(ix0 + x, iy0 + y, 0, kernelData[ky * kw + kx]); } } return kernelImage; } public static float[] normalizeKernelData(float[] kernelData) { float[] clone = kernelData.clone(); float sum = 0; for (int i = 0; i < kernelData.length; i++) { sum += clone[i]; } for (int i = 0; i < kernelData.length; i++) { clone[i] /= sum; } return clone; } public static RenderedImage toFloat(RenderedImage sourceImage, RenderingHints hints) { int type = sourceImage.getSampleModel().getDataType(); if (type == DataBuffer.TYPE_BYTE || type == DataBuffer.TYPE_INT) { return toFloat(sourceImage, 0.0, 255.0, 0.0, 1.0, hints); } else if (type == DataBuffer.TYPE_SHORT) { return toFloat(sourceImage, Short.MIN_VALUE, Short.MAX_VALUE, 0.0, 1.0, hints); } else if (type == DataBuffer.TYPE_USHORT) { return toFloat(sourceImage, 0, 2 * Short.MAX_VALUE + 1, 0.0, 1.0, hints); } else { return sourceImage; } } public static RenderedImage toFloat(RenderedImage sourceImage, double x1, double x2, double y1, double y2, RenderingHints hints) { double a = (y2 - y1) / (x2 - x1); double b = y1 - a * x1; return RescaleDescriptor.create(FormatDescriptor.create(sourceImage, DataBuffer.TYPE_FLOAT, hints), new double[]{a}, new double[]{b}, hints); } public static int getNextBase2Size(int n) { if (n <= 0) { throw new IllegalArgumentException("n <= 0"); } if (Integer.bitCount(n) == 1) { return n; } return (int) Math.pow(2.0, 1.0 + Math.floor(Math.log(n) / Math.log(2.0))); } private void printImageInfo(RenderedImage sourceImage, String name) { if (trace) { System.out.println(name + ":"); System.out.println(" minX = " + sourceImage.getMinX()); System.out.println(" minY = " + sourceImage.getMinY()); System.out.println(" width = " + sourceImage.getWidth()); System.out.println(" height = " + sourceImage.getHeight()); System.out.println(" tileWidth = " + sourceImage.getTileWidth()); System.out.println(" tileHeight = " + sourceImage.getTileHeight()); } } }