/* * 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.bc.ceres.jai.operator.InterpretationType; import com.bc.ceres.jai.operator.ReinterpretDescriptor; import com.bc.ceres.jai.operator.ScalingType; import org.apache.commons.math3.util.FastMath; import javax.media.jai.ImageLayout; import javax.media.jai.JAI; import javax.media.jai.PixelAccessor; import javax.media.jai.PointOpImage; import javax.media.jai.UnpackedImageData; import java.awt.Rectangle; import java.awt.image.DataBuffer; import java.awt.image.PixelInterleavedSampleModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.util.Map; import static com.bc.ceres.jai.operator.ReinterpretDescriptor.EXPONENTIAL; import static com.bc.ceres.jai.operator.ReinterpretDescriptor.LINEAR; import static com.bc.ceres.jai.operator.ReinterpretDescriptor.LOGARITHMIC; public final class ReinterpretOpImage extends PointOpImage { private static final double LOG10 = Math.log(10); private final double factor; private final double offset; private final ScalingType scalingType; private final InterpretationType interpretationType; private final ScalingTransform scalingTransform; static RenderedImage create(RenderedImage source, double factor, double offset, ScalingType scalingType, InterpretationType interpretationType, Map<Object, Object> config) { final ImageLayout imageLayout; if (config != null && config.get(JAI.KEY_IMAGE_LAYOUT) instanceof ImageLayout) { imageLayout = (ImageLayout) config.get(JAI.KEY_IMAGE_LAYOUT); } else { final int targetDataType = ReinterpretDescriptor.getTargetDataType(source.getSampleModel().getDataType(), factor, offset, scalingType, interpretationType); final PixelInterleavedSampleModel sampleModel = new PixelInterleavedSampleModel(targetDataType, source.getTileWidth(), source.getTileHeight(), 1, source.getTileWidth(), new int[]{0}); imageLayout = ReinterpretDescriptor.createTargetImageLayout(source, sampleModel); } return new ReinterpretOpImage(source, imageLayout, config, factor, offset, scalingType, interpretationType); } private ReinterpretOpImage(RenderedImage source, ImageLayout imageLayout, Map<Object, Object> config, double factor, double offset, ScalingType scalingType, InterpretationType interpretationType) { super(source, imageLayout, config, true); this.factor = factor; this.offset = offset; this.scalingType = scalingType; this.interpretationType = interpretationType; this.scalingTransform = scalingType == EXPONENTIAL ? new Pow10() : scalingType == LOGARITHMIC ? new Log10() : null; // set flag to permit in-place operation. permitInPlaceOperation(); } @Override protected void computeRect(Raster[] sourceRasters, WritableRaster targetRaster, Rectangle targetRectangle) { if (scalingType == LINEAR && factor == 1.0 && offset == 0.0) { reformat(sourceRasters[0], targetRaster, targetRectangle); } else { rescale(sourceRasters[0], targetRaster, targetRectangle); } } private void rescale(Raster sourceRaster, WritableRaster targetRaster, Rectangle targetRectangle) { final int sourceDataType = sourceRaster.getSampleModel().getDataType(); final int targetDataType = targetRaster.getSampleModel().getDataType(); final PixelAccessor sourceAcc = new PixelAccessor(getSourceImage(0)); final PixelAccessor targetAcc = new PixelAccessor(this); final UnpackedImageData sourcePixels; final UnpackedImageData targetPixels; sourcePixels = sourceAcc.getPixels(sourceRaster, targetRectangle, sourceDataType, false); targetPixels = targetAcc.getPixels(targetRaster, targetRectangle, targetDataType, true); switch (sourceDataType) { case DataBuffer.TYPE_BYTE: if (interpretationType == ReinterpretDescriptor.INTERPRET_BYTE_SIGNED) { rescaleSByte(sourcePixels, targetPixels, targetRectangle); } else { rescaleByte(sourcePixels, targetPixels, targetRectangle); } break; case DataBuffer.TYPE_USHORT: rescaleUShort(sourcePixels, targetPixels, targetRectangle); break; case DataBuffer.TYPE_SHORT: rescaleShort(sourcePixels, targetPixels, targetRectangle); break; case DataBuffer.TYPE_INT: if (interpretationType == ReinterpretDescriptor.INTERPRET_INT_UNSIGNED) { rescaleUInt(sourcePixels, targetPixels, targetRectangle); } else { rescaleInt(sourcePixels, targetPixels, targetRectangle); } break; case DataBuffer.TYPE_FLOAT: rescaleFloat(sourcePixels, targetPixels, targetRectangle); break; case DataBuffer.TYPE_DOUBLE: rescaleDouble(sourcePixels, targetPixels, targetRectangle); break; } targetAcc.setPixels(targetPixels); } private void reformat(Raster sourceRaster, WritableRaster targetRaster, Rectangle targetRectangle) { final int sourceDataType = sourceRaster.getSampleModel().getDataType(); final int targetDataType = targetRaster.getSampleModel().getDataType(); final PixelAccessor sourceAcc = new PixelAccessor(getSourceImage(0)); final PixelAccessor targetAcc = new PixelAccessor(this); final UnpackedImageData sourcePixels; final UnpackedImageData targetPixels; sourcePixels = sourceAcc.getPixels(sourceRaster, targetRectangle, sourceDataType, false); switch (sourceDataType) { case DataBuffer.TYPE_BYTE: if (interpretationType == ReinterpretDescriptor.INTERPRET_BYTE_SIGNED) { targetPixels = targetAcc.getPixels(targetRaster, targetRectangle, targetDataType, true); reformatSByte(sourcePixels, targetPixels, targetRectangle); break; } case DataBuffer.TYPE_INT: if (interpretationType == ReinterpretDescriptor.INTERPRET_INT_UNSIGNED) { targetPixels = targetAcc.getPixels(targetRaster, targetRectangle, targetDataType, true); reformatUInt(sourcePixels, targetPixels, targetRectangle); break; } default: targetPixels = sourcePixels; } targetAcc.setPixels(targetPixels); } private void reformatSByte(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final byte[] sourceData = sourcePixels.getByteData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final short[] targetData = targetPixels.getShortData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final short v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = v; sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } private void reformatUInt(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final int[] sourceData = sourcePixels.getIntData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final double[] targetData = targetPixels.getDoubleData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset] & 0xFFFFFFFFL; targetData[targetPixelOffset] = v; sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } private void rescaleByte(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final byte[] sourceData = sourcePixels.getByteData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final float[] targetData = targetPixels.getFloatData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; if (scalingTransform != null) { ScalingTransform st = this.scalingTransform; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset] & 0xFF; targetData[targetPixelOffset] = (float) st.transform(factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } else { for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset] & 0xFF; targetData[targetPixelOffset] = (float) (factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } } private void rescaleSByte(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final byte[] sourceData = sourcePixels.getByteData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final float[] targetData = targetPixels.getFloatData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; if (scalingTransform != null) { ScalingTransform st = this.scalingTransform; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = (float) st.transform(factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } else { for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = (float) (factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } } private void rescaleUShort(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final short[] sourceData = sourcePixels.getShortData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final float[] targetData = targetPixels.getFloatData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; if (scalingTransform != null) { ScalingTransform st = this.scalingTransform; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset] & 0xFFFF; targetData[targetPixelOffset] = (float) st.transform(factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } else { for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset] & 0xFFFF; targetData[targetPixelOffset] = (float) (factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } } private void rescaleShort(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final short[] sourceData = sourcePixels.getShortData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final float[] targetData = targetPixels.getFloatData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; if (scalingTransform != null) { ScalingTransform st = this.scalingTransform; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = (float) st.transform(factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } else { for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = (float) (factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } } private void rescaleInt(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final int[] sourceData = sourcePixels.getIntData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final double[] targetData = targetPixels.getDoubleData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; if (scalingTransform != null) { ScalingTransform st = this.scalingTransform; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = st.transform(factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } else { for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = factor * v + offset; sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } } private void rescaleUInt(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final int[] sourceData = sourcePixels.getIntData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final double[] targetData = targetPixels.getDoubleData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; if (scalingTransform != null) { ScalingTransform st = this.scalingTransform; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset] & 0xFFFFFFFFL; targetData[targetPixelOffset] = st.transform(factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } else { for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset] & 0xFFFFFFFFL; targetData[targetPixelOffset] = factor * v + offset; sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } } private void rescaleFloat(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final float[] sourceData = sourcePixels.getFloatData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final float[] targetData = targetPixels.getFloatData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; if (scalingTransform != null) { ScalingTransform st = this.scalingTransform; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final float v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = (float) st.transform(factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } else { for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final float v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = (float) (factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } } private void rescaleDouble(UnpackedImageData sourcePixels, UnpackedImageData targetPixels, Rectangle targetRectangle) { final int sourceLineStride = sourcePixels.lineStride; final int sourcePixelStride = sourcePixels.pixelStride; final double[] sourceData = sourcePixels.getDoubleData(0); final int targetLineStride = targetPixels.lineStride; final int targetPixelStride = targetPixels.pixelStride; final double[] targetData = targetPixels.getDoubleData(0); final int w = targetRectangle.width; final int h = targetRectangle.height; int sourceLineOffset = sourcePixels.bandOffsets[0]; int targetLineOffset = targetPixels.bandOffsets[0]; if (scalingTransform != null) { ScalingTransform st = this.scalingTransform; for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = st.transform(factor * v + offset); sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } else { for (int y = 0; y < h; y++) { int sourcePixelOffset = sourceLineOffset; sourceLineOffset += sourceLineStride; int targetPixelOffset = targetLineOffset; targetLineOffset += targetLineStride; for (int x = 0; x < w; x++) { final double v = sourceData[sourcePixelOffset]; targetData[targetPixelOffset] = factor * v + offset; sourcePixelOffset += sourcePixelStride; targetPixelOffset += targetPixelStride; } // next x } // next y } } private static ImageLayout createTargetImageLayout(RenderedImage source, SampleModel sampleModel) { final int w = source.getWidth(); final int h = source.getHeight(); final ImageLayout imageLayout = new ImageLayout(); imageLayout.setWidth(w); imageLayout.setHeight(h); imageLayout.setSampleModel(sampleModel); return imageLayout; } /** * Implementation code of this interface shall be easily in-lined by the compiler. */ private interface ScalingTransform { double transform(double x); } private final class Pow10 implements ScalingTransform { public double transform(double x) { // This is ~500 ms per 4 mega-pixels on my Intel i7 2.8 GHz CPU //return Math.exp(LOG10 * x); // This is ~700 ms per 4 mega-pixels on my Intel i7 2.8 GHz CPU //return Math.pow(10.0, x); // This is ~300 ms per 4 mega-pixels on my Intel i7 2.8 GHz CPU return FastMath.exp(LOG10 * x); } } private final class Log10 implements ScalingTransform { public double transform(double x) { // This is slightly below 300 ms per 4 mega-pixels on my Intel i7 2.8 GHz CPU return Math.log10(x); // This is slightly above 300 ms per 4 mega-pixels on my Intel i7 2.8 GHz CPU //return Math.log(x) / LOG10; // This is ~900 ms per 4 mega-pixels on my Intel i7 2.8 GHz CPU //return FastMath.log10(x); } } }