001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    Copyright © 2012-2023 Open Geospatial Consortium, Inc.
004 *    http://www.geoapi.org
005 *
006 *    Licensed under the Apache License, Version 2.0 (the "License");
007 *    you may not use this file except in compliance with the License.
008 *    You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *    Unless required by applicable law or agreed to in writing, software
013 *    distributed under the License is distributed on an "AS IS" BASIS,
014 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 *    See the License for the specific language governing permissions and
016 *    limitations under the License.
017 */
018package org.opengis.test.coverage.image;
019
020import java.util.Random;
021import java.io.Closeable;
022import java.io.IOException;
023import java.awt.Rectangle;
024import java.awt.image.RenderedImage;
025import javax.imageio.IIOParam;
026
027
028/**
029 * Base class for all image I/O tests.
030 *
031 * @author  Martin Desruisseaux (Geomatys)
032 * @version 3.1
033 * @since   3.1
034 */
035@SuppressWarnings("strictfp")   // Because we still target Java 11.
036public strictfp abstract class ImageIOTestCase extends ImageBackendTestCase {
037    /**
038     * Default number of iterations when testing the image read operations.
039     */
040    static final int DEFAULT_NUM_ITERATIONS = 10;
041
042    /**
043     * {@code true} if the reader or writer takes in account the parameter value given to
044     * {@link IIOParam#setSourceRegion(Rectangle)}. The default value is {@code true}.
045     * Subclasses can set this flag to {@code false} when testing an incomplete implementation.
046     */
047    protected boolean isSubregionSupported = true;
048
049    /**
050     * {@code true} if the reader or writer takes in account the two first parameter values given to
051     * {@link IIOParam#setSourceSubsampling(int, int, int, int)}. The default value is {@code true}.
052     * Subclasses can set this flag to {@code false} when testing an incomplete implementation.
053     */
054    protected boolean isSubsamplingSupported = true;
055
056    /**
057     * {@code true} if the reader or writer takes in account the two last parameter values given to
058     * {@link IIOParam#setSourceSubsampling(int, int, int, int)}. The default value is {@code true}.
059     * Subclasses can set this flag to {@code false} when testing an incomplete implementation.
060     */
061    protected boolean isSubsamplingOffsetSupported = true;
062
063    /**
064     * {@code true} if the reader or writer takes in account the parameter value given to
065     * {@link IIOParam#setSourceBands(int[])}. The default value is {@code true}.
066     * Subclasses can set this flag to {@code false} if this feature cannot be tested for
067     * the current implementation.
068     *
069     * <p>Note that this feature cannot be tested with some standard readers like PNG, because
070     * those readers require an explicit destination image to be specified if the number of bands
071     * to read differs from the number of bands in the source image.</p>
072     */
073    protected boolean isSourceBandsSupported = true;
074
075    /**
076     * The tolerance threshold to use when comparing floating point numbers. The default value
077     * is 0. Subclasses can relax this tolerance threshold if needed.
078     *
079     * <p>This threshold applies only to values of type {@code float} and {@code double};
080     * it doesn't apply to integer types.</p>
081     */
082    protected double sampleToleranceThreshold;
083
084    /**
085     * The random number generator.
086     */
087    final Random random;
088
089    /**
090     * Creates a new test case using a default random number generator.
091     * The sub-regions, sub-samplings and source bands will be different
092     * for every test execution. If reproducible subsetting sequences are
093     * needed, use the {@link #ImageIOTestCase(long)} constructor instead.
094     */
095    protected ImageIOTestCase() {
096        random = new Random();
097    }
098
099    /**
100     * Creates a new test case using a random number generator initialized to the given seed.
101     *
102     * @param seed  the initial seed for the random numbers generator. Use a constant value if
103     *        the tests need to be reproduced with the same sequence of image parameters.
104     */
105    protected ImageIOTestCase(final long seed) {
106        random = new Random(seed);
107    }
108
109    /**
110     * Returns an iterator on a random region, subsampling and source bands of the given image.
111     * The selected parameters are set in the given {@code param} object.
112     *
113     * @param  image  the image for which to return an iterator.
114     * @param  param  the parameter where to set the random region, subsampling and source bands.
115     * @return an iterator over a random region of the specified image.
116     */
117    final PixelIterator getIteratorOnRandomSubset(final RenderedImage image, final IIOParam param) {
118        final Rectangle region = getBounds(image);
119        if (isSubregionSupported) {
120            final int dx = random.nextInt(region.width);
121            final int dy = random.nextInt(region.height);
122            region.x     += random.nextInt(region.width  - dx);
123            region.y     += random.nextInt(region.height - dy);
124            region.width  = dx + 1;
125            region.height = dy + 1;
126            param.setSourceRegion(region);
127        }
128        int xSubsampling=1, ySubsampling=1;
129        if (isSubsamplingSupported) {
130            xSubsampling = random.nextInt(region.width /2 + 1) + 1;
131            ySubsampling = random.nextInt(region.height/2 + 1) + 1;
132            int xOffset=0, yOffset=0;
133            if (isSubsamplingOffsetSupported) {
134                xOffset = random.nextInt(xSubsampling);
135                yOffset = random.nextInt(ySubsampling);
136                region.x      += xOffset;
137                region.y      += yOffset;
138                region.width  -= xOffset;
139                region.height -= yOffset;
140            }
141            param.setSourceSubsampling(xSubsampling, ySubsampling, xOffset, yOffset);
142        }
143        int[] sourceBands = null;
144        if (isSourceBandsSupported) {
145            final int numBands = image.getSampleModel().getNumBands();
146            sourceBands = new int[random.nextInt(numBands) + 1];
147            for (int i=0; i<sourceBands.length; i++) {
148                int band;
149                do band = random.nextInt(numBands);
150                while (contains(sourceBands, i, band));
151                sourceBands[i] = band;
152            }
153            param.setSourceBands(sourceBands);
154        }
155        return new PixelIterator(image, region, xSubsampling, ySubsampling, sourceBands);
156    }
157
158    /**
159     * Closes the given input or output stream if it implements the {@link Closeable} interface.
160     * Do nothing otherwise.
161     *
162     * @param  stream  the input or output stream to close, or {@code null} if none.
163     * @throws IOException if an error occurred while closing the stream.
164     */
165    static void close(final Object stream) throws IOException {
166        if (stream instanceof Closeable) {
167            ((Closeable) stream).close();
168        }
169    }
170}