/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2015 GeoSolutions * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package it.geosolutions.jaiext.mosaic; import java.awt.Rectangle; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import javax.media.jai.RasterAccessor; import javax.media.jai.RasterFormatTag; import it.geosolutions.jaiext.range.Range; import it.geosolutions.jaiext.range.RangeFactory; /** * Extends the RasterAcessor to handle more data type transformation cases * * @author Andrea Aime - GeoSolutions */ public class RasterAccessorExt extends RasterAccessor { /** * Value indicating how far GRAY_EXPANSION_MASK_SHIFT info is shifted to avoid interfering with * the data type info. */ private static final int GRAY_EXPANSION_MASK_SHIFT = 11; /** Value indicating how many bits the GRAY_EXPANSION_MASK is */ private static final int GRAY_EXPANSION_MASK_SIZE = 1; /** The bits of a FormatTag associated with the gray expansion. */ public static final int GRAY_EXPANSION_MASK = 3 << GRAY_EXPANSION_MASK_SHIFT; /** Flag indicating the gray bands should not be expanded */ public static final int UNEXPANDED = 0x00 << GRAY_EXPANSION_MASK_SHIFT; /** Flag indicating the gray bands should be expanded to RGB */ public static final int GRAY_TO_RGB = 0X01 << GRAY_EXPANSION_MASK_SHIFT; /** Flag indicating the gray bands should be rescaled to the target type */ public static final int GRAY_SCALE = 0X02 << GRAY_EXPANSION_MASK_SHIFT; public RasterAccessorExt(Raster raster, Rectangle rect, RasterFormatTag rft, ColorModel theColorModel, int targetBands, int targetDataType) { super(raster, rect, rft, theColorModel); // gray to multiband expansion if (rft.getNumBands() == 1 && (rft.getFormatTagID() & GRAY_EXPANSION_MASK) == GRAY_TO_RGB) { int newNumBands = targetBands; // all zero, we are just replicating the arrays int newBandDataOffsets[] = new int[newNumBands]; for (int i = 0; i < newBandDataOffsets.length; i++) { newBandDataOffsets[i] = this.bandDataOffsets[0]; } int newBandOffsets[] = new int[newNumBands]; for (int i = 0; i < newBandOffsets.length; i++) { newBandOffsets[i] = this.bandOffsets[0]; } switch (formatTagID & DATATYPE_MASK) { case DataBuffer.TYPE_BYTE: byte byteDataArray[] = byteDataArrays[0]; byteDataArrays = new byte[newNumBands][]; for (int i = 0; i < newNumBands; i++) { byteDataArrays[i] = byteDataArray; } break; case DataBuffer.TYPE_USHORT: case DataBuffer.TYPE_SHORT: short shortDataArray[] = shortDataArrays[0]; shortDataArrays = new short[newNumBands][]; for (int i = 0; i < newNumBands; i++) { shortDataArrays[i] = shortDataArray; } break; case DataBuffer.TYPE_INT: // in case of mixed color models, gray ushort and byte, the RasterAccessor // sets the tag to int, but we need to crush the data down from ushort to byte // before expanding the gray int intDataArray[] = intDataArrays[0]; if (raster.getDataBuffer().getDataType() == DataBuffer.TYPE_USHORT && targetDataType == DataBuffer.TYPE_BYTE) { final int length = intDataArray.length; for (int i = 0; i < length; i++) { int sample = intDataArray[i]; intDataArray[i] = shortToByte(sample); } } intDataArrays = new int[newNumBands][]; for (int i = 0; i < newNumBands; i++) { intDataArrays[i] = intDataArray; } break; case DataBuffer.TYPE_FLOAT: float floatDataArray[] = floatDataArrays[0]; floatDataArrays = new float[newNumBands][]; for (int i = 0; i < newNumBands; i++) { floatDataArrays[i] = floatDataArray; } break; case DataBuffer.TYPE_DOUBLE: double doubleDataArray[] = doubleDataArrays[0]; doubleDataArrays = new double[newNumBands][]; for (int i = 0; i < newNumBands; i++) { doubleDataArrays[i] = doubleDataArray; } break; } this.numBands = newNumBands; this.bandDataOffsets = newBandDataOffsets; this.bandOffsets = newBandDataOffsets; } else if (rft.getNumBands() == 1 && (rft.getFormatTagID() & GRAY_EXPANSION_MASK) == GRAY_SCALE) { int sourceDataType = raster.getSampleModel().getDataType(); if (targetDataType == DataBuffer.TYPE_USHORT && sourceDataType == DataBuffer.TYPE_BYTE) { for (int i = 0; i < intDataArrays.length; i++) { int[] pixels = intDataArrays[i]; for (int j = 0; j < pixels.length; j++) { pixels[j] = byteToShort(pixels[j]); } } } else { throw new IllegalArgumentException("Cannot perform gray rescaling from data type " + sourceDataType + " to data type " + targetDataType); } } } /** * Rescales a ushort sample down into the byte range * * @param sample * @return */ private static final int shortToByte(int sample) { return (int) Math.round((sample / 65536d) * 255); } /** * Rescales a byte to the ushort range * * @param theByte * @return */ private static final int byteToShort(int theByte) { double d = theByte; return (int) Math.round((d / 255) * 65535); } /** * Finds the appropriate tags for the constructor, based on the SampleModel and ColorModel of * all the source and destination. * * @param srcs The operations sources; may be <code>null</code> which is taken to be equivalent * to zero sources. * @param dst The operation destination. * @return An array containing <code>RasterFormatTag</code>s for the sources in the first * src.length elements and a <code>RasterFormatTag</code> for the destination in the * last element. * @throws NullPointerException if <code>dst</code> is <code>null</code>. */ public static RasterFormatTag[] findCompatibleTags(RenderedImage srcs[], RenderedImage dst) { RasterFormatTag[] tags = RasterAccessor.findCompatibleTags(srcs, dst); // check if we need to perform gray expansion if (dst.getSampleModel().getNumBands() > 1) { for (int i = 0; i < srcs.length; i++) { RenderedImage src = srcs[i]; if (src.getSampleModel().getNumBands() == 1 && !(src.getColorModel() instanceof IndexColorModel)) { tags[i] = applyMask(src, tags[i], GRAY_TO_RGB); } } } else if (dst.getSampleModel().getNumBands() == 1 && !(dst.getColorModel() instanceof IndexColorModel)) { int destinationDataType = dst.getSampleModel().getDataType(); for (int i = 0; i < srcs.length; i++) { RenderedImage src = srcs[i]; int sourceDataType = src.getSampleModel().getDataType(); if (destinationDataType != sourceDataType) { tags[i] = applyMask(src, tags[i], GRAY_SCALE); } } } return tags; } private static RasterFormatTag applyMask(RenderedImage src, RasterFormatTag oldTag, int mask) { int tagId = oldTag.getFormatTagID() | mask; RasterFormatTag newTag = new RasterFormatTag(src.getSampleModel(), tagId); return newTag; } public static Range expandNoData(Range noData, RasterFormatTag rft, RenderedImage sourceImage, RenderedImage destImage) { int formatTagID = rft.getFormatTagID(); // handle gray to gray (we get the output sample model as the RasterAccessor sets the // target data type to int in case of mixed sample models, but eventually things work out int targetDataType = destImage.getSampleModel().getDataType(); int formatDataType = formatTagID & DATATYPE_MASK; int sourceDataType = sourceImage.getSampleModel().getDataType(); int destinationDataType = destImage.getSampleModel().getDataType(); if (rft.getNumBands() == 1 && (rft.getFormatTagID() & GRAY_EXPANSION_MASK) == GRAY_TO_RGB && formatDataType == DataBuffer.TYPE_INT && sourceDataType == DataBuffer.TYPE_USHORT && destinationDataType == DataBuffer.TYPE_BYTE) { int min = noData.getMin().intValue(); int max = noData.getMax().intValue(); byte scaledMin = (byte) (shortToByte(min) & 0xFF); byte scaledMax = (byte) (shortToByte(max) & 0xFF); return RangeFactory.create(scaledMin, noData.isMinIncluded(), scaledMax, noData.isMaxIncluded()); } else if (rft.getNumBands() == 1 && (rft.getFormatTagID() & GRAY_EXPANSION_MASK) == GRAY_SCALE) { if (targetDataType == DataBuffer.TYPE_USHORT && sourceDataType == DataBuffer.TYPE_BYTE) { int min = noData.getMin().intValue(); int max = noData.getMax().intValue(); int expandedMin = byteToShort(min); int expandedMax = byteToShort(max); return RangeFactory.create(expandedMin, noData.isMinIncluded(), expandedMax, noData.isMaxIncluded()); } else { throw new IllegalArgumentException("Cannot perform gray rescaling from data type " + sourceDataType + " to data type " + targetDataType); } } return noData; } /** * Returns true if palette expansion is required * * @param sourceImage * @param formatTagID * @return */ static boolean isPaletteExpansionRequired(RenderedImage sourceImage, int formatTagID) { return (formatTagID & EXPANSION_MASK) == EXPANDED && sourceImage.getColorModel() instanceof IndexColorModel; } }