/* 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.scale;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.awt.RenderingHints;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.renderable.ParameterBlock;
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.ROIShape;
import javax.media.jai.RenderedOp;
import javax.media.jai.TiledImage;
import org.junit.Test;
import it.geosolutions.jaiext.range.Range;
import it.geosolutions.jaiext.range.RangeFactory;
import it.geosolutions.jaiext.testclasses.TestBase;
import it.geosolutions.rendered.viewer.RenderedImageBrowser;
/**
* This test-class is an extension of the TestBase class inside the jt-utilities project. By calling the testGlobal() 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
* testGlobal() method are tested images with all the possible data type by calling the testImage() method. This method is used for creating an image
* with the user-defined parameters(data type, ROI, No Data Range) and then scaling it with the supplied interpolation type. If the user wants to see
* a scaled image with the selected type of test, must set JAI.Ext.Interactive parameter to true, JAI.Ext.TestSelector from 0 to 5 and
* JAI.Ext.InverseScale to 0 or 1 (Magnification/reduction) to the Console. The methods testImageAffine() testGlobalAffine() are not supported, they
* are defined in the jt-affine project.
*/
public class TestScale extends TestBase {
protected void testGlobal(boolean useROIAccessor, boolean isBinary, boolean bicubic2Disabled,
boolean noDataRangeUsed, boolean roiPresent,
it.geosolutions.jaiext.testclasses.TestBase.InterpolationType interpType,
it.geosolutions.jaiext.testclasses.TestBase.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.NEGATIVE_INFINITY;
if (isBinary) {
sourceNoDataByte = 1;
sourceNoDataUshort = 1;
sourceNoDataInt = 1;
}
// ImageTest
// starting dataType
int dataType = DataBuffer.TYPE_BYTE;
testImage(dataType, sourceNoDataByte, useROIAccessor, isBinary, bicubic2Disabled,
noDataRangeUsed, roiPresent, interpType, testSelect, scaleValue);
dataType = DataBuffer.TYPE_USHORT;
testImage(dataType, sourceNoDataUshort, useROIAccessor, isBinary, bicubic2Disabled,
noDataRangeUsed, roiPresent, interpType, testSelect, scaleValue);
dataType = DataBuffer.TYPE_INT;
testImage(dataType, sourceNoDataInt, useROIAccessor, isBinary, bicubic2Disabled,
noDataRangeUsed, roiPresent, interpType, testSelect, scaleValue);
if (!isBinary) {
dataType = DataBuffer.TYPE_SHORT;
testImage(dataType, sourceNoDataShort, useROIAccessor, isBinary, bicubic2Disabled,
noDataRangeUsed, roiPresent, interpType, testSelect, scaleValue);
dataType = DataBuffer.TYPE_FLOAT;
testImage(dataType, sourceNoDataFloat, useROIAccessor, isBinary, bicubic2Disabled,
noDataRangeUsed, roiPresent, interpType, testSelect, scaleValue);
dataType = DataBuffer.TYPE_DOUBLE;
testImage(dataType, sourceNoDataDouble, useROIAccessor, isBinary, bicubic2Disabled,
noDataRangeUsed, roiPresent, interpType, testSelect, scaleValue);
}
}
protected <T extends Number & Comparable<? super T>> void testImage(int dataType,
T noDataValue, boolean useROIAccessor, boolean isBinary, boolean bicubic2Disabled,
boolean noDataRangeUsed, boolean roiPresent, 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;
// Source test image
RenderedImage sourceImage = null;
if (isBinary) {
// destination no data Value
destinationNoData = 0;
} else {
// destination no data Value
destinationNoData = 255;
}
sourceImage = createTestImage(dataType, DEFAULT_WIDTH, DEFAULT_HEIGHT, noDataValue,
isBinary);
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));
}
// Interpolator initialization
Interpolation interp = null;
// Interpolators
switch (interpType) {
case NEAREST_INTERP:
// Nearest-Neighbor
interp = new javax.media.jai.InterpolationNearest();
break;
case BILINEAR_INTERP:
// Bilinear
interp = 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));
}
break;
case BICUBIC_INTERP:
// Bicubic
interp = 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));
}
break;
default:
break;
}
// Background
double[] bkg = new double[]{destinationNoData};
// Scale operation
RenderedImage destinationIMG = ScaleDescriptor.create(sourceImage, scaleX, scaleY,
transX, transY, interp, roi, useROIAccessor, noDataRange, bkg, hints);
if (INTERACTIVE && dataType == DataBuffer.TYPE_BYTE
&& TEST_SELECTOR == testSelect.getType() && INVERSE_SCALE == scaleValue.getType()) {
RenderedImageBrowser.showChain(destinationIMG, false, roiPresent);
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// image tile calculation for searching possible errors
((PlanarImage) destinationIMG).getTiles();
}
// Check minimum and maximum value for a tile
Raster simpleTile = destinationIMG.getTile(destinationIMG.getMinTileX(),
destinationIMG.getMinTileY());
int tileWidth = simpleTile.getWidth();
int tileHeight = simpleTile.getHeight();
switch (dataType) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_INT:
if (!isBinary) {
int minValue = Integer.MAX_VALUE;
int maxValue = Integer.MIN_VALUE;
for (int i = 0; i < tileHeight; i++) {
for (int j = 0; 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);
}
break;
case DataBuffer.TYPE_FLOAT:
float minValuef = Float.MAX_VALUE;
float maxValuef = -Float.MAX_VALUE;
for (int i = 0; i < tileHeight; i++) {
for (int j = 0; 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);
break;
case DataBuffer.TYPE_DOUBLE:
double minValued = Double.MAX_VALUE;
double maxValued = -Double.MAX_VALUE;
for (int i = 0; i < tileHeight; i++) {
for (int j = 0; 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);
break;
default:
throw new IllegalArgumentException("Wrong data type");
}
// Control if the ROI has been expanded
PlanarImage planarIMG = (PlanarImage) destinationIMG;
int imgWidthROI = destinationIMG.getWidth() * 3 / 4 - 1;
int imgHeightROI = destinationIMG.getHeight() * 3 / 4 - 1;
int tileInROIx = planarIMG.XToTileX(imgWidthROI);
int tileInROIy = planarIMG.YToTileY(imgHeightROI);
Raster testTile = destinationIMG.getTile(tileInROIx, tileInROIy);
switch (dataType) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_INT:
if (!isBinary) {
int value = testTile.getSample(testTile.getMinX() + 2, testTile.getMinY() + 1, 0);
assertFalse(value == (int) destinationNoData);
}
break;
case DataBuffer.TYPE_FLOAT:
float valuef = testTile.getSampleFloat(testTile.getMinX() + 2, testTile.getMinY() + 1,
0);
assertFalse((int) valuef == (int) destinationNoData);
break;
case DataBuffer.TYPE_DOUBLE:
double valued = testTile.getSampleDouble(testTile.getMinX() + 2,
testTile.getMinY() + 1, 0);
assertFalse(valued == destinationNoData);
break;
default:
throw new IllegalArgumentException("Wrong data type");
}
// Forcing to retrieve an array of all the image tiles
// Control if the scale operation has been correctly performed
// width
assertEquals((int) (DEFAULT_WIDTH * scaleX), destinationIMG.getWidth());
// height
assertEquals((int) (DEFAULT_HEIGHT * scaleY), destinationIMG.getHeight());
//Final Image disposal
if(destinationIMG instanceof RenderedOp){
((RenderedOp)destinationIMG).dispose();
}
}
public void assertNoDataBleedByte(Interpolation interpolation) {
final byte constant = (byte) (0xff & 255);
RenderedImage source = getConstantImage(10, 10, new Byte[] {constant});
assertNoDataBleed(interpolation, source, 255);
}
public void assertNoDataBleedShort(Interpolation interpolation) {
final short constant = (short) (0xffff & 65535);
RenderedImage source = getConstantImage(10, 10, new Short[] {constant});
assertNoDataBleed(interpolation, source, constant);
}
public void assertNoDataBleedFloat(Interpolation interpolation) {
final float constant = 65535;
RenderedImage source = getConstantImage(10, 10, new Float[] {constant});
assertNoDataBleed(interpolation, source, (int) constant);
}
public void assertNoDataBleedDouble(Interpolation interpolation) {
final double constant = 65535;
RenderedImage source = getConstantImage(10, 10, new Double[] {constant});
assertNoDataBleed(interpolation, source, (int) constant);
}
private void assertNoDataBleed(Interpolation interpolation, RenderedImage source, int expectedValue) {
RenderingHints hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER,BorderExtender.createInstance(BorderExtender.BORDER_COPY));
RenderedImage scaled= ScaleDescriptor.create(source, 2f, 2f,
0f, 0f, interpolation, null, null, RangeFactory.create(0, 0), null, hints);
// make sure all pixels are solid like the input ones
Raster raster = scaled.getData();
for (int i = raster.getMinY(); i < raster.getMinY() + raster.getHeight(); i++) {
for (int j = raster.getMinX(); j < raster.getMinX() + raster.getWidth(); j++) {
int value = raster.getSample(j, i, 0);
assertEquals("Unexpected value at " + i + ", " + j + ": " + value, expectedValue, value);
}
}
}
protected RenderedImage getConstantImage(float width, float height, Number[] values) {
ParameterBlock pb = new ParameterBlock();
pb.add(width);
pb.add(height);
pb.add(values);
return JAI.create("constant", pb);
}
protected void assertInterpolateInHole(Interpolation interpolation) {
assertInterpolateInHole(DataBuffer.TYPE_BYTE, interpolation);
assertInterpolateInHole(DataBuffer.TYPE_USHORT, interpolation);
}
protected void assertInterpolateInHole(int dataType, Interpolation interpolation) {
// build image with high ring at borders and almost nodata in the middle
SampleModel sm = new ComponentSampleModel(dataType, 10, 10, 1, 10, new int[] {0});
int width = 10;
int height = 10;
TiledImage source = new TiledImage(0, 0, 10, 10, 0, 0, sm, PlanarImage.createColorModel(sm));
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if(x == 0 || x == (width - 1) || y == 0 || y == (height - 1)) {
source.setSample(x, y, 0, 255);
} else {
source.setSample(x, y, 0, 1);
}
}
}
RenderingHints hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER,BorderExtender.createInstance(BorderExtender.BORDER_COPY));
RenderedImage scaled= ScaleDescriptor.create(source, 2f, 2f,
0f, 0f, interpolation, null, null, RangeFactory.create(0, 0), null, hints);
// make sure none of the pixels became 0
Raster raster = scaled.getData();
for (int i = raster.getMinY(); i < raster.getMinY() + raster.getHeight(); i++) {
for (int j = raster.getMinX(); j < raster.getMinX() + raster.getWidth(); j++) {
int value = raster.getSample(j, i, 0);
assertTrue("Expected valid value but found nodata", value > 0);
}
}
}
}