/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-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.image.io; import java.awt.Dimension; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.BandedSampleModel; import java.awt.image.ColorModel; import java.awt.image.ComponentSampleModel; import java.awt.image.DataBuffer; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.PixelInterleavedSampleModel; import java.awt.image.SampleModel; import java.awt.image.SinglePixelPackedSampleModel; import javax.imageio.ImageReadParam; import javax.imageio.ImageTypeSpecifier; import javax.media.jai.ComponentSampleModelJAI; import javax.media.jai.PlanarImage; import org.geotools.resources.image.ComponentColorModelJAI; /** * A class describing how a raw binary stream is to be decoded. In the context of * {@link RawBinaryImageReader}, the stream may not contains enough information * for an optimal decoding. For example the stream may not contains image's * width and height. The {@code RawBinaryImageReadParam} gives a chance * to specify those missing informations. * * @since 2.0 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * * @todo We should consider to use Sun's RAW decoder provided with "Java Advanced Imaging Image I/O * Tools" instead (download at http://java.sun.com/products/java-media/jai/). This replacement * is not yet done because we need to figure out a way to handle pad values first. */ public class RawBinaryImageReadParam extends ImageReadParam { /** * The expected image model, or {@code null} if unknow. */ private SampleModel model; /** * The expected image size, or {@code null} if unknow. */ private Dimension size; /** * The expected data type, or {@link DataBuffer#TYPE_UNDEFINED} if unknow. */ private int dataType = DataBuffer.TYPE_UNDEFINED; /** * The target data type, or {@link DataBuffer#TYPE_UNDEFINED} if not * defined. In the later case, the target data type will be the same * than the raw one. */ private int targetDataType = DataBuffer.TYPE_UNDEFINED; /** * The pad value, or {@link Double#NaN} if there is none. */ private double padValue = Double.NaN; /** * Constructs a new {@code RawBinaryImageReadParam} with default parameters. */ public RawBinaryImageReadParam() { } /** * Specifies the image size in the input stream. Setting the size to {@code null} * reset the default size, which is reader dependent. Most readers will thrown an * exception at reading time if the image size is unspecified. * * @param size The expected image size, or {@code null} if unknow. */ public void setStreamImageSize(final Dimension size) { this.size = (size!=null) ? new Dimension(size.width, size.height) : null; } /** * Returns the image size in the input stream, or {@code null} if unknow. * Image size is specified by the last call to {@link #setStreamImageSize} or * {@link #setStreamSampleModel}. */ public Dimension getStreamImageSize() { return (size!=null) ? (Dimension) size.clone() : null; } /** * Checks the validity of the specified data type. * * @param dataType The data type to check. * @throws IllegalArgumentException if {@code dataType} is not one of * the valid enums of {@link DataBuffer}. */ private static void checkDataType(final int dataType) throws IllegalArgumentException { if ((dataType < DataBuffer.TYPE_BYTE || dataType > DataBuffer.TYPE_DOUBLE) && dataType != DataBuffer.TYPE_UNDEFINED) { throw new IllegalArgumentException(String.valueOf(dataType)); } } /** * Specifies the data type in input stream. Setting data type to * {@link DataBuffer#TYPE_UNDEFINED} reset the default value, which * is reader dependent. * * @param dataType The data type, or {@link DataBuffer#TYPE_UNDEFINED} if unknow. * Know data type should be a constant from {@link DataBuffer}. Common * types are {@link DataBuffer#TYPE_INT}, {@link DataBuffer#TYPE_FLOAT} * and {@link DataBuffer#TYPE_DOUBLE}. */ public void setStreamDataType(final int dataType) { checkDataType(dataType); this.dataType = dataType; } /** * Returns the data type in input stream, or {@link DataBuffer#TYPE_UNDEFINED} * if unknow. Data type is specified by the last call to {@link #setStreamDataType} * or {@link #setStreamSampleModel}. */ public int getStreamDataType() { return dataType; } /** * Sets the desired image type for the destination image, using one of * {@link DataBuffer} enumeration constant. This setting will override * any previous setting made with {@link #setDestinationType(ImageTypeSpecifier)} * or this {@code setDestinationType(int)} method. * * @param destType The data type. This should be a constant from {@link DataBuffer}. * Common types are {@link DataBuffer#TYPE_INT}, {@link DataBuffer#TYPE_FLOAT} * and {@link DataBuffer#TYPE_DOUBLE}. */ public void setDestinationType(final int destType) { checkDataType(destType); targetDataType = destType; setDestinationType(getDestinationType(model!=null ? model.getNumBands() : 1)); } /** * Creates a destination type with the specified number of bands. * If no such destination type is available, returns {@code null}. */ final ImageTypeSpecifier getDestinationType(final int numBands) { if (targetDataType == DataBuffer.TYPE_UNDEFINED) { return null; } final SampleModel sampleModel; final ColorModel colorModel; final ColorSpace colorSpace = getColorSpace(numBands); if (model!=null) { /* * Case 1: we know the sample model for data in the * underlying stream. We will use the same * model for the memory image, just changing * the data type. */ if (numBands != model.getNumBands()) { throw new IllegalArgumentException("Number of bands mismatch"); } sampleModel = getStreamSampleModel(model, model, size, targetDataType); } else { /* * Case 2: We have to create a sample model from scratch. We * will use a banded sample model with some arbitrary * color space (which may be changed after the image * reading is completed). */ final int width, height; if (size!=null) { width = size.width; height = size.height; } else { width = height = 1; } final int[] bankIndices = new int[numBands]; final int[] bandOffsets = new int[numBands]; for (int i=numBands; --i>=0;) bankIndices[i]=i; if (ContinuousPalette.USE_JAI_MODEL) { sampleModel = new ComponentSampleModelJAI(targetDataType, width, height, 1, width, bankIndices, bandOffsets); } else { return ImageTypeSpecifier.createBanded(colorSpace, bankIndices, bandOffsets, targetDataType, false, false); } } /* * Constructs a color model likely to matches the * sample model, and then finish the type specifier. */ if (sampleModel instanceof ComponentSampleModel) { // This is the most common case. colorModel = new ComponentColorModelJAI(getColorSpace(numBands), false, false, Transparency.OPAQUE, sampleModel.getDataType()); } else { // Fallback to JAI helper method if we have a less common case. colorModel = PlanarImage.createColorModel(sampleModel); } return new ImageTypeSpecifier(colorModel, sampleModel); } /** * Returns a default color space for the destination sample model. * If no destination image has been specified, then a gray scale * color space will be constructed for values ranging from 0 to 1. */ private ColorSpace getColorSpace(int numBands) { if (destination!=null) { return destination.getColorModel().getColorSpace(); } /* * Overrides the number of source bands if this * parameter block contains enough informations. */ if (sourceBands!=null) { numBands = sourceBands.length; } else if (model!=null) { numBands = model.getNumBands(); } /* * Checks the number of destination bands. If 'destinationBands' is * null, then all bands are going to be used. If it is non-null, * then the destination image may have more bands than what we are * going to use. This problem still an open question... As a patch, * current implementation search for the greatest band number. */ if (destinationBands!=null) { for (int i=0; i<destinationBands.length; i++) { if (destinationBands[i] >= numBands) { numBands = destinationBands[i]+1; } } } if (numBands==1) { return ColorSpace.getInstance(ColorSpace.CS_GRAY); } return new ScaledColorSpace(numBands, 0, 0, 1); } /** * Set the pad value. * * @param padValue The pad value, or {@link Double#NaN} if there is none. */ public void setPadValue(final double padValue) { this.padValue = padValue; } /** * Returns the pad value, or {@link Double#NaN} if there is none */ public double getPadValue() { return padValue; } /** * Set a sample model indicating the data layout in the input stream. * Indications comprise image size and data type, i.e. calling this * method with a non-null value is equivalent to calling also the * following methods: * * <blockquote><pre> * setStreamImageSize(model.getWidth(), model.getHeight()); * setStreamDataType(model.getDataType()); * </pre></blockquote> * * Setting the sample model to {@code null} reset * the default model, which is reader dependent. */ public void setStreamSampleModel(final SampleModel model) { this.model = model; if (model != null) { size = new Dimension(model.getWidth(), model.getHeight()); dataType = model.getDataType(); } } /** * Returns a sample model indicating the data layout in the input stream. * The {@link SampleModel}'s width and height should matches the image * size in the input stream. * * @return A sample model indicating the data layout in the input stream, * or {@code null} if unknow. */ public SampleModel getStreamSampleModel() { return model = getStreamSampleModel(null); } /** * Returns a sample model indicating the data layout in the input stream. * The {@link SampleModel}'s width and height should matches the image * size in the input stream. * * @param defaultSampleModel A default sample model, or {@code null} * if there is no default. If this {@code RawBinaryImageReadParam} * contains unspecified sample model, image size or data type, values * from {@code defaultSampleModel} will be used. * @return A sample model indicating the data layout in the input stream, * or {@code null} if unknow. */ final SampleModel getStreamSampleModel(final SampleModel defaultSampleModel) { return getStreamSampleModel(defaultSampleModel, model, size, dataType); } /** * Returns a sample model indicating the data layout in the input stream. * The {@link SampleModel}'s width and height should matches the image * size in the input stream. * * @param defaultSampleModel A default sample model, or {@code null} * if there is no default. If this {@code RawBinaryImageReadParam} * contains unspecified sample model, image size or data type, values * from {@code defaultSampleModel} will be used. * @param model The sample model in the underlying stream, or {@code null}. * @param size The image size in the underlying stream, or {@code null}. * @param dataType the data type. * @return A sample model indicating the data layout in the input stream, * or {@code null} if unknow. */ private static SampleModel getStreamSampleModel(final SampleModel defaultSampleModel, SampleModel model, Dimension size, int dataType) { if (defaultSampleModel != null) { if (model == null) { model = defaultSampleModel; } if (size == null) { size = new Dimension(defaultSampleModel.getWidth(), defaultSampleModel.getHeight()); } if (dataType == DataBuffer.TYPE_UNDEFINED) { dataType = defaultSampleModel.getDataType(); } } if (model == null || size == null || dataType == DataBuffer.TYPE_UNDEFINED) { return null; } final int width = size.width; final int height = size.height; if (dataType != model.getDataType()) { if (model instanceof ComponentSampleModel) { final ComponentSampleModel cast = (ComponentSampleModel) model; final int pixelStride = cast.getPixelStride(); final int scanlineStride = cast.getScanlineStride(); final int[] bankIndices = cast.getBankIndices(); final int[] bandOffsets = cast.getBandOffsets(); if (model instanceof BandedSampleModel) { model = new BandedSampleModel(dataType, width, height, scanlineStride, bankIndices, bandOffsets); } else if (model instanceof PixelInterleavedSampleModel) { model = new PixelInterleavedSampleModel(dataType, width, height, pixelStride, scanlineStride, bandOffsets); } else if (model instanceof ComponentSampleModelJAI) { model = new ComponentSampleModelJAI(dataType, width, height, pixelStride, scanlineStride, bankIndices, bandOffsets); } else { model = new ComponentSampleModel(dataType, width, height, pixelStride, scanlineStride, bankIndices, bandOffsets); } } else if (model instanceof MultiPixelPackedSampleModel) { final MultiPixelPackedSampleModel cast = (MultiPixelPackedSampleModel) model; final int numberOfBits = DataBuffer.getDataTypeSize(dataType); final int scanlineStride = cast.getScanlineStride(); final int dataBitOffset = cast.getDataBitOffset(); model = new MultiPixelPackedSampleModel(dataType, width, height, numberOfBits, scanlineStride, dataBitOffset); } else if (model instanceof SinglePixelPackedSampleModel) { final SinglePixelPackedSampleModel cast = (SinglePixelPackedSampleModel) model; final int scanlineStride = cast.getScanlineStride(); final int[] bitMasks = cast.getBitMasks(); model = new SinglePixelPackedSampleModel(dataType, width, height, scanlineStride, bitMasks); } else { throw new IllegalStateException(model.getClass().getName()); } } if (model.getWidth() != width || model.getHeight() != height) { model = model.createCompatibleSampleModel(width, height); } return model; } }