com.bc.ceres.jai.opimage.ReinterpretOpImage.java Source code

Java tutorial

Introduction

Here is the source code for com.bc.ceres.jai.opimage.ReinterpretOpImage.java

Source

/*
 * 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);
        }
    }
}