Search in sources :

Example 1 with Palette

use of org.geotoolkit.image.palette.Palette in project geotoolkit by Geomatys.

the class SpatialImageReader method getImageType.

/**
 * Returns an image type specifier indicating the {@link SampleModel} and {@link ColorModel}
 * to use for reading the image. In addition, this method also detects if some conversions
 * (represented by {@link SampleConverter} instances) are required in order to store the
 * sample values using the selected models. The conversions (if any) are keept as small as
 * possible, but are sometime impossible to avoid for example because {@link IndexColorModel}
 * does not allow negative sample values.
 * <p>
 * The default implementation applies the following steps:
 *
 * <ol>
 *   <li><p>The {@linkplain SampleDimension#getValidSampleValues() range of expected values}
 *       and the {@linkplain SampleDimension#getFillSampleValues() fill values} are extracted
 *       from the {@linkplain #getImageMetadata(int) image metadata}, if any.</p></li>
 *
 *   <li><p>If the given {@code parameters} argument is an instance of {@link SpatialImageReadParam},
 *       then the user-supplied {@linkplain SpatialImageReadParam#getPaletteName palette name}
 *       is fetched. Otherwise or if no palette name was explicitly set, then this method default
 *       to {@value org.geotoolkit.image.io.SpatialImageReadParam#DEFAULT_PALETTE_NAME}. The
 *       palette name will be used in order to {@linkplain PaletteFactory#getColors(String)
 *       read a predefined set of colors} (as [A]RGB values) to be given to the
 *       {@linkplain IndexColorModel index color model}.</p></li>
 *
 *   <li><p>If the {@linkplain #getRawDataType raw data type} is {@link DataBuffer#TYPE_FLOAT
 *       TYPE_FLOAT} or {@link DataBuffer#TYPE_DOUBLE TYPE_DOUBLE}, then this method builds
 *       a {@linkplain PaletteFactory#getContinuousPalette continuous palette} suitable for
 *       the range fetched at step 1. The data are assumed <cite>geophysics</cite> values
 *       rather than some packed values. Consequently, the {@linkplain SampleConverter sample
 *       converters} will replace no-data values by {@linkplain Float#NaN NaN}, but no other
 *       changes will be applied.</p></li>
 *
 *   <li><p>Otherwise, if the {@linkplain #getRawDataType raw data type} is a unsigned integer type
 *       like {@link DataBuffer#TYPE_BYTE TYPE_BYTE} or {@link DataBuffer#TYPE_USHORT TYPE_USHORT},
 *       then this method builds an {@linkplain PaletteFactory#getPalette indexed palette} (i.e. a
 *       palette backed by an {@linkplain IndexColorModel index color model}) with just the minimal
 *       {@linkplain IndexColorModel#getMapSize size} needed for containing fully the range and the
 *       no-data values fetched at step 1. The data are assumed <cite>packed</cite> values rather
 *       than geophysics values. Consequently, the {@linkplain SampleConverter sample converters}
 *       will be the {@linkplain SampleConverter#IDENTITY identity converter} except in the
 *       following cases:
 *       <ul>
 *         <li>The {@linkplain SampleDimension#getValidSampleValues() range of valid values} is
 *             outside the range allowed by the {@linkplain #getRawDataType raw data type} (e.g.
 *             the range of valid values contains negative integers). In this case, the sample
 *             converter will shift the values to a strictly positive range and replace fill
 *             values by 0.</li>
 *         <li>At least one {@linkplain SampleDimension#getFillSampleValues() fill value} is
 *             outside the range of values allowed by the {@linkplain #getRawDataType raw data
 *             type}. In this case, this method will try to only replace the fill values by 0,
 *             without shifting the valid values if this shift can be avoided.</li>
 *         <li>At least one {@linkplain SampleDimension#getFillSampleValues() fill value} is
 *             far away from the {@linkplain SampleDimension#getValidSampleValues() range of
 *             valid values} (for example 9999 while the range of valid values is [0&hellip;255]).
 *             The meaning of "far away" is determined by the {@link #collapseNoDataValues
 *             collapseNoDataValues} method.</li>
 *       </ul>
 *       </p></li>
 *
 *   <li><p>Otherwise, if the {@linkplain #getRawDataType raw data type} is a signed integer
 *       type like {@link DataBuffer#TYPE_SHORT TYPE_SHORT}, then this method builds an
 *       {@linkplain PaletteFactory#getPalette indexed palette} with the maximal {@linkplain
 *       IndexColorModel#getMapSize size} supported by the raw data type (note that this is
 *       memory expensive - typically 256 kilobytes). Negative values will be stored in their
 *       two's complement binary form in order to fit in the range of positive integers
 *       supported by the {@linkplain IndexColorModel index color model}.</p></li>
 * </ol>
 *
 * {@section Using the Sample Converters}
 * If the {@code converters} argument is non-null, then this method will store the
 * {@link SampleConverter} instances in the supplied array. The array length shall be equals
 * to the number of {@linkplain ImageReadParam#getSourceBands() source} and
 * {@linkplain ImageReadParam#getDestinationBands() destination bands}.
 * <p>
 * The converters shall be used by {@link #read(int,ImageReadParam) read} method
 * implementations for converting the values read in the datafile to values acceptable
 * by the {@linkplain ColorModel color model}. See the
 * {@link #getDestination(int, ImageReadParam, int, int, SampleConverter[]) getDestination}
 * method for code example.
 *
 * {@section Overriding this method}
 * Subclasses can override this method for example if the color {@linkplain Palette palette}
 * and range of values should be computed in a different way. The example below creates an
 * image type using hard-coded objects:
 *
 * {@preformat java
 *     int minimum     = -2000;      // Minimal expected value
 *     int maximum     = +2300;      // Maximal expected value
 *     int fillValue   = -9999;      // Value for missing data
 *     String colors   = "rainbow";  // Named set of RGB colors
 *     converters[0]   = SampleConverter.createOffset(1 - minimum, fillValue);
 *     Palette palette = PaletteFactory.getDefault().getPalettePadValueFirst(colors, maximum - minimum);
 *     return palette.getImageTypeSpecifier();
 * }
 *
 * @param imageIndex
 *          The index of the image to be queried.
 * @param parameters
 *          The user-supplied parameters, or {@code null}. Note: we recommend to supply
 *          {@link #getDefaultReadParam} instead of {@code null} since subclasses may
 *          override the later with default values suitable to a particular format.
 * @param converters
 *          If non-null, an array where to store the converters created by this method.
 *          The length of this array shall be equals to the number of target bands.
 * @return
 *          The image type (never {@code null}).
 * @throws IOException
 *          If an error occurs while reading the format information from the input source.
 *
 * @see #getRawDataType
 * @see #collapseNoDataValues
 * @see #getDestination(int, ImageReadParam, int, int, SampleConverter[])
 */
@SuppressWarnings("fallthrough")
protected ImageTypeSpecifier getImageType(final int imageIndex, final ImageReadParam parameters, final SampleConverter[] converters) throws IOException {
    /*
         * Extracts all informations we will need from the user-supplied parameters, if any.
         * Note: the number of bands in the target image (as requested by the caller)
         * may be different than the number of bands in the source image (on disk).
         */
    final ImageTypeSpecifier userType;
    final String paletteName;
    final int[] sourceBands;
    final int[] targetBands;
    final int visibleBand;
    final int numBands;
    if (parameters != null) {
        sourceBands = parameters.getSourceBands();
        targetBands = parameters.getDestinationBands();
        userType = parameters.getDestinationType();
    } else {
        sourceBands = null;
        targetBands = null;
        userType = null;
    }
    if (sourceBands != null) {
        // == targetBands.length (assuming valid ImageReadParam).
        numBands = sourceBands.length;
    } else if (targetBands != null) {
        numBands = targetBands.length;
    } else {
        numBands = getNumBands(imageIndex);
    }
    List<? extends SampleDomain> bands = null;
    if (parameters instanceof SpatialImageReadParam) {
        final SpatialImageReadParam geoparam = (SpatialImageReadParam) parameters;
        paletteName = geoparam.getNonNullPaletteName();
        visibleBand = geoparam.getVisibleBand();
        bands = geoparam.getSampleDomains();
    } else {
        paletteName = SpatialImageReadParam.DEFAULT_PALETTE_NAME;
        visibleBand = 0;
    }
    /*
         * Gets the band metadata. If the user specified explicitly a SampleDomain in the
         * parameters, this is all the information we need - so we can avoid the cost of
         * querying IIOMetadata. Otherwise we will need to extract the image IIOMetadata.
         */
    boolean convertBandIndices = false;
    if (bands == null) {
        final SpatialMetadata metadata;
        final boolean oldIgnore = ignoreMetadata;
        try {
            ignoreMetadata = false;
            metadata = getImageMetadata(imageIndex);
        } finally {
            ignoreMetadata = oldIgnore;
        }
        if (metadata != null) {
            final List<SampleDimension> sd = metadata.getListForType(SampleDimension.class);
            if (!isNullOrEmpty(sd)) {
                convertBandIndices = (sourceBands != null);
                bands = sd;
            }
        }
    }
    /*
         * Gets the data type, and check if we should replace it by an other type. Type
         * replacements are allowed only if the appropriate SampleConversionType enum is set.
         */
    boolean replaceFillValues = false;
    int dataType = (userType != null) ? userType.getSampleModel().getDataType() : getRawDataType(imageIndex);
    if (userType == null && parameters instanceof SpatialImageReadParam) {
        final SpatialImageReadParam geoparam = (SpatialImageReadParam) parameters;
        switch(dataType) {
            case DataBuffer.TYPE_SHORT:
                {
                    if (geoparam.isSampleConversionAllowed(SHIFT_SIGNED_INTEGERS)) {
                        dataType = DataBuffer.TYPE_USHORT;
                    }
                // Fall through
                }
            case DataBuffer.TYPE_USHORT:
            case DataBuffer.TYPE_INT:
            case DataBuffer.TYPE_BYTE:
                {
                    if (bands == null || !geoparam.isSampleConversionAllowed(STORE_AS_FLOATS)) {
                        break;
                    }
                    boolean hasFillValues = false;
                    for (final SampleDomain domain : bands) {
                        final double[] fillValues = domain.getFillSampleValues();
                        if (fillValues != null && fillValues.length != 0) {
                            hasFillValues = true;
                            break;
                        }
                    }
                    if (!hasFillValues) {
                        break;
                    }
                    dataType = DataBuffer.TYPE_FLOAT;
                // Fall through
                }
            case DataBuffer.TYPE_FLOAT:
            case DataBuffer.TYPE_DOUBLE:
                {
                    replaceFillValues = geoparam.isSampleConversionAllowed(REPLACE_FILL_VALUES);
                }
        }
    }
    /*
         * Gets the minimal and maximal values allowed for the target image type.
         * Note that this is meanless for floating point types, so the values in
         * that case are arbitrary.
         *
         * The only integer types that are signed are SHORT (not to be confused with
         * USHORT) and INT. Other types like BYTE and USHORT are treated as unsigned.
         */
    final boolean isFloat;
    final long floor, ceil;
    switch(dataType) {
        // Actually we don't really know what to do for this case...
        case DataBuffer.TYPE_UNDEFINED:
        // Fall through since we can treat this case as float.
        case DataBuffer.TYPE_DOUBLE:
        case DataBuffer.TYPE_FLOAT:
            {
                isFloat = true;
                floor = Long.MIN_VALUE;
                ceil = Long.MAX_VALUE;
                break;
            }
        case DataBuffer.TYPE_INT:
            {
                isFloat = false;
                floor = Integer.MIN_VALUE;
                ceil = Integer.MAX_VALUE;
                break;
            }
        case DataBuffer.TYPE_SHORT:
            {
                isFloat = false;
                floor = Short.MIN_VALUE;
                ceil = Short.MAX_VALUE;
                break;
            }
        default:
            {
                isFloat = false;
                floor = 0;
                ceil = (1L << DataBuffer.getDataTypeSize(dataType)) - 1;
                break;
            }
    }
    /*
         * Computes a range of values for all bands, as the union in order to make sure that
         * we can stores every sample values. Also creates SampleConverters in the process.
         * The later is an opportunist action since we gather most of the needed information
         * during the loop.
         */
    NumberRange<?> allRanges = null;
    NumberRange<?> visibleRange = null;
    SampleConverter visibleConverter = SampleConverter.IDENTITY;
    // Only in the visible band, and must be positive.
    double maximumFillValue = 0;
    if (bands != null) {
        // To be created only if needed.
        MetadataHelper helper = null;
        // Never 0 - check was performed above.
        final int numMetadataBands = bands.size();
        for (int i = 0; i < numBands; i++) {
            int bandIndex = convertBandIndices ? sourceBands[i] : i;
            if (bandIndex < 0 || bandIndex >= numMetadataBands) {
                if (numMetadataBands != 1) {
                    // If there is exactly one metadata band, don't log any warning since
                    // we will assume that the metadata band apply to all data bands.
                    Warnings.log(this, null, SpatialImageReader.class, "getImageType", indexOutOfBounds(bandIndex, 0, numMetadataBands));
                }
                bandIndex = numMetadataBands - 1;
            }
            /*
                 * Before to get the range, get the fill values with maximal precision.
                 * Some values may need to be casted from 'double' to 'float' in order
                 * to match the sample values in the raster. This cast to various types
                 * will be performed internally by the SampleConverter implementations.
                 */
            final SampleDomain band = bands.get(bandIndex);
            final double[] fillValues = band.getFillSampleValues();
            final NumberRange<?> range;
            if (band instanceof SampleDimension) {
                if (helper == null) {
                    helper = new MetadataHelper(this);
                }
                range = helper.getValidSampleValues(bandIndex, (SampleDimension) band, fillValues);
            } else {
                range = band.getValidSampleValues();
            }
            double minimum, maximum;
            if (range != null) {
                minimum = range.getMinDouble();
                maximum = range.getMaxDouble();
                if (!isFloat) {
                    // treat as if we use the maximal range allowed by the data type.
                    if (minimum == Double.NEGATIVE_INFINITY)
                        minimum = floor;
                    if (maximum == Double.POSITIVE_INFINITY)
                        maximum = ceil;
                }
                final double extent = maximum - minimum;
                if (extent >= 0 && (isFloat || extent <= (ceil - floor))) {
                    allRanges = (allRanges != null) ? allRanges.unionAny(range) : range;
                } else {
                    // Use range.getMin/MaxValue() because they may be integers rather than doubles.
                    Warnings.log(this, null, SpatialImageReader.class, "getImageType", Errors.Keys.IllegalRange_2, range.getMinValue(), range.getMaxValue());
                    continue;
                }
            } else {
                minimum = Double.NaN;
                maximum = Double.NaN;
            }
            final int targetBand = (targetBands != null) ? targetBands[i] : i;
            /*
                 * For floating point types, replaces no-data values by NaN because the floating
                 * point numbers are typically used for geophysics data, so the raster is likely
                 * to be a "geophysics" view for GridCoverage2D. All other values are stored "as
                 * is" without any offset.
                 *
                 * For integer types, if the range of values from the source data file fits into
                 * the range of values allowed by the destination raster, we will use an identity
                 * converter. If the only required conversion is a shift from negative to positive
                 * values, creates an offset converter with no-data values collapsed to 0.
                 */
            final SampleConverter converter;
            if (isFloat) {
                // If the sample values are float values, we need to replace 99.99 fill value
                // (for example) by 99.99f, which is 99.98999786376953 in double precision,
                // otherwise the SampleConverter may not find them (denpending which method
                // is invoked). This cast is done by the PadValueMask constructor.
                converter = replaceFillValues ? SampleConverter.createPadValuesMask(fillValues) : SampleConverter.IDENTITY;
            } else {
                final boolean isZeroValid = (minimum <= 0 && maximum >= 0);
                boolean collapsePadValues = false;
                if (fillValues != null && fillValues.length != 0) {
                    final double[] sorted = fillValues.clone();
                    Arrays.sort(sorted);
                    double minFill = sorted[0];
                    double maxFill = minFill;
                    int indexMax = sorted.length;
                    while (--indexMax != 0 && Double.isNaN(maxFill = sorted[indexMax])) ;
                    assert minFill <= maxFill || Double.isNaN(minFill) : maxFill;
                    if (targetBand == visibleBand && maxFill > maximumFillValue) {
                        maximumFillValue = maxFill;
                    }
                    if (minFill < floor || maxFill > ceil) {
                        // At least one fill value is outside the range of acceptable values.
                        collapsePadValues = true;
                    } else if (minimum >= 0) {
                        /*
                             * Arbitrary optimization of memory usage:  if there is a "large" empty
                             * space between the range of valid values and a no-data value, then we
                             * may (at subclass implementors choice) collapse the no-data values to
                             * zero in order to avoid wasting the empty space.  Note that we do not
                             * perform this collapse if the valid range contains negative values
                             * because it would not save any memory. We do not check the no-data
                             * values between 0 and 'minimum' for the same reason.
                             */
                        int k = Arrays.binarySearch(sorted, maximum);
                        if (// We want the first element greater than maximum.
                        k >= 0)
                            // We want the first element greater than maximum.
                            k++;
                        else
                            // Really ~ operator, not -
                            k = ~k;
                        if (k <= indexMax) {
                            double unusedSpace = Math.max(sorted[k] - maximum - 1, 0);
                            while (++k <= indexMax) {
                                final double delta = sorted[k] - sorted[k - 1] - 1;
                                if (delta > 0) {
                                    unusedSpace += delta;
                                }
                            }
                            final int unused = (int) Math.min(Math.round(unusedSpace), Integer.MAX_VALUE);
                            collapsePadValues = collapseNoDataValues(isZeroValid, sorted, unused);
                        // We invoked 'collapseNoDataValues' unconditionally even if
                        // 'unused' is zero because the user may decide on the basis
                        // of other criterions, like 'isZeroValid'.
                        }
                    }
                }
                if (minimum < floor || maximum > ceil) {
                    // The range of valid values is outside the range allowed by raw data type.
                    converter = SampleConverter.createOffset(Math.ceil(1 - minimum), fillValues);
                } else if (collapsePadValues) {
                    if (isZeroValid) {
                        // We need to collapse the no-data values to 0, but it causes a clash
                        // with the range of valid values. So we also shift the later.
                        converter = SampleConverter.createOffset(Math.ceil(1 - minimum), fillValues);
                    } else {
                        // We need to collapse the no-data values and there is no clash.
                        converter = SampleConverter.createPadValuesMask(fillValues);
                    }
                } else {
                    /*
                         * Do NOT take 'fillValues' in account if there is no need to collapse
                         * them. This is not the converter's job to transform "packed" values to
                         * "geophysics" values. We just want them to fit in the IndexColorModel,
                         * and they already fit. So the identity converter is appropriate even
                         * in presence of pad values.
                         */
                    converter = SampleConverter.IDENTITY;
                }
            }
            if (converters != null && i < converters.length) {
                converters[i] = converter;
            }
            if (targetBand == visibleBand) {
                visibleConverter = converter;
                visibleRange = range;
            }
        }
    }
    /*
         * Ensure that all converters are defined. We typically have no converter if there
         * is no "ImageDescription/Dimensions" metadata. If the user specified explicitly
         * the image type, then we are done.
         */
    if (converters != null) {
        for (int i = Math.min(converters.length, numBands); --i >= 0; ) {
            if (converters[i] == null) {
                converters[i] = visibleConverter;
            }
        }
    }
    if (userType != null) {
        return userType;
    }
    /*
         * Creates a color palette suitable for the range of values in the visible band.
         * The case for floating points is the simplest: we should not have any offset,
         * at most a replacement of no-data values. In the case of integer values, we
         * must make sure that the indexed color map is large enough for containing both
         * the highest data value and the highest no-data value.
         */
    if (visibleRange == null) {
        visibleRange = (allRanges != null) ? allRanges : NumberRange.create(floor, true, ceil, true);
    }
    PaletteFactory factory = null;
    if (parameters instanceof SpatialImageReadParam) {
        factory = ((SpatialImageReadParam) parameters).getPaletteFactory();
    }
    if (factory == null) {
        factory = PaletteFactory.getDefault();
    }
    factory.setWarningLocale(locale);
    final double minimum = visibleRange.getMinDouble();
    final double maximum = visibleRange.getMaxDouble();
    final Palette palette;
    if (isFloat) {
        assert visibleConverter.getOffset() == 0 : visibleConverter;
        palette = factory.getContinuousPalette(paletteName, (float) minimum, (float) maximum, dataType, numBands, visibleBand);
    } else {
        final double offset = visibleConverter.getOffset();
        long lower, upper;
        if (minimum == Double.NEGATIVE_INFINITY) {
            lower = floor;
        } else {
            lower = Math.round(minimum + offset);
            if (!visibleRange.isMinIncluded()) {
                // Must be inclusive
                lower++;
            }
        }
        if (maximum == Double.POSITIVE_INFINITY) {
            upper = ceil;
        } else {
            upper = Math.round(maximum + offset);
            if (visibleRange.isMaxIncluded()) {
                // Must be exclusive
                upper++;
            }
        }
        long size = Math.max(upper, Math.round(maximumFillValue) + 1);
        if (lower < 0) {
            size -= lower;
        }
        /*
             * The target lower, upper and size parameters are usually in the range of SHORT
             * or USHORT data type.  The Palette class will perform the necessary checks and
             * throw an exception if those variables are out of range. However we may have
             * values out of this range for TYPE_INT, in which case we will use the same slow
             * color model than the one for floating point values.
             */
        if (lower >= Short.MIN_VALUE && (lower + size) <= (lower >= 0 ? IndexedPalette.MAX_UNSIGNED + 1 : Short.MAX_VALUE + 1)) {
            palette = factory.getPalette(paletteName, (int) lower, (int) upper, (int) size, numBands, visibleBand);
        } else {
            palette = factory.getContinuousPalette(paletteName, lower, upper, dataType, numBands, visibleBand);
        }
    }
    return palette.getImageTypeSpecifier();
}
Also used : IndexedPalette(org.geotoolkit.image.palette.IndexedPalette) Palette(org.geotoolkit.image.palette.Palette) SpatialMetadata(org.geotoolkit.image.io.metadata.SpatialMetadata) PaletteFactory(org.geotoolkit.image.palette.PaletteFactory) SampleDimension(org.geotoolkit.image.io.metadata.SampleDimension) SampleDomain(org.geotoolkit.image.io.metadata.SampleDomain) ImageTypeSpecifier(javax.imageio.ImageTypeSpecifier) MetadataHelper(org.geotoolkit.image.io.metadata.MetadataHelper)

Example 2 with Palette

use of org.geotoolkit.image.palette.Palette in project geotoolkit by Geomatys.

the class PaletteCreatorDemo method main.

/**
 * @param args the command line arguments
 */
public static void main(String[] args) throws FileNotFoundException, IOException {
    final Palette palette = PALETTE_FACTORY.getPalette(PALETTE_NAME, NB_COLORS);
    final IndexColorModel icm = (IndexColorModel) palette.getColorModel();
    for (int i = 0; i < NB_COLORS; i++) {
        final Color color = new Color(icm.getRGB(i));
        final String hexColor = Integer.toHexString(color.getRGB()).substring(2);
        System.out.println("RGB for " + i + " : " + color + " | hexadecimal : #" + hexColor);
    }
}
Also used : Palette(org.geotoolkit.image.palette.Palette) Color(java.awt.Color) IndexColorModel(java.awt.image.IndexColorModel)

Example 3 with Palette

use of org.geotoolkit.image.palette.Palette in project geotoolkit by Geomatys.

the class DefaultJenks method evaluate.

@Override
public Object evaluate(Object object, Class context) {
    if (object instanceof RenderedImage) {
        final RenderedImage image = (RenderedImage) object;
        final int dataType = image.getSampleModel().getDataType();
        final Raster data = image.getData();
        int classes = (Integer) this.classNumber.getValue();
        final int numBands = data.getNumBands();
        final int width = data.getWidth();
        final int height = data.getHeight();
        final Set<Double> values = new TreeSet<Double>();
        double[] pixel = new double[numBands];
        Double key = Double.NaN;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                data.getPixel(x, y, pixel);
                // arbitrary only get the value ofthe first band
                // TODO add bandIndex input parameter in Jenks function
                key = Double.valueOf(pixel[0]);
                // bypass noData values
                if (Arrays.binarySearch(noData, key) < 0 && !values.contains(key)) {
                    values.add(key);
                }
            }
        }
        // prevent classification errors if requested classes is superior to computable classe number.
        final int computableClasses = values.size();
        if (classes > computableClasses) {
            classes = computableClasses;
            LOGGER.log(Level.WARNING, "Not enough distinct data to compute the requested number of class. Jenks will be computed for {0} classes.", classes);
        }
        final double[] pixelValues = new double[values.size()];
        int index = 0;
        for (Double val : values) {
            pixelValues[index] = val.doubleValue();
            index++;
        }
        // compute classes
        final Classification classification = new Classification();
        classification.setData(pixelValues);
        classification.setClassNumber(classes);
        classification.computeJenks(false);
        final int[] indexes = classification.getIndex();
        // create palette
        final List<Color> colors = new ArrayList<Color>();
        try {
            final Palette palette = PALETTE_FACTORY.getPalette((String) paletteName.getValue(), classes);
            final IndexColorModel icm = (IndexColorModel) palette.getColorModel();
            for (int i = 0; i < classes; i++) {
                colors.add(new Color(icm.getRGB(i)));
            }
        } catch (IOException ex) {
            LOGGER.log(Level.WARNING, "Palette not found.", ex);
        }
        colorMap.clear();
        colorMap.put(Double.NEGATIVE_INFINITY, new Color(0, 0, 0, 0));
        for (int i = 0; i < indexes.length; i++) {
            colorMap.put(pixelValues[indexes[i] - 1], colors.get(i));
        }
        for (int i = 0; i < noData.length; i++) {
            colorMap.put(noData[i], new Color(0, 0, 0, 0));
        }
        /*
             * HACK byte -> no-data = 255 else no-data = Double.NaN
             * TODO find more elegant way to support no-data values.
             */
        if (dataType == DataBuffer.TYPE_BYTE) {
            colorMap.put(255.0, new Color(0, 0, 0, 0));
        }
        final ColorModel originColorModel = image.getColorModel();
        final ColorModel newColorModel = new CompatibleColorModel(originColorModel.getPixelSize(), new JenksCategorize(colorMap));
        /*
             * Gives the color model to the image layout and creates a new image using the Null
             * operation, which merely propagates its first source along the operation chain
             * unmodified (except for the ColorModel given in the layout in this case).
             */
        final ImageLayout layout = new ImageLayout().setColorModel(newColorModel);
        return new NullOpImage(image, layout, null, OpImage.OP_COMPUTE_BOUND);
    }
    return null;
}
Also used : Palette(org.geotoolkit.image.palette.Palette) Color(java.awt.Color) IOException(java.io.IOException) NullOpImage(javax.media.jai.NullOpImage) Classification(org.geotoolkit.image.classification.Classification) ImageLayout(javax.media.jai.ImageLayout)

Aggregations

Palette (org.geotoolkit.image.palette.Palette)3 Color (java.awt.Color)2 IndexColorModel (java.awt.image.IndexColorModel)1 IOException (java.io.IOException)1 ImageTypeSpecifier (javax.imageio.ImageTypeSpecifier)1 ImageLayout (javax.media.jai.ImageLayout)1 NullOpImage (javax.media.jai.NullOpImage)1 Classification (org.geotoolkit.image.classification.Classification)1 MetadataHelper (org.geotoolkit.image.io.metadata.MetadataHelper)1 SampleDimension (org.geotoolkit.image.io.metadata.SampleDimension)1 SampleDomain (org.geotoolkit.image.io.metadata.SampleDomain)1 SpatialMetadata (org.geotoolkit.image.io.metadata.SpatialMetadata)1 IndexedPalette (org.geotoolkit.image.palette.IndexedPalette)1 PaletteFactory (org.geotoolkit.image.palette.PaletteFactory)1