/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. */ package org.geotools.renderer.lite.gridcoverage2d; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.RenderedImage; import java.awt.image.renderable.ParameterBlock; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.media.jai.Histogram; import javax.media.jai.ImageLayout; import javax.media.jai.JAI; import javax.media.jai.LookupTableJAI; import javax.media.jai.ParameterBlockJAI; import javax.media.jai.RenderedOp; import javax.media.jai.operator.ExtremaDescriptor; import javax.media.jai.operator.HistogramDescriptor; import javax.media.jai.operator.LookupDescriptor; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.factory.Hints; import org.geotools.image.ImageWorker; import org.geotools.referencing.piecewise.DefaultPiecewiseTransform1D; import org.geotools.referencing.piecewise.DefaultPiecewiseTransform1DElement; import org.geotools.referencing.piecewise.GenericPiecewise; import org.geotools.referencing.piecewise.MathTransform1DAdapter; import org.geotools.referencing.piecewise.PiecewiseTransform1D; import org.geotools.renderer.i18n.ErrorKeys; import org.geotools.renderer.i18n.Errors; import org.geotools.renderer.i18n.Vocabulary; import org.geotools.renderer.i18n.VocabularyKeys; import org.geotools.resources.image.ColorUtilities; import org.geotools.styling.ContrastEnhancement; import org.geotools.styling.StyleVisitor; import org.geotools.util.NumberRange; import org.geotools.util.SimpleInternationalString; import org.opengis.coverage.grid.GridCoverage; import org.opengis.filter.expression.Expression; import org.opengis.referencing.operation.TransformException; import org.opengis.util.InternationalString; /** * This implementations of {@link CoverageProcessingNode} takes care of the * {@link ContrastEnhancement} element of the SLD 1.0 spec. * * @author Simone Giannecchini, GeoSolutions * */ class ContrastEnhancementNode extends StyleVisitorCoverageProcessingNodeAdapter implements StyleVisitor, CoverageProcessingNode { /** * Minimal normalized value. */ private static final double MIN_VALUE = 0d; /** * Maximal normalized value. */ private static final double MAX_VALUE = 1d; /* * (non-Javadoc) * @see CoverageProcessingNode#getName() */ public InternationalString getName() { return Vocabulary.formatInternational(VocabularyKeys.CONTRAST_ENHANCEMENT); } /** * Specified the supported Histogram Enhancement algorithms. * * @todo in the future this should be pluggable. */ private final static Set<String> SUPPORTED_HE_ALGORITHMS; /** * This are the different types f histogram equalization that we support for * the moment. MOre should be added soon. * */ static { //load the contrast enhancement operations final HashSet<String> heAlg = new HashSet<String>(2, 1.0f); heAlg.add("NORMALIZE"); heAlg.add("HISTOGRAM"); heAlg.add("LOGARITHMIC"); heAlg.add("EXPONENTIAL"); SUPPORTED_HE_ALGORITHMS = Collections.unmodifiableSet(heAlg); //load, if it is needed the GenericPiecewise tranformation try{ new ParameterBlockJAI(GenericPiecewise.OPERATION_NAME); }catch (final Exception e) { GenericPiecewise.register(JAI.getDefaultInstance()); } } /** Histogram Enhancement algorithm to use. */ private String type = null; /** * Value we'll use for the gamma correction operation. */ private double gammaValue = Double.NaN; /* * (non-Javadoc) * * @see org.geotools.renderer.lite.gridcoverage2d.StyleVisitorAdapter#visit(org.geotools.styling.ContrastEnhancement) */ public synchronized void visit(final ContrastEnhancement ce) { // ///////////////////////////////////////////////////////////////////// // // Do nothing if we don't have a valid ContrastEnhancement element. This // would protect us against bad SLDs // // ///////////////////////////////////////////////////////////////////// if (ce == null) return; // ///////////////////////////////////////////////////////////////////// // // TYPE of the operation to perform // // ///////////////////////////////////////////////////////////////////// final Expression expType = ce.getType(); if (expType != null) { final String type = expType.evaluate(null, String.class); if (type != null) { this.type = type.toUpperCase(); if (!SUPPORTED_HE_ALGORITHMS.contains(type.toUpperCase())) throw new IllegalArgumentException(Errors.format(ErrorKeys.OPERATION_NOT_FOUND_$1, type.toUpperCase())); } } // ///////////////////////////////////////////////////////////////////// // // GAMMA // // ///////////////////////////////////////////////////////////////////// final Expression gamma = ce.getGammaValue(); if (gamma != null) { final Number number = gamma.evaluate(null, Double.class); if (number != null) { gammaValue = number.doubleValue(); // check the gamma value if (gammaValue < 0) throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "Gamma", number)); if (Double.isNaN(gammaValue) || Double.isInfinite(gammaValue)) throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "Gamma", number)); } } } /** * Default constructor */ public ContrastEnhancementNode() { this(null); } /** * Constructor for a {@link ContrastEnhancementNode} which allows to specify * a {@link Hints} instance to control internal factory machinery. * * @param hints * {@link Hints} instance to control internal factory machinery. */ public ContrastEnhancementNode(final Hints hints) { super( 1, hints, SimpleInternationalString.wrap("ContrastEnhancementNode"), SimpleInternationalString .wrap("Node which applies ContrastEnhancement following SLD 1.0 spec.")); } /* * (non-Javadoc) * * @see org.geotools.renderer.lite.gridcoverage2d.StyleVisitorCoverageProcessingNodeAdapter#execute() */ @SuppressWarnings("unchecked") protected GridCoverage2D execute() { assert Thread.holdsLock(this); final Hints hints = getHints(); // ///////////////////////////////////////////////////////////////////// // // Get the sources and see what we got to do. Note that if we have more // than once source we'll use only the first one but we'll // // ///////////////////////////////////////////////////////////////////// final List<CoverageProcessingNode> sources = this.getSources(); if (sources != null && !sources.isEmpty()) { final GridCoverage2D source = (GridCoverage2D) getSource(0).getOutput(); ensureSourceNotNull(source, this.getName().toString()); GridCoverage2D output; if ((!Double.isNaN(gammaValue) && !Double.isInfinite(gammaValue) && !(Math.abs(gammaValue -1)<1E-6))|| (type != null && type.length() > 0)) { // ///////////////////////////////////////////////////////////////////// // // We have a valid gamma value, let's go ahead. // // ///////////////////////////////////////////////////////////////////// final RenderedImage sourceImage = source.getRenderedImage(); // ///////////////////////////////////////////////////////////////////// // // PREPARATION // // ///////////////////////////////////////////////////////////////////// // // // // Get the source image and if necessary convert it to use a // ComponentColorModel. This way we are sure we will have a // visible image most part of the time. // // // //// // // @todo TODO HACK we need to convert to byte the image when going to // apply HISTOGRAM anyway // //// RenderedImage initialImage; if(type!=null&&type.equalsIgnoreCase("HISTOGRAM")) { initialImage = new ImageWorker(sourceImage) .setRenderingHints(hints) .forceComponentColorModel() .rescaleToBytes() .getRenderedImage(); } else { initialImage = new ImageWorker(sourceImage) .setRenderingHints(hints) .forceComponentColorModel() .getRenderedImage(); } final int numbands = initialImage.getSampleModel().getNumBands(); // // // // Save the alpha band if present, in order to put it back // later in the loop. We are not going to use it anyway for // the IHS conversion. // // // RenderedImage alphaBand = null; if (numbands % 2 == 0) { // get the alpha band alphaBand = new ImageWorker(initialImage) .setRenderingHints(hints).retainLastBand() .getRenderedImage(); // strip the alpha band from the original image initialImage = new ImageWorker(initialImage) .setRenderingHints(hints).retainBands(numbands - 1) .getRenderedImage(); } // // // // Get the single band to work on, which might be the // intensity for RGB(A) or the GRAY channel for Gray(A) // // // RenderedImage intensityImage = null; RenderedImage hChannel = null; RenderedImage sChannel = null; final boolean intensity; RenderedImage IHS = null; if (numbands > 1) { // convert the prepared image to IHS colorspace to work // on I band IHS = new ImageWorker(initialImage) .setRenderingHints(hints).forceColorSpaceIHS() .getRenderedImage(); // get the various singular bands intensityImage = new ImageWorker(IHS).setRenderingHints( hints).retainFirstBand().getRenderedImage(); sChannel = new ImageWorker(IHS).setRenderingHints(hints) .retainLastBand().getRenderedImage(); hChannel = new ImageWorker(IHS).setRenderingHints(hints) .retainBands(new int[] { 1 }).getRenderedImage(); intensity = true; } else { // // // // we have only one band we don't need to go to IHS // // // intensityImage = initialImage; intensity = false; } // ///////////////////////////////////////////////////////////////////// // // HISTOGRAM ENHANCEMENT // // // // ///////////////////////////////////////////////////////////////////// final RenderedImage heImage = performContrastEnhancement(intensityImage, hints); // ///////////////////////////////////////////////////////////////////// // // GAMMA CORRECTION // // Lookup for building the actual lut that caches the gamma // correction function's values. // // ///////////////////////////////////////////////////////////////////// RenderedImage finalImage = performGammaCorrection(heImage,hints); // ///////////////////////////////////////////////////////////////////// // // POSTPROCESSING // // Take care of the intermediated image we left back. This // means, handle the fact that we might have gone to IHS and // the alpha band. // // ///////////////////////////////////////////////////////////////////// if (intensity) { // // // // IHS --> RGB // // Let's merge the modified IHS image. The message on // the mailing list (see comments for this class) // mentioned that it is required to pass a RenderingHing // with a ImageLayout with the IHS color // model. // // // final ImageLayout imageLayout = new ImageLayout(); imageLayout.setColorModel(IHS.getColorModel()); imageLayout.setSampleModel(IHS.getSampleModel()); final RenderingHints rendHints = new RenderingHints(Collections.EMPTY_MAP); rendHints.add(hints); rendHints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT,imageLayout)); // merge and go to rgb again final ParameterBlock pb = new ParameterBlock(); pb.addSource(finalImage); pb.addSource(hChannel); pb.addSource(sChannel); finalImage = JAI.create("bandmerge", pb, rendHints); finalImage = new ImageWorker(finalImage).setRenderingHints(hints).forceColorSpaceRGB().getRenderedImage(); } // // // // ALPHA BAND // // Let's merge the alpha band with the image we have rebuilt. // // // if (alphaBand != null) { final ColorModel cm = new ComponentColorModel( numbands >= 3 ? ColorSpace .getInstance(ColorSpace.CS_sRGB) : ColorSpace .getInstance(ColorSpace.CS_GRAY), numbands >= 3 ? new int[] { 8, 8, 8, 8 } : new int[] { 8, 8 }, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); final ImageLayout imageLayout = new ImageLayout(); imageLayout.setColorModel(cm); imageLayout.setSampleModel(cm.createCompatibleSampleModel(finalImage.getWidth(), finalImage.getHeight())); // merge and go to rgb finalImage = new ImageWorker(finalImage) .setRenderingHints(hints) .setRenderingHint(JAI.KEY_IMAGE_LAYOUT,imageLayout) .addBand(alphaBand, false) .getRenderedOperation(); } // ///////////////////////////////////////////////////////////////////// // // OUTPUT // // ///////////////////////////////////////////////////////////////////// final int numSourceBands=source.getNumSampleDimensions(); final int numActualBands= finalImage.getSampleModel().getNumBands(); final GridCoverageFactory factory = getCoverageFactory(); final HashMap<Object,Object> props = new HashMap<Object,Object>(); if(source.getProperties() != null) { props.putAll(source.getProperties()); } if(numActualBands==numSourceBands) { final String name = "ce_coverage" + source.getName(); output = factory.create( name, finalImage, (GridGeometry2D)source.getGridGeometry(), source.getSampleDimensions(), new GridCoverage[]{source}, props); } else { // replicate input bands final GridSampleDimension sd[]= new GridSampleDimension[numActualBands]; for(int i=0;i<numActualBands;i++) sd[i]=(GridSampleDimension) source.getSampleDimension(0); output = factory.create( "ce_coverage"+source.getName().toString(), finalImage, (GridGeometry2D)source.getGridGeometry(), sd, new GridCoverage[]{source}, props); } } else // ///////////////////////////////////////////////////////////////////// // // We do not have a valid gamma value, let's try with a // conservative approach that is, let's forward the source // coverage intact. // // ///////////////////////////////////////////////////////////////////// output = source; return output; } throw new IllegalStateException(Errors.format( ErrorKeys.SOURCE_CANT_BE_NULL_$1, this.getName().toString())); } /** * Performs a contrast enhancement operation on the input image. Note that * not all the contrast enhancement operations have been implemented in a * way that is generic enough o handle all data types. * * @param inputImage the input {@link RenderedImage} to work on. * @param hints {@link Hints} to control the contrast enhancement process. * @return a {@link RenderedImage} on which a contrast enhancement has been performed. */ private RenderedImage performContrastEnhancement( RenderedImage inputImage, final Hints hints) { if (type != null && type.length() > 0) { assert inputImage.getSampleModel().getNumBands() == 1:inputImage; final int dataType=inputImage.getSampleModel().getDataType(); // ///////////////////////////////////////////////////////////////////// // // Histogram Normalization // // // // ///////////////////////////////////////////////////////////////////// if (type.equalsIgnoreCase("NORMALIZE")) { //step 1 do the extrema to get the statistics for this image final RenderedOp image = ExtremaDescriptor.create(inputImage, null, Integer.valueOf(1), Integer.valueOf(1), null, Integer.valueOf(1), null); final double[][] extrema = (double[][]) image .getProperty("extrema"); final int numBands = extrema[0].length; assert numBands == 1:image; // // // // Shortcut fr byte datatype // // // if(dataType==DataBuffer.TYPE_BYTE){ //// // // Optimisation for byte images, we use the lookup operation // //// if (extrema[1][0] == 255 && extrema[0][0] == 0) return inputImage; final double delta = extrema[1][0] - extrema[0][0]; final double scale = 255 / delta; final double offset = -scale * extrema[0][0]; //create the lookup table final byte[] lut = new byte[256]; for (int i = 1; i < lut.length; i++) lut[i] = (byte) (scale * i + offset + 0.5d); //do the actual lookup final LookupTableJAI lookup = new LookupTableJAI(lut); final ParameterBlock pb = new ParameterBlock(); pb.addSource(inputImage); pb.add(lookup); return JAI.create("lookup", pb, hints); } //// // // General case, we use the rescale in order to stretch the values to highest and lowest dim // //// //get the correct dim for this data type final double maximum=ColorUtilities.getMaximum(dataType); final double minimum=ColorUtilities.getMinimum(dataType); if (extrema[1][0] == maximum && extrema[0][0] == minimum) return inputImage; //compute the scale factors final double delta = extrema[1][0] - extrema[0][0]; final double scale = (maximum -minimum)/ delta; final double offset = minimum - scale * extrema[0][0]; //do the actual rescale final ParameterBlock pb = new ParameterBlock(); pb.addSource(inputImage); pb.add(new double []{scale}); pb.add(new double []{offset}); return JAI.create("rescale", pb, hints); } // ///////////////////////////////////////////////////////////////////// // // EXPONENTIAL Normalization // // // // ///////////////////////////////////////////////////////////////////// if (type.equalsIgnoreCase("EXPONENTIAL")) { if(dataType==DataBuffer.TYPE_BYTE){ //// // // Optimisation for byte images // //// final byte lut[] = new byte[256]; final double normalizationFactor=255.0; final double correctionFactor=255.0/(Math.E-1); for (int i = 1; i < lut.length; i++) lut[i] = (byte) (0.5f + correctionFactor * (Math.exp(i / normalizationFactor) - 1.0)); return LookupDescriptor.create(inputImage, new LookupTableJAI(lut), hints); } //// // // General case, we use the piecewise1D transform // //// // // STEP 1 do the extrema // //// //step 1 do the extrema to get the statistics for this image final RenderedOp statistics = ExtremaDescriptor.create(inputImage, null, Integer.valueOf(1), Integer.valueOf(1), null, Integer.valueOf(1), null); final double[] minimum=(double[]) statistics.getProperty("minimum"); final double[] maximum=(double[]) statistics.getProperty("maximum"); final double normalizationFactor=maximum[0]; final double correctionFactor=normalizationFactor/(Math.E-1); //// // // STEP 2 do the gamma correction by using generic piecewise // //// final DefaultPiecewiseTransform1DElement mainElement = DefaultPiecewiseTransform1DElement.create( "exponential-contrast-enhancement-transform", NumberRange.create(minimum[0],maximum[0]), new MathTransform1DAdapter() { /* * (non-Javadoc) * @see org.opengis.referencing.operation.MathTransform1D#derivative(double) */ public double derivative(double value) throws TransformException { throw new UnsupportedOperationException(Errors.format(ErrorKeys.UNSUPPORTED_OPERATION_$1)); } public boolean isIdentity() { return false; } /* * (non-Javadoc) * @see org.opengis.referencing.operation.MathTransform1D#transform(double) */ public double transform(double value) throws TransformException { value = correctionFactor*(Math.exp(value/normalizationFactor)-1); return value; } }); final PiecewiseTransform1D<DefaultPiecewiseTransform1DElement> transform = new DefaultPiecewiseTransform1D<DefaultPiecewiseTransform1DElement> ( new DefaultPiecewiseTransform1DElement[] {mainElement},0); final ParameterBlockJAI pbj = new ParameterBlockJAI( GenericPiecewise.OPERATION_NAME); pbj.addSource(inputImage); pbj.setParameter("Domain1D", transform); pbj.setParameter("bandIndex", Integer.valueOf(0)); return JAI.create( GenericPiecewise.OPERATION_NAME, pbj); } if (type.equalsIgnoreCase("LOGARITHMIC")) { // ///////////////////////////////////////////////////////////////////// // // Logarithm Normalization // // // // ///////////////////////////////////////////////////////////////////// if(dataType==DataBuffer.TYPE_BYTE){ //// // // Optimisation for byte images m we use lookup // //// final byte lut[] = new byte[256]; final double normalizationFactor=255.0; final double correctionFactor=100.0; for (int i = 1; i < lut.length; i++) lut[i] = (byte) (0.5f + normalizationFactor * Math.log((i * correctionFactor / normalizationFactor+ 1.0))); return LookupDescriptor.create(inputImage, new LookupTableJAI(lut), hints); } //// // // General case // //// //define a specific piecewise for the logarithm //// // // STEP 1 do the extrema // //// //step 1 do the extrema to get the statistics for this image final RenderedOp statistics = ExtremaDescriptor.create(inputImage, null, Integer.valueOf(1), Integer.valueOf(1), null, Integer.valueOf(1), null); final double[] minimum=(double[]) statistics.getProperty("minimum"); final double[] maximum=(double[]) statistics.getProperty("maximum"); final double normalizationFactor=maximum[0]; final double correctionFactor=100.0; //// // // STEP 2 do the gamma correction by using generic piecewise // //// final DefaultPiecewiseTransform1DElement mainElement = DefaultPiecewiseTransform1DElement.create( "logarithmic-contrast-enhancement-transform", NumberRange.create(minimum[0],maximum[0]), new MathTransform1DAdapter() { /* * (non-Javadoc) * @see org.opengis.referencing.operation.MathTransform1D#derivative(double) */ public double derivative(double value) throws TransformException { throw new UnsupportedOperationException(Errors.format(ErrorKeys.UNSUPPORTED_OPERATION_$1)); } public boolean isIdentity() { return false; } /* * (non-Javadoc) * @see org.opengis.referencing.operation.MathTransform1D#transform(double) */ public double transform(double value) throws TransformException { value =normalizationFactor*Math.log(1+(value*correctionFactor/normalizationFactor)); return value; } }); final PiecewiseTransform1D<DefaultPiecewiseTransform1DElement> transform = new DefaultPiecewiseTransform1D<DefaultPiecewiseTransform1DElement> ( new DefaultPiecewiseTransform1DElement[] {mainElement},0); final ParameterBlockJAI pbj = new ParameterBlockJAI( GenericPiecewise.OPERATION_NAME); pbj.addSource(inputImage); pbj.setParameter("Domain1D", transform); pbj.setParameter("bandIndex", Integer.valueOf(0)); return JAI.create( GenericPiecewise.OPERATION_NAME, pbj); } if (type.equalsIgnoreCase("HISTOGRAM")) { // ///////////////////////////////////////////////////////////////////// // // Histogram Equalization // // IT WORKS ONLY ON BYTE DATA TYPE!!! // // ///////////////////////////////////////////////////////////////////// //convert the input image to 8 bit inputImage=new ImageWorker(inputImage).rescaleToBytes().getRenderedImage(); // compute the histogram final RenderedOp hist = HistogramDescriptor.create(inputImage, null, Integer.valueOf(1), Integer.valueOf(1), new int[] { 256 }, new double[] { 0 }, new double[] { 256 }, null); final Histogram h = (Histogram) hist.getProperty("histogram"); // now compute the PDF and the CDF for the original image final byte[] cumulative = new byte[h.getNumBins(0)]; // sum of bins (we might have excluded 0 hence we cannot really // optimise) float totalBinSum = 0; for (int i = 0; i < cumulative.length; i++) { totalBinSum += h.getBinSize(0, i); } // this is the scale factor for the histogram equalization // process final float scale = (float) (h.getHighValue(0) - 1 - h.getLowValue(0))/ totalBinSum; float sum = 0; for (int i = 1; i < cumulative.length; i++) { sum += h.getBinSize(0, i - 1); cumulative[i] = (byte) ((sum * scale + h.getLowValue(0)) + .5F); } final LookupTableJAI lookup = new LookupTableJAI(cumulative); final ParameterBlock pb = new ParameterBlock(); pb.addSource(hist); pb.add(lookup); return JAI.create("lookup", pb, hints); } } return inputImage; } /** * Performs a gamma correction operation on the input image. * * @param inputImage the input {@link RenderedImage} to work on. * @param hints {@link Hints} to control the contrast enhancement process. * @return a {@link RenderedImage} on which a gamma correction has been performed. */ private RenderedImage performGammaCorrection( final RenderedImage inputImage, final Hints hints) { //note that we should work on a single band assert inputImage.getSampleModel().getNumBands() == 1:inputImage; final int dataType=inputImage.getSampleModel().getDataType(); RenderedImage result=inputImage; if (!Double.isNaN(gammaValue) && Math.abs(gammaValue - 1.0) > 1E-6) { if (dataType == DataBuffer.TYPE_BYTE) { //// // // Byte case, use lookup to optimize // //// final byte[] lut = new byte[256]; for (int i = 1; i < lut.length; i++) lut[i] = (byte) (255.0 * Math.pow(i / 255.0, gammaValue) + 0.5d); // apply the operation now final LookupTableJAI lookup = new LookupTableJAI(lut); final ParameterBlock pb = new ParameterBlock(); pb.addSource(inputImage); pb.add(lookup); result = JAI.create("lookup", pb, hints); } else { //// // // Generic case // //// // // STEP 1 do the extrema // //// //step 1 do the extrema to get the statistics for this image final RenderedOp statistics = ExtremaDescriptor.create(inputImage, null, Integer.valueOf(1), Integer.valueOf(1), null, Integer.valueOf(1), null); final double[] minimum=(double[]) statistics.getProperty("minimum"); final double[] maximum=(double[]) statistics.getProperty("maximum"); final double scale = (maximum[0]-minimum[0])/(MAX_VALUE-MIN_VALUE); final double offset = minimum[0] - MIN_VALUE*scale; //// // // STEP 2 do the gamma correction by using generic piecewise // //// final DefaultPiecewiseTransform1DElement mainElement = DefaultPiecewiseTransform1DElement.create( "gamma-correction-transform", NumberRange.create(minimum[0],maximum[0]), new MathTransform1DAdapter() { /* * (non-Javadoc) * @see org.opengis.referencing.operation.MathTransform1D#derivative(double) */ public double derivative(double value) throws TransformException { throw new UnsupportedOperationException(Errors.format(ErrorKeys.UNSUPPORTED_OPERATION_$1)); } public boolean isIdentity() { return false; } /* * (non-Javadoc) * @see org.opengis.referencing.operation.MathTransform1D#transform(double) */ public double transform(double value) throws TransformException { value = (value-offset)/scale; return offset+Math.pow(value, gammaValue)*scale; } }); final PiecewiseTransform1D<DefaultPiecewiseTransform1DElement> transform = new DefaultPiecewiseTransform1D<DefaultPiecewiseTransform1DElement> ( new DefaultPiecewiseTransform1DElement[] {mainElement},0); final ParameterBlockJAI pbj = new ParameterBlockJAI( GenericPiecewise.OPERATION_NAME); pbj.addSource(inputImage); pbj.setParameter("Domain1D", transform); pbj.setParameter("bandIndex", Integer.valueOf(0)); result = JAI.create( GenericPiecewise.OPERATION_NAME, pbj); } } assert result.getSampleModel().getNumBands() == 1:result; return result; } }