sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: First draft of BandedSampleConverter (not yet tested).
Date Fri, 03 Jan 2020 19:24:59 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 2dff54f  First draft of BandedSampleConverter (not yet tested).
2dff54f is described below

commit 2dff54f7fdaa3dac143f289d1fa5471bbc04d844
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Jan 3 20:24:24 2020 +0100

    First draft of BandedSampleConverter (not yet tested).
---
 .../coverage/j2d/BandedSampleConverter.java        | 149 ++++++
 .../sis/internal/coverage/j2d/Transferer.java      | 574 +++++++++++++++++++++
 2 files changed, 723 insertions(+)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverter.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverter.java
new file mode 100644
index 0000000..a94da76
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BandedSampleConverter.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.internal.coverage.j2d;
+
+import java.awt.Dimension;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.awt.image.RenderedImage;
+import java.awt.image.BandedSampleModel;
+import org.opengis.referencing.operation.MathTransform1D;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.image.ComputedImage;
+import org.apache.sis.util.Workaround;
+
+
+/**
+ * An image where each sample value is computed independently of other sample values and
independently
+ * of neighbor points. Values are computed by a separated {@link MathTransform1D} for each
band
+ * (by contrast, an {@code InterleavedSampleConverter} would handle all sample values as
a coordinate tuple).
+ * Current implementation makes the following simplifications:
+ *
+ * <ul>
+ *   <li>The image has exactly one source.</li>
+ *   <li>Image layout (minimum coordinates, image size, tile grid) is the same than
source image layout,
+ *     unless the source has too large tiles in which case {@link ImageLayout} automatically
subdivides
+ *     the tile grid in smaller tiles.</li>
+ *   <li>Image is computed and stored on a band-by-band basis using a {@link BandedSampleModel}.</li>
+ *   <li>Calculation is performed on {@code float} or {@code double} numbers.</li>
+ * </ul>
+ *
+ * Subclasses may relax those restrictions at the cost of more complex {@link #computeTile(int,
int)}
+ * implementation. Those restrictions may also be relaxed in future versions of this class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public abstract class BandedSampleConverter extends ComputedImage {
+    /**
+     * The transfer functions to apply on each band of the source image.
+     */
+    private final MathTransform1D[] converters;
+
+    /**
+     * Creates a new image of the given data type which will compute values using the given
converters.
+     *
+     * @param  source      the image for which to convert sample values.
+     * @param  layout      object to use for computing tile size, or {@code null} for the
default.
+     * @param  targetType  the type of this image resulting from conversion of given image.
+     * @param  converters  the transfer functions to apply on each band of the source image.
+     */
+    public BandedSampleConverter(final RenderedImage source, final ImageLayout layout,
+                                 final int targetType, final MathTransform1D... converters)
+    {
+        super(createSampleModel(targetType, converters.length, layout, source), source);
+        this.converters = converters;
+    }
+
+    /**
+     * Returns the sample model to use for this image. This is a workaround for RFE #4093999
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @Workaround(library="JDK", version="1.8")
+    private static BandedSampleModel createSampleModel(final int targetType,
+            final int numBands, ImageLayout layout, final RenderedImage source)
+    {
+        if (layout == null) {
+            layout = ImageLayout.DEFAULT;
+        }
+        final Dimension tile = layout.suggestTileSize(source);
+        return new BandedSampleModel(targetType, tile.width, tile.height, numBands);
+    }
+
+    /**
+     * Returns the minimum <var>x</var> coordinate (inclusive) of this image.
+     * This is the the same value than the source image (not necessarily zero).
+     *
+     * @return the minimum <var>x</var> coordinate (column) of this image.
+     */
+    @Override
+    public int getMinX() {
+        return getSource(0).getMinX();
+    }
+
+    /**
+     * Returns the minimum <var>y</var> coordinate (inclusive) of this image.
+     * This is the the same value than the source image (not necessarily zero).
+     *
+     * @return the minimum <var>y</var> coordinate (row) of this image.
+     */
+    @Override
+    public int getMinY() {
+        return getSource(0).getMinY();
+    }
+
+    /**
+     * Returns the minimum tile index in the <var>x</var> direction.
+     * This is the the same value than the source image (not necessarily zero).
+     *
+     * @return the minimum tile index in the <var>x</var> direction.
+     */
+    @Override
+    public int getMinTileX() {
+        return getSource(0).getMinTileX();
+    }
+
+    /**
+     * Returns the minimum tile index in the <var>y</var> direction.
+     * This is the the same value than the source image (not necessarily zero).
+     *
+     * @return the minimum tile index in the <var>y</var> direction.
+     */
+    @Override
+    public int getMinTileY() {
+        return getSource(0).getMinTileY();
+    }
+
+    /**
+     * Computes the tile at specified indices.
+     *
+     * @param  tileX  the column index of the tile to compute.
+     * @param  tileY  the row index of the tile to compute.
+     * @return computed tile for the given indices (can not be null).
+     * @throws TransformException if an error occurred while converting a sample value.
+     */
+    @Override
+    protected Raster computeTile(final int tileX, final int tileY) throws TransformException
{
+        final Raster         source = getSource(0).getTile(tileX, tileY);
+        final WritableRaster target = createTile(tileX, tileY);
+        final Transferer   transfer = Transferer.suggest(source, target);
+        transfer.compute(converters);
+        return target;
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Transferer.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Transferer.java
new file mode 100644
index 0000000..8ccda79
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Transferer.java
@@ -0,0 +1,574 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.internal.coverage.j2d;
+
+import java.awt.Rectangle;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.awt.image.SampleModel;
+import java.awt.image.ComponentSampleModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferFloat;
+import java.awt.image.DataBufferDouble;
+import org.apache.sis.internal.util.Numerics;
+import org.opengis.referencing.operation.MathTransform1D;
+import org.opengis.referencing.operation.TransformException;
+
+
+/**
+ * Strategy for reading and writing data between two rasters, with a computation between
them.
+ * Some strategies are to write directly in the destination raster, other strategies are
to use
+ * an intermediate buffer. This class has the following constraints:
+ *
+ * <ul>
+ *   <li>Source values can not be modified. Calculations must be done either directly
in the
+ *       target raster, or in a temporary buffer.</li>
+ *   <li>Direct access to the {@link DataBuffer} arrays may disable video card acceleration.
+ *       This class assumes that it is acceptable for {@code float} and {@code double} types,
+ *       and to be avoided for integer types.</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+abstract class Transferer {
+    /**
+     * The image tile from which to read sample values.
+     */
+    protected final Raster source;
+
+    /**
+     * The image tile where to write sample values after processing.
+     */
+    protected final WritableRaster target;
+
+    /**
+     * Coordinates of the region to read and write. This class assumes that
+     * {@link #source} and {@link #target} share the same coordinate system.
+     */
+    protected final Rectangle region;
+
+    /**
+     * The band to read and write. This class assumes that {@link #source} and
+     * {@link #target} have the same number of bands and bands in same order.
+     */
+    protected int band;
+
+    /**
+     * Creates a new instance for transferring data between the two specified rasters.
+     *
+     * @param  source  image tile from which to read sample values.
+     * @param  target  image tile where to write sample values after processing.
+     */
+    protected Transferer(final Raster source, final WritableRaster target) {
+        this.source = source;
+        this.target = target;
+        this.region = target.getBounds();
+    }
+
+    /**
+     * Sets {@code region.height} to the height of the buffer where values are stored
+     * during data transfer and where {@link MathTransform1D} operations are applied.
+     * If this {@code Transferer} does not use an intermediate buffer (i.e. if it
+     * copies values directly in the target buffer and processes them in-place),
+     * then this method should leave {@link #region} unchanged.
+     *
+     * <p>The default implementation does nothing. This is the most conservative approach
+     * since it does not require {@code Transferer} to split data processing in strips,
+     * at the cost of more memory consumption if this {@code Transferer} does not write
+     * data directly in the target tile.</p>
+     *
+     * @return {@code region.y + region.height}.
+     *
+     * @see ImageUtilities#prepareTransferRegion(Rectangle, int)
+     */
+    int prepareTransferRegion() {
+        return Math.addExact(region.y, region.height);
+    }
+
+    /**
+     * Computes all sample values from the source tile and writes the result in the target
tile.
+     * This method invokes {@link #computeStrip(MathTransform1D)} repetitively for sub-regions
of the tile.
+     *
+     * @param  converters  the converters to apply on each band.
+     * @throws TransformException if an error occurred during calculation.
+     */
+    public final void compute(final MathTransform1D[] converters) throws TransformException
{
+        final int afterLastRow = prepareTransferRegion();
+        final int ymin         = region.y;
+        final int maxHeight    = region.height;             // Computed by `prepareTransferRegion()`.
+        for (band=0; band < converters.length; band++) {
+            final MathTransform1D converter = converters[band];
+            region.y = ymin;
+            do {
+                region.height = Math.min(maxHeight, afterLastRow - region.y);
+                computeStrip(converter);
+            } while ((region.y += region.height) < afterLastRow);
+        }
+    }
+
+    /**
+     * Reads sample values from the {@linkplain #source} tile, applies the given operation
for current
+     * {@linkplain #region} and {@linkplain #band}, then writes results in the {@linkplain
#target} tile.
+     * The {@linkplain #region} and the {@linkplain #band} number must be set before to invoke
this method.
+     *
+     * @param  converter  the operation to apply on sample values in current region and current
band number.
+     * @throws TransformException if an error occurred during calculation.
+     */
+    abstract void computeStrip(final MathTransform1D converter) throws TransformException;
+
+    /**
+     * Returns the number of elements in {@link #region}. This is a helper method for subclass
implementations.
+     */
+    final int length() {
+        return Math.multiplyExact(region.width, region.height);
+    }
+
+
+
+
+    /**
+     * Read {@code double} values from the source and write {@code double} values directly
in the target raster,
+     * without intermediate buffer. This strategy is possible only when the target raster
uses the {@code double}
+     * type for storing sample values. This operation is executed in one step, without subdivisions
in strips.
+     */
+    private static final class DoubleToDirect extends Transferer {
+        /** Data buffer of target raster. */
+        private final DataBufferDouble buffer;
+
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        DoubleToDirect(final Raster source, final WritableRaster target) {
+            super(source, target);
+            buffer = (DataBufferDouble) target.getDataBuffer();
+        }
+
+        /** Copies source values directly in the target, then applies the conversion in-place.
*/
+        @Override void computeStrip(final MathTransform1D converter) throws TransformException
{
+            final double[] data = buffer.getData(band);
+            source.getSamples(region.x, region.y, region.width, region.height, band, data);
+            converter.transform(data, 0, data, 0, length());
+        }
+    }
+
+
+
+
+    /**
+     * Read {@code float} values from the source and write {@code float} values directly
in the target raster,
+     * without intermediate buffer. This strategy is possible only when the target raster
uses the {@code float}
+     * type for storing sample values. This operation is executed in one step, without subdivisions
in strips.
+     */
+    private static final class FloatToDirect extends Transferer {
+        /** Data buffer of target raster. */
+        private final DataBufferFloat buffer;
+
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        FloatToDirect(final Raster source, final WritableRaster target) {
+            super(source, target);
+            buffer = (DataBufferFloat) target.getDataBuffer();
+        }
+
+        /** Copies source values directly in the target, then applies the conversion in-place.
*/
+        @Override void computeStrip(final MathTransform1D converter) throws TransformException
{
+            final float[] data = buffer.getData(band);
+            source.getSamples(region.x, region.y, region.width, region.height, band, data);
+            converter.transform(data, 0, data, 0, length());
+        }
+    }
+
+
+
+
+    /*
+     * TODO: provide an IntegerToDirect class which would use the SampleModel.getSamples(…,
int[]) method
+     * instead than SampleModel.getSamples(…, float[]). The reason is that the former method
is optimized
+     * in Java2D while the later is not. We would not provide that optimisation for double
target type
+     * because it is less commonly used.
+     */
+
+
+
+
+    /**
+     * Read {@code double} values from the source raster and write {@code double} values
in a temporary buffer.
+     * Note that reading and writing data has {@code double} does not imply that raster data
type must be that type.
+     * The temporary buffer will be written in the target raster as a separated step. The
use of a temporary buffer
+     * is needed when the target raster does not use the {@code double} type, or does not
use a layout that allows
+     * us to write directly in the raster array.
+     *
+     * <div class="note"><b>Note:</b>
+     * having a source raster with {@code double} data type does not remove the need to use
a temporary buffer,
+     * because we can not modify the source data. We still need to allocate a temporary array
for collecting the
+     * operation results before final writing in the target array.</div>
+     */
+    private static final class DoubleToDouble extends Transferer {
+        /** Temporary buffer where to copy data and apply operation. */
+        private double[] buffer;
+
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        DoubleToDouble(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Subdivides the region to process in smaller strips, for smaller {@linkplain #buffer}.
*/
+        @Override int prepareTransferRegion() {
+            return ImageUtilities.prepareTransferRegion(region, DataBuffer.TYPE_DOUBLE);
+        }
+
+        /** Copies source values in temporary buffer, applies conversion then copies to target.
*/
+        @Override void computeStrip(final MathTransform1D converter) throws TransformException
{
+            buffer = source.getSamples(region.x, region.y, region.width, region.height, band,
buffer);
+            converter.transform(buffer, 0, buffer, 0, length());
+            target.setSamples(region.x, region.y, region.width, region.height, band, buffer);
+        }
+    }
+
+
+
+
+    /**
+     * Read {@code float} values from the source raster and write {@code float} values in
a temporary buffer.
+     * Note that reading and writing data has {@code float} does not imply that raster data
type must be that type.
+     * The temporary buffer will be written in the target raster as a separated step. The
use of a temporary buffer
+     * is needed when the target raster does not use the {@code float} type, or does not
use a layout that allows
+     * us to write directly in the raster array.
+     */
+    private static final class FloatToFloat extends Transferer {
+        /** Temporary buffer where to copy data and apply operation. */
+        private float[] buffer;
+
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        FloatToFloat(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Subdivides the region to process in smaller strips, for smaller {@linkplain #buffer}.
*/
+        @Override int prepareTransferRegion() {
+            return ImageUtilities.prepareTransferRegion(region, DataBuffer.TYPE_FLOAT);
+        }
+
+        /** Copies source values in temporary buffer, applies conversion then copies to target.
*/
+        @Override void computeStrip(final MathTransform1D converter) throws TransformException
{
+            buffer = source.getSamples(region.x, region.y, region.width, region.height, band,
buffer);
+            converter.transform(buffer, 0, buffer, 0, length());
+            target.setSamples(region.x, region.y, region.width, region.height, band, buffer);
+        }
+    };
+
+
+
+
+    /**
+     * Read {@code double} values from the source raster and write {@code int} values in
a temporary buffer.
+     * The floating point values will be rounded and clamped to the range of the integer
type.
+     * Note that we do not provide any direct version for integer types because direct access
+     * to {@link DataBuffer} array disable Java2D acceleration on video card.
+     */
+    private static class DoubleToInteger extends Transferer {
+        /** Temporary buffer where to copy data and apply operation. */
+        protected double[] buffer;
+
+        /** Temporary buffer where to round data before transfer to target raster. */
+        protected int[] transfer;
+
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        DoubleToInteger(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Subdivides the region to process in smaller strips, for smaller {@linkplain #buffer}.
*/
+        @Override final int prepareTransferRegion() {
+            return ImageUtilities.prepareTransferRegion(region, DataBuffer.TYPE_DOUBLE);
+        }
+
+        /** Copies source values in temporary buffer, applies conversion then copies to target.
*/
+        @Override final void computeStrip(final MathTransform1D converter) throws TransformException
{
+            final int length = length();
+            buffer = source.getSamples(region.x, region.y, region.width, region.height, band,
buffer);
+            converter.transform(buffer, 0, buffer, 0, length);
+            if (transfer == null) transfer = new int[length];
+            clamp(length);
+            target.setSamples(region.x, region.y, region.width, region.height, band, transfer);
+        }
+
+        /** Clamps data to the range of target integer type. */
+        void clamp(final int length) {
+            for (int i=0; i<length; i++) {
+                transfer[i] = Numerics.clamp(Math.round(buffer[i]));
+            }
+        }
+    }
+
+
+
+
+    /**
+     * Read {@code double} values from the source raster and write signed {@code short} values
in a temporary buffer.
+     * The floating point values will be rounded and clamped to the range of the integer
type.
+     */
+    private static final class DoubleToShort extends DoubleToInteger {
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        DoubleToShort(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Clamps data to the range of target integer type. */
+        @Override void clamp(final int length) {
+            for (int i=0; i<length; i++) {
+                transfer[i] = (int) Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, Math.round(buffer[i])));
+            }
+        }
+    }
+
+
+
+
+    /**
+     * Read {@code double} values from the source raster and write unsigned {@code short}
values in a temporary buffer.
+     * The floating point values will be rounded and clamped to the range of the integer
type.
+     */
+    private static final class DoubleToUShort extends DoubleToInteger {
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        DoubleToUShort(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Clamps data to the range of target integer type. */
+        @Override void clamp(final int length) {
+            for (int i=0; i<length; i++) {
+                transfer[i] = (int) Math.max(0, Math.min(0xFFFF, Math.round(buffer[i])));
+            }
+        }
+    }
+
+
+
+
+    /**
+     * Read {@code double} values from the source raster and write unsigned {@code byte}
values in a temporary buffer.
+     * The floating point values will be rounded and clamped to the range of the integer
type.
+     */
+    private static final class DoubleToByte extends DoubleToInteger {
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        DoubleToByte(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Clamps data to the range of target integer type. */
+        @Override void clamp(final int length) {
+            for (int i=0; i<length; i++) {
+                transfer[i] = (int) Math.max(0, Math.min(0xFF, Math.round(buffer[i])));
+            }
+        }
+    }
+
+
+
+
+    /**
+     * Read {@code float} values from the source raster and write {@code int} values in a
temporary buffer.
+     * The floating point values will be rounded and clamped to the range of the integer
type.
+     * Note that we do not provide any direct version for integer types because direct access
+     * to {@link DataBuffer} array disable Java2D acceleration on video card.
+     */
+    private static class FloatToInteger extends Transferer {
+        /** Temporary buffer where to copy data and apply operation. */
+        protected float[] buffer;
+
+        /** Temporary buffer where to round data before transfer to target raster. */
+        protected int[] transfer;
+
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        FloatToInteger(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Subdivides the region to process in smaller strips, for smaller {@linkplain #buffer}.
*/
+        @Override final int prepareTransferRegion() {
+            return ImageUtilities.prepareTransferRegion(region, DataBuffer.TYPE_FLOAT);
+        }
+
+        /** Copies source values in temporary buffer, applies conversion then copies to target.
*/
+        @Override final void computeStrip(final MathTransform1D converter) throws TransformException
{
+            final int length = length();
+            buffer = source.getSamples(region.x, region.y, region.width, region.height, band,
buffer);
+            converter.transform(buffer, 0, buffer, 0, length);
+            if (transfer == null) transfer = new int[length];
+            clamp(length);
+            target.setSamples(region.x, region.y, region.width, region.height, band, transfer);
+        }
+
+        /** Clamps data to the range of target integer type. */
+        void clamp(final int length) {
+            for (int i=0; i<length; i++) {
+                transfer[i] = Math.round(buffer[i]);
+            }
+        }
+    }
+
+
+
+
+    /**
+     * Read {@code float} values from the source raster and write signed {@code short} values
in a temporary buffer.
+     * The floating point values will be rounded and clamped to the range of the integer
type.
+     */
+    private static final class FloatToShort extends FloatToInteger {
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        FloatToShort(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Clamps data to the range of target integer type. */
+        @Override void clamp(final int length) {
+            for (int i=0; i<length; i++) {
+                transfer[i] = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, Math.round(buffer[i])));
+            }
+        }
+    }
+
+
+
+
+    /**
+     * Read {@code float} values from the source raster and write unsigned {@code short}
values in a temporary buffer.
+     * The floating point values will be rounded and clamped to the range of the integer
type.
+     */
+    private static final class FloatToUShort extends FloatToInteger {
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        FloatToUShort(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Clamps data to the range of target integer type. */
+        @Override void clamp(final int length) {
+            for (int i=0; i<length; i++) {
+                transfer[i] = Math.max(0, Math.min(0xFFFF, Math.round(buffer[i])));
+            }
+        }
+    }
+
+
+
+
+    /**
+     * Read {@code float} values from the source raster and write unsigned {@code byte} values
in a temporary buffer.
+     * The floating point values will be rounded and clamped to the range of the integer
type.
+     */
+    private static final class FloatToByte extends FloatToInteger {
+        /** Creates a new instance for transferring data between the two specified rasters.
*/
+        FloatToByte(final Raster source, final WritableRaster target) {
+            super(source, target);
+        }
+
+        /** Clamps data to the range of target integer type. */
+        @Override void clamp(final int length) {
+            for (int i=0; i<length; i++) {
+                transfer[i] = Math.max(0, Math.min(0xFF, Math.round(buffer[i])));
+            }
+        }
+    }
+
+
+
+
+    /**
+     * Suggests a strategy for transferring data from the given source to the given target.
+     * Some operation can be applied on sample values during the transfer for producing a
+     * computed image.
+     *
+     * @param  source  image tile from which to read sample values.
+     * @param  target  image tile where to write sample values after processing.
+     * @return object to use for applying the operation.
+     */
+    static Transferer suggest(final Raster source, final WritableRaster target) {
+        switch (ImageUtilities.getDataType(target)) {
+            case DataBuffer.TYPE_DOUBLE: {
+                if (isDirect(target)) {
+                    return new DoubleToDirect(source, target);
+                }
+                break;
+            }
+            case DataBuffer.TYPE_FLOAT: {
+                switch (ImageUtilities.getDataType(source)) {
+                    case DataBuffer.TYPE_BYTE:
+                    case DataBuffer.TYPE_SHORT:
+                    case DataBuffer.TYPE_USHORT:        // TODO: consider using IntegerToDirect
here.
+                    case DataBuffer.TYPE_FLOAT: {
+                        if (isDirect(target)) {
+                            return new FloatToDirect(source, target);
+                        } else {
+                            return new FloatToFloat(source, target);
+                        }
+                    }
+                    /*
+                     * TYPE_DOUBLE, TYPE_INT and any unknown types. We handle TYPE_INT as
`double`
+                     * because conversion of 32 bits integer to `float` may lost precision
digits.
+                     */
+                }
+                break;
+            }
+            case DataBuffer.TYPE_INT:    return isFloat(source) ? new FloatToInteger(source,
target) : new DoubleToInteger(source, target);
+            case DataBuffer.TYPE_USHORT: return isFloat(source) ? new FloatToUShort (source,
target) : new DoubleToUShort (source, target);
+            case DataBuffer.TYPE_SHORT:  return isFloat(source) ? new FloatToShort  (source,
target) : new DoubleToShort  (source, target);
+            case DataBuffer.TYPE_BYTE:   return isFloat(source) ? new FloatToByte   (source,
target) : new DoubleToByte   (source, target);
+        }
+        /*
+         * Most conservative fallback, also used for any unknown type.
+         */
+        return new DoubleToDouble(source, target);
+    }
+
+    /**
+     * Returns {@code true} if the given raster use a data type than can be casted to the
{@code float} type
+     * without precision lost. If the type is unknown, then this method returns {@code false}.
Note that this
+     * method also returns {@code false} for {@link DataBuffer#TYPE_INT} because conversion
of 32 bits integer
+     * to the {@code float} type may lost precision digits.
+     */
+    private static boolean isFloat(final Raster source) {
+        switch (ImageUtilities.getDataType(source)) {
+            case DataBuffer.TYPE_BYTE:
+            case DataBuffer.TYPE_USHORT:
+            case DataBuffer.TYPE_SHORT:
+            case DataBuffer.TYPE_FLOAT: return true;
+            default: return false;
+        }
+    }
+
+    /**
+     * Returns {@code true} if the given raster stores sample values at consecutive locations
in each band.
+     * In other words, verifies if the given raster uses a pixel stride of 1 with no gab
between lines.
+     * Another condition is that the data buffer must start at offset 0.
+     */
+    private static boolean isDirect(final Raster target) {
+        final SampleModel sm = target.getSampleModel();
+        if (sm instanceof ComponentSampleModel) {
+            final ComponentSampleModel cm = (ComponentSampleModel) sm;
+            if (cm.getPixelStride() == 1 && cm.getScanlineStride() == target.getWidth())
{
+                for (final int offset : target.getDataBuffer().getOffsets()) {
+                    if (offset != 0) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+}


Mime
View raw message