/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
* http://www.geo-solutions.it/
* Copyright 2014 GeoSolutions
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.geosolutions.jaiext.affine;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import it.geosolutions.jaiext.interpolators.InterpolationBicubic;
import it.geosolutions.jaiext.interpolators.InterpolationBilinear;
import it.geosolutions.jaiext.interpolators.InterpolationNearest;
import it.geosolutions.jaiext.range.Range;
import it.geosolutions.jaiext.range.RangeFactory;
import it.geosolutions.jaiext.testclasses.TestBase;
import it.geosolutions.rendered.viewer.RenderedImageBrowser;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.IOException;
import javax.media.jai.BorderExtender;
import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import javax.media.jai.ROIShape;
import javax.media.jai.RenderedOp;
/**
* This test-class is an extension of the TestBase class inside the jt-utilities project. By calling the testGlobalAffine() method with the selected
* parameters is possible to create an image with the selected preferences and then process it with the preferred interpolation type. Inside the
* testGlobalAffine() method are tested images with all the possible data type by calling the testImageAffine() method. This method is used for
* creating an image with the user-defined parameters(data type, ROI, No Data Range) and then transforming it with 4 possible transformations:
*
* <ul>
* <li>Only Rotation</li>
* <li>Only Scaling</li>
* <li>Only Translation</li>
* <li>Combination of the 3 above</li>
* </ul>
*
* The affine transformation is performed with the selected interpolation type. If the user wants to see the result image with the selected kind of
* test, must set JAI.Ext.Interactive parameter to true, JAI.Ext.TestSelector from 0 to 5, JAI.Ext.TransformationSelector from 0 to 3 (one of the
* above described transformations)and JAI.Ext.InverseScale to 0 or 1 (Magnification/reduction) to the Console. The testAllOperation() method is used
* for grouping all the tests on the same image with 4 different transformations in only one test-method. The methods testImage() and testGlobal() are
* not supported, they are defined in the jt-scale project.
*/
public class TestAffine extends TestBase {
/** Quadrant rotation number for the Affine transformation */
protected int numquadrants = 1;
/** X coordinate point for rotation */
protected double anchorX = 0;
/** Y coordinate point for rotation */
protected double anchorY = DEFAULT_HEIGHT - 1;
/** Integer indicating which operation should be visualized */
public static Integer TRANSFORMATION_SELECTOR = Integer
.getInteger("JAI.Ext.TransformationSelector");
protected float transY = -DEFAULT_HEIGHT;
protected <T extends Number & Comparable<? super T>> void testImageAffine(
RenderedImage sourceImage, int dataType, T noDataValue, boolean useROIAccessor,
boolean isBinary, boolean bicubic2Disabled, boolean noDataRangeUsed,
boolean roiPresent, boolean setDestinationNoData, TransformationType transformType,
InterpolationType interpType, TestSelection testSelect, ScaleType scaleValue) {
if (scaleValue == ScaleType.REDUCTION) {
scaleX = 0.5f;
scaleY = 0.5f;
} else {
scaleX = 1.5f;
scaleY = 1.5f;
}
// No Data Range
Range noDataRange = null;
if (noDataRangeUsed && !isBinary) {
switch (dataType) {
case DataBuffer.TYPE_BYTE:
noDataRange = RangeFactory.create(noDataValue.byteValue(), true,
noDataValue.byteValue(), true);
break;
case DataBuffer.TYPE_USHORT:
noDataRange = RangeFactory.create(noDataValue.shortValue(), true,
noDataValue.shortValue(), true);
break;
case DataBuffer.TYPE_SHORT:
noDataRange = RangeFactory.create(noDataValue.shortValue(), true,
noDataValue.shortValue(), true);
break;
case DataBuffer.TYPE_INT:
noDataRange = RangeFactory.create(noDataValue.intValue(), true,
noDataValue.intValue(), true);
break;
case DataBuffer.TYPE_FLOAT:
noDataRange = RangeFactory.create(noDataValue.floatValue(), true,
noDataValue.floatValue(), true,true);
break;
case DataBuffer.TYPE_DOUBLE:
noDataRange = RangeFactory.create(noDataValue.doubleValue(), true,
noDataValue.doubleValue(), true,true);
break;
default:
throw new IllegalArgumentException("Wrong data type");
}
}
// ROI
ROIShape roi = null;
if (roiPresent) {
roi = roiCreation();
}
// Hints are used only with roiAccessor
RenderingHints hints = null;
if (useROIAccessor) {
hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER,
BorderExtender.createInstance(BorderExtender.BORDER_ZERO));
}
// Affine Transform selection
AffineTransform transform = null;
if (transformType == TransformationType.ROTATE_OP) {
// Rotation
transform = AffineTransform.getQuadrantRotateInstance(numquadrants, anchorX, anchorY);
} else if (transformType == TransformationType.SCALE_OP) {
// Scale (X and Y doubled)
transform = AffineTransform.getScaleInstance(scaleX, scaleY);
} else if (transformType == TransformationType.TRANSLATE_OP) {
transX = DEFAULT_WIDTH;
transY = 0;
// Translation
transform = AffineTransform.getTranslateInstance(transX, transY);
} else if (transformType == TransformationType.ALL) {
transX = 0;
transY = -DEFAULT_HEIGHT;
// Rotation
transform = AffineTransform.getQuadrantRotateInstance(numquadrants, anchorX, anchorY);
// + Scale (X and Y doubled)
transform.concatenate(AffineTransform.getScaleInstance(scaleX, scaleY));
// + Translation (translation towards the center of the image)
transform.concatenate(AffineTransform.getTranslateInstance(transX, transY));
} else {
transform = new AffineTransform();
}
RenderedImage destinationIMG = null;
// Interpolator initialization
Interpolation interpN = null;
Interpolation interpB = null;
Interpolation interpBN = null;
// Interpolators
switch (interpType) {
case NEAREST_INTERP:
// Nearest-Neighbor
interpN = new javax.media.jai.InterpolationNearest();
// Affine operation
destinationIMG = AffineDescriptor.create(sourceImage, transform, interpN, new double[] {destinationNoData},
(ROI) roi, useROIAccessor, setDestinationNoData, noDataRange, hints);
break;
case BILINEAR_INTERP:
// Bilinear
interpB = new javax.media.jai.InterpolationBilinear(DEFAULT_SUBSAMPLE_BITS);
if (hints != null) {
hints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender
.createInstance(BorderExtender.BORDER_COPY)));
} else {
hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER,
BorderExtender.createInstance(BorderExtender.BORDER_COPY));
}
// Affine operation
destinationIMG = AffineDescriptor.create(sourceImage, transform, interpB, new double[] {destinationNoData},
(ROI) roi, useROIAccessor, setDestinationNoData, noDataRange, hints);
break;
case BICUBIC_INTERP:
// Bicubic
interpBN = new javax.media.jai.InterpolationBicubic(DEFAULT_SUBSAMPLE_BITS);
if (hints != null) {
hints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender
.createInstance(BorderExtender.BORDER_COPY)));
} else {
hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER,
BorderExtender.createInstance(BorderExtender.BORDER_COPY));
}
// Affine operation
destinationIMG = AffineDescriptor.create(sourceImage, transform, interpBN, new double[] {destinationNoData},
(ROI) roi, useROIAccessor, setDestinationNoData, noDataRange, hints);
break;
default:
break;
}
if (INTERACTIVE && dataType == DataBuffer.TYPE_BYTE
&& TEST_SELECTOR == testSelect.getType()
&& TRANSFORMATION_SELECTOR == transformType.getValue()
&& INVERSE_SCALE == scaleValue.getType()) {
RenderedImageBrowser.showChain(destinationIMG, false, roiPresent);
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// Forcing to retrieve an array of all the image tiles
((PlanarImage) destinationIMG).getTiles();
}
// Control if the operation has been correctly performed(can be done only because
// the image is a square and so even if it is rotated, its dimensions are unchanged).
// Control if the ROI has been expanded
PlanarImage planarIMG = (PlanarImage) destinationIMG;
if (transformType == TransformationType.SCALE_OP) {
if (!isBinary && roiPresent) {
// Control if the ROI has been expanded
int imgWidthROI = destinationIMG.getWidth() * 3 / 4 - 2;
int imgHeightROI = destinationIMG.getHeight() * 3 / 4 - 2;
int tileInROIx = planarIMG.XToTileX(imgWidthROI);
int tileInROIy = planarIMG.YToTileY(imgHeightROI);
Raster testTile = destinationIMG.getTile(tileInROIx, tileInROIy);
boolean interpNear = false;
if (interpN != null) {
interpNear = true;
}
testROI(dataType, testTile, interpNear);
// Check minimum and maximum value for a tile
int xFirstTile = destinationIMG.getMinTileX() + destinationIMG.getNumXTiles() - 1;
int ySecondTile = destinationIMG.getMinTileY() + destinationIMG.getNumYTiles() - 1;
Raster simpleTile = destinationIMG.getTile(xFirstTile, ySecondTile);
testEmptyImage(dataType, simpleTile, isBinary);
}
// width
assertEquals((int) (DEFAULT_WIDTH * scaleX), destinationIMG.getWidth());
// height
assertEquals((int) (DEFAULT_HEIGHT * scaleY), destinationIMG.getHeight());
} else if (transformType == TransformationType.TRANSLATE_OP) {
if (!isBinary && roiPresent) {
// Control if the ROI has been expanded
int imgWidthROI = destinationIMG.getMinX() + destinationIMG.getWidth() / 4 - 1;
int imgHeightROI = destinationIMG.getMinY() + destinationIMG.getHeight() * 3 / 4
- 1;
int tileInROIx = planarIMG.XToTileX(imgWidthROI);
int tileInROIy = planarIMG.YToTileY(imgHeightROI);
Raster testTile = destinationIMG.getTile(tileInROIx, tileInROIy);
boolean interpNear = false;
if (interpN != null) {
interpNear = true;
}
testROI(dataType, testTile, interpNear);
// Check minimum and maximum value for a tile
int xFirstTile = destinationIMG.getMinTileX() + destinationIMG.getNumXTiles() - 1;
int ySecondTile = destinationIMG.getMinTileY() + destinationIMG.getNumYTiles() - 1;
Raster simpleTile = destinationIMG.getTile(xFirstTile, ySecondTile);
testEmptyImage(dataType, simpleTile, isBinary);
}
double actualX = destinationIMG.getMinX();
double actualY = destinationIMG.getMinY();
double expectedX = sourceImage.getMinX() + transX;
double expectedY = sourceImage.getMinY() + transY;
double tolerance = 0.1f;
// X axis
assertEquals(expectedX, actualX, tolerance);
// Y axis
assertEquals(expectedY, actualY, tolerance);
} else if (transformType == TransformationType.ROTATE_OP) {
// Control if the ROI has been expanded
if (!isBinary && roiPresent) {
int imgWidthROI = destinationIMG.getMinX() + destinationIMG.getWidth() / 4 + 1;
int imgHeightROI = destinationIMG.getMinY() + destinationIMG.getHeight() * 3 / 4
- 1;
int tileInROIx = planarIMG.XToTileX(imgWidthROI);
int tileInROIy = planarIMG.YToTileY(imgHeightROI);
Raster testTile = destinationIMG.getTile(tileInROIx, tileInROIy);
boolean interpNear = false;
if (interpN != null) {
interpNear = true;
}
testROI(dataType, testTile, interpNear);
// Check minimum and maximum value for a tile
int xFirstTile = destinationIMG.getMinTileX() + 1; //+ destinationIMG.getNumXTiles() - 1;
int ySecondTile = destinationIMG.getMinTileY()+ 1;
Raster simpleTile = destinationIMG.getTile(xFirstTile, ySecondTile);
testEmptyImage(dataType, simpleTile, isBinary);
}
// width
assertEquals((int) (DEFAULT_WIDTH), destinationIMG.getHeight());
// height
assertEquals((int) (DEFAULT_HEIGHT), destinationIMG.getWidth());
}
// Final Image disposal
if (destinationIMG instanceof RenderedOp) {
((RenderedOp) destinationIMG).dispose();
}
}
// Test for checking if the ROI is correctly expanded or reduced
protected void testROI(int dataType, Raster testTile, boolean interpNearest) {
switch (dataType) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_INT:
int value = 0;
if (interpNearest) {
value = testTile.getSample(testTile.getMinX(), testTile.getMinY() + 2, 0);
assertFalse(value == (int) destinationNoData);
} else {
value = testTile.getSample(testTile.getMinX(), testTile.getMinY(), 0);
assertFalse(value == (int) destinationNoData);
}
break;
case DataBuffer.TYPE_FLOAT:
if (interpNearest) {
float valuef = testTile.getSampleFloat(testTile.getMinX(), testTile.getMinY() + 2,
0);
assertFalse((int) valuef == (int) destinationNoData);
} else {
float valuef = testTile.getSampleFloat(testTile.getMinX(), testTile.getMinY(), 0);
assertFalse((int) valuef == (int) destinationNoData);
}
break;
case DataBuffer.TYPE_DOUBLE:
if (interpNearest) {
double valued = testTile.getSampleDouble(testTile.getMinX(),
testTile.getMinY() + 2, 0);
assertFalse(valued == destinationNoData);
} else {
double valued = testTile.getSampleDouble(testTile.getMinX(), testTile.getMinY(), 0);
assertFalse(valued == destinationNoData);
}
break;
default:
throw new IllegalArgumentException("Wrong data type");
}
}
protected void testEmptyImage(int dataType, Raster simpleTile, boolean isBinary) {
int tileminX = simpleTile.getMinX();
int tileminY = simpleTile.getMinY();
int tileWidth = tileminX + simpleTile.getWidth();
int tileHeight = tileminY + simpleTile.getHeight();
switch (dataType) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_INT:
int minValue = Integer.MAX_VALUE;
int maxValue = Integer.MIN_VALUE;
for (int i = tileminY; i < tileHeight; i++) {
for (int j = tileminX; j < tileWidth; j++) {
int value = simpleTile.getSample(j, i, 0);
if (value > maxValue) {
maxValue = value;
}
if (value < minValue) {
minValue = value;
}
}
}
// Check if the values are not max and minimum value
//assertFalse(minValue == maxValue);
assertFalse(minValue == Integer.MAX_VALUE);
assertFalse(maxValue == Integer.MIN_VALUE);
assertEquals(minValue, destinationNoData, 1E-6);
assertEquals(maxValue, destinationNoData, 1E-6);
break;
case DataBuffer.TYPE_FLOAT:
float minValuef = Float.MAX_VALUE;
float maxValuef = -Float.MAX_VALUE;
for (int i = tileminY; i < tileHeight; i++) {
for (int j = tileminX; j < tileWidth; j++) {
float valuef = simpleTile.getSample(j, i, 0);
if (Float.isNaN(valuef) || valuef == Float.POSITIVE_INFINITY
|| valuef == Float.POSITIVE_INFINITY) {
valuef = 255;
}
if (valuef > maxValuef) {
maxValuef = valuef;
}
if (valuef < minValuef) {
minValuef = valuef;
}
}
}
// Check if the values are not max and minimum value
//assertFalse((int) minValuef == (int) maxValuef);
assertFalse(minValuef == Float.MAX_VALUE);
assertFalse(maxValuef == -Float.MAX_VALUE);
assertEquals(minValuef, destinationNoData, 1E-6);
assertEquals(maxValuef, destinationNoData, 1E-6);
break;
case DataBuffer.TYPE_DOUBLE:
double minValued = Double.MAX_VALUE;
double maxValued = -Double.MAX_VALUE;
for (int i = tileminY; i < tileHeight; i++) {
for (int j = tileminX; j < tileWidth; j++) {
double valued = simpleTile.getSampleDouble(j, i, 0);
if (Double.isNaN(valued) || valued == Double.POSITIVE_INFINITY
|| valued == Double.POSITIVE_INFINITY) {
valued = 255;
}
if (valued > maxValued) {
maxValued = valued;
}
if (valued < minValued) {
minValued = valued;
}
}
}
// Check if the values are not max and minimum value
//assertFalse((int) minValued == (int) maxValued);
assertFalse(minValued == Double.MAX_VALUE);
assertFalse(maxValued == -Double.MAX_VALUE);
assertEquals(minValued, destinationNoData, 1E-6);
assertEquals(maxValued, destinationNoData, 1E-6);
break;
default:
throw new IllegalArgumentException("Wrong data type");
}
}
protected void testGlobalAffine(boolean useROIAccessor, boolean isBinary,
boolean bicubic2Disabled, boolean noDataRangeUsed, boolean roiPresent,
boolean setDestinationNoData, InterpolationType interpType, TestSelection testSelect,
ScaleType scaleValue) {
Byte sourceNoDataByte = 100;
Short sourceNoDataUshort = Short.MAX_VALUE - 1;
Short sourceNoDataShort = -255;
Integer sourceNoDataInt = Integer.MAX_VALUE - 1;
Float sourceNoDataFloat = -15.2f;
Double sourceNoDataDouble = Double.POSITIVE_INFINITY;
if (isBinary) {
sourceNoDataByte = 1;
sourceNoDataUshort = 1;
sourceNoDataInt = 1;
// destination no data Value
destinationNoData = 0;
} else {
// destination no data Value
destinationNoData = 255;
}
// ImageTest
// starting dataType
int dataType = DataBuffer.TYPE_BYTE;
testAllOperation(dataType, isBinary, sourceNoDataByte, useROIAccessor, bicubic2Disabled,
noDataRangeUsed, roiPresent, setDestinationNoData, interpType, testSelect,
scaleValue);
dataType = DataBuffer.TYPE_USHORT;
testAllOperation(dataType, isBinary, sourceNoDataUshort, useROIAccessor, bicubic2Disabled,
noDataRangeUsed, roiPresent, setDestinationNoData, interpType, testSelect,
scaleValue);
dataType = DataBuffer.TYPE_INT;
testAllOperation(dataType, isBinary, sourceNoDataInt, useROIAccessor, bicubic2Disabled,
noDataRangeUsed, roiPresent, setDestinationNoData, interpType, testSelect,
scaleValue);
if (!isBinary) {
dataType = DataBuffer.TYPE_SHORT;
testAllOperation(dataType, isBinary, sourceNoDataShort, useROIAccessor,
bicubic2Disabled, noDataRangeUsed, roiPresent, setDestinationNoData,
interpType, testSelect, scaleValue);
dataType = DataBuffer.TYPE_FLOAT;
testAllOperation(dataType, isBinary, sourceNoDataFloat, useROIAccessor,
bicubic2Disabled, noDataRangeUsed, roiPresent, setDestinationNoData,
interpType, testSelect, scaleValue);
dataType = DataBuffer.TYPE_DOUBLE;
testAllOperation(dataType, isBinary, sourceNoDataDouble, useROIAccessor,
bicubic2Disabled, noDataRangeUsed, roiPresent, setDestinationNoData,
interpType, testSelect, scaleValue);
}
}
protected <T extends Number & Comparable<? super T>> void testAllOperation(int dataType,
boolean isBinary, T sourceNoData, boolean useROIAccessor, boolean bicubic2Disabled,
boolean noDataRangeUsed, boolean roiPresent, boolean setDestinationNoData,
InterpolationType interpType, TestSelection testSelect, ScaleType scaleValue) {
RenderedImage sourceImage = createTestImage(dataType, DEFAULT_WIDTH, DEFAULT_HEIGHT,
sourceNoData, isBinary);
testImageAffine(sourceImage, dataType, sourceNoData, useROIAccessor, isBinary,
bicubic2Disabled, noDataRangeUsed, roiPresent, setDestinationNoData,
TransformationType.ROTATE_OP, interpType, testSelect, scaleValue);
testImageAffine(sourceImage, dataType, sourceNoData, useROIAccessor, isBinary,
bicubic2Disabled, noDataRangeUsed, roiPresent, setDestinationNoData,
TransformationType.TRANSLATE_OP, interpType, testSelect, scaleValue);
testImageAffine(sourceImage, dataType, sourceNoData, useROIAccessor, isBinary,
bicubic2Disabled, noDataRangeUsed, roiPresent, setDestinationNoData,
TransformationType.SCALE_OP, interpType, testSelect, scaleValue);
testImageAffine(sourceImage, dataType, sourceNoData, useROIAccessor, isBinary,
bicubic2Disabled, noDataRangeUsed, roiPresent, setDestinationNoData,
TransformationType.ALL, interpType, testSelect, scaleValue);
}
}