/* 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 java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.DataBuffer;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderContext;
import java.awt.image.renderable.RenderableImage;
import javax.media.jai.BorderExtender;
import javax.media.jai.CRIFImpl;
import javax.media.jai.ImageLayout;
import javax.media.jai.Interpolation;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import com.sun.media.jai.mlib.MlibAffineRIF;
import com.sun.media.jai.opimage.CopyOpImage;
import com.sun.media.jai.opimage.RIFUtil;
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.scale.ScaleBicubicOpImage;
import it.geosolutions.jaiext.scale.ScaleBilinearOpImage;
import it.geosolutions.jaiext.scale.ScaleGeneralOpImage;
import it.geosolutions.jaiext.scale.ScaleNearestOpImage;
import it.geosolutions.jaiext.translate.TranslateIntOpImage;
import it.geosolutions.jaiext.utilities.ImageUtilities;
/**
* @since EA4
* @see AffineOpimage, ScaleOpImage
*/
public class AffineCRIF extends CRIFImpl {
private static final float TOLERANCE = 0.01F;
/** Constructor. */
public AffineCRIF() {
super("affine");
}
/**
* Creates an affine operation as an instance of AffineOpImage.
*/
public RenderedImage create(ParameterBlock paramBlock, RenderingHints renderHints) {
// Get ImageLayout from renderHints if any.
ImageLayout layout = RIFUtil.getImageLayoutHint(renderHints);
// Get TileCache from renderHints if any.
// TileCache cache = RIFUtil.getTileCacheHint(renderHints);
// Get BorderExtender from renderHints if any.
BorderExtender extender = RIFUtil.getBorderExtenderHint(renderHints);
// Get the source image
RenderedImage source = paramBlock.getRenderedSource(0);
// Get the affine transformation
Object arg0 = paramBlock.getObjectParameter(0);
AffineTransform transform = (AffineTransform) arg0;
// Get the interpolation object
Object arg1 = paramBlock.getObjectParameter(1);
Interpolation interp = (Interpolation) arg1;
// Get the background values
double[] backgroundValues = (double[]) paramBlock.getObjectParameter(2);
// Get the affine transform matrix
double tr[];
tr = new double[6];
transform.getMatrix(tr);
// Get the Nodata Range
Range nodata = (Range) paramBlock.getObjectParameter(6);
nodata = RangeFactory.convert(nodata, source.getSampleModel().getDataType());
// Get the boolean useROIAccessor (by default set to false)
boolean useROIAccessor = false;
// SG make sure we use the ROI
Object property = paramBlock.getObjectParameter(3);
ROI roi = null;
if (property instanceof ROI) {
roi = (ROI) property;
PlanarImage temp = PlanarImage.wrapRenderedImage(source);
temp.setProperty("ROI", roi);
source = temp;
// If ROI is present then the ROI Accessor can be set to true.
useROIAccessor = (Boolean) paramBlock.getObjectParameter(4);
}
try {
// check if we can use the native operation instead
Rectangle sourceBounds = new Rectangle(source.getMinX(), source.getMinY(),
source.getWidth(), source.getHeight());
if ((roi == null || (ImageUtilities.isMediaLibAvailable() && roi.contains(sourceBounds))) && (nodata == null)) {
RenderedImage accelerated = new MlibAffineRIF().create(paramBlock, renderHints);
if (accelerated != null) {
return accelerated;
}
}
} catch (Exception e) {
// Eat exception and proceed with pure java approach
}
// Get the boolean setDestinationNoData
boolean setDestinationNoData = (Boolean) paramBlock.getObjectParameter(5);
// Get the image bounds
Rectangle sourceBounds = new Rectangle(source.getMinX(), source.getMinY(),
source.getWidth(), source.getHeight());
// For remember
// tr[0] = scale on the X axis
// tr[1] = shear on the Y axis
// tr[2] = shear on the X axis
// tr[3] = scale on the Y axis
// tr[4] = translate on the X axis
// tr[5] = translate on the Y axis
//
// Check and see if the affine transform is doing a copy.
// If so call the copy operation.This operation can
// be executed only if ROI data are not defined or contain all the
// image or are empty.
//
if ((tr[0] == 1.0) && (tr[3] == 1.0) && (tr[2] == 0.0) && (tr[1] == 0.0) && (tr[4] == 0.0)
&& (tr[5] == 0.0)
&& (roi == null || (roi.getBounds().isEmpty() || roi.contains(sourceBounds)))
&& (nodata == null)) {
// It's a copy
return new CopyOpImage(source, renderHints, layout);
}
//
// Check and see if the affine transform is in fact doing
// a Translate operation. That is a scale by 1 and no rotation.
// In which case call translate. Note that only integer translate
// is applicable. For non-integer translate we'll have to do the
// affine. This operation can be executed only if ROI data are not
// defined or contain all the image or are empty.
// If the hints contain an ImageLayout hint, we can't use
// TranslateIntOpImage since it isn't capable of dealing with that.
if ((tr[0] == 1.0) && (tr[3] == 1.0) && (tr[2] == 0.0) && (tr[1] == 0.0)
&& (Math.abs(tr[4] - (int) tr[4]) < TOLERANCE)
&& (Math.abs(tr[5] - (int) tr[5]) < TOLERANCE) && layout == null
&& (roi == null || (roi.getBounds().isEmpty() || roi.contains(sourceBounds))
&& (nodata == null))) {
// It's a integer translate
return new TranslateIntOpImage(source, renderHints, (int) tr[4], (int) tr[5]);
}
// control if the image is binary
SampleModel sm = source.getSampleModel();
boolean isBinary = (sm instanceof MultiPixelPackedSampleModel)
&& (sm.getSampleSize(0) == 1)
&& (sm.getDataType() == DataBuffer.TYPE_BYTE
|| sm.getDataType() == DataBuffer.TYPE_USHORT || sm.getDataType() == DataBuffer.TYPE_INT);
// Check which kind of interpolation we are using
boolean nearestInterp = interp instanceof InterpolationNearest
|| interp instanceof javax.media.jai.InterpolationNearest;
boolean bilinearInterp = interp instanceof InterpolationBilinear
|| interp instanceof javax.media.jai.InterpolationBilinear;
boolean bicubicInterp = interp instanceof InterpolationBicubic
|| interp instanceof javax.media.jai.InterpolationBicubic
|| interp instanceof javax.media.jai.InterpolationBicubic2;
// Transformation of the interpolators JAI-->JAI-EXT
int dataType = source.getSampleModel().getDataType();
double destinationNoData = (backgroundValues != null && backgroundValues.length > 0) ? backgroundValues[0]
: 0;
if (interp instanceof javax.media.jai.InterpolationNearest) {
interp = new InterpolationNearest(nodata, useROIAccessor, destinationNoData, dataType);
} else if (interp instanceof javax.media.jai.InterpolationBilinear) {
interp = new InterpolationBilinear(interp.getSubsampleBitsH(), nodata, useROIAccessor,
destinationNoData, dataType);
} else if (interp instanceof javax.media.jai.InterpolationBicubic) {
javax.media.jai.InterpolationBicubic bic = (javax.media.jai.InterpolationBicubic) interp;
interp = new InterpolationBicubic(bic.getSubsampleBitsH(), nodata, useROIAccessor,
destinationNoData, dataType, true, bic.getPrecisionBits());
} else if (interp instanceof javax.media.jai.InterpolationBicubic2) {
javax.media.jai.InterpolationBicubic2 bic = (javax.media.jai.InterpolationBicubic2) interp;
interp = new InterpolationBicubic(bic.getSubsampleBitsH(), nodata, useROIAccessor,
destinationNoData, dataType, false, bic.getPrecisionBits());
}
//
// Check and see if the affine transform is in fact doing
// a Scale operation. In which case call Scale which is more
// optimized than Affine.
//
if ((tr[0] > 0.0) && (tr[2] == 0.0) && (tr[1] == 0.0) && (tr[3] > 0.0)) {
// It's a scale
if (nearestInterp && !isBinary) {
return new ScaleNearestOpImage(source, layout, renderHints, extender, interp,
(float) tr[0], (float) tr[3], (float) tr[4], (float) tr[5], useROIAccessor, nodata, backgroundValues);
} else if (nearestInterp && isBinary) {
return new ScaleGeneralOpImage(source, layout, renderHints, extender, interp,
(float) tr[0], (float) tr[3], (float) tr[4], (float) tr[5], useROIAccessor, nodata, backgroundValues);
} else if (bilinearInterp && !isBinary) {
return new ScaleBilinearOpImage(source, layout, renderHints, extender, interp,
(float) tr[0], (float) tr[3], (float) tr[4], (float) tr[5], useROIAccessor, nodata, backgroundValues);
} else if (bilinearInterp && isBinary) {
return new ScaleGeneralOpImage(source, layout, renderHints, extender, interp,
(float) tr[0], (float) tr[3], (float) tr[4], (float) tr[5], useROIAccessor, nodata, backgroundValues);
} else if (bicubicInterp && !isBinary) {
return new ScaleBicubicOpImage(source, layout, renderHints, extender, interp,
(float) tr[0], (float) tr[3], (float) tr[4], (float) tr[5], useROIAccessor, nodata, backgroundValues);
} else if (bicubicInterp && isBinary) {
return new ScaleGeneralOpImage(source, layout, renderHints, extender, interp,
(float) tr[0], (float) tr[3], (float) tr[4], (float) tr[5], useROIAccessor, nodata, backgroundValues);
} else {
return new ScaleGeneralOpImage(source, layout, renderHints, extender, interp,
(float) tr[0], (float) tr[3], (float) tr[4], (float) tr[5], useROIAccessor, nodata, backgroundValues);
}
}
// Have to do Affine
if (nearestInterp && !isBinary) {
return new AffineNearestOpImage(source, extender, renderHints, layout, transform,
interp, backgroundValues, setDestinationNoData, useROIAccessor, nodata);
} else if (nearestInterp && isBinary) {
return new AffineGeneralOpImage(source, extender, renderHints, layout, transform,
interp, useROIAccessor, backgroundValues, setDestinationNoData, nodata);
} else if (bilinearInterp && !isBinary) {
return new AffineBilinearOpImage(source, extender, renderHints, layout, transform,
interp, backgroundValues, setDestinationNoData, useROIAccessor, nodata);
} else if (bilinearInterp && isBinary) {
return new AffineGeneralOpImage(source, extender, renderHints, layout, transform,
interp, useROIAccessor, backgroundValues, setDestinationNoData, nodata);
} else if (bicubicInterp && !isBinary) {
return new AffineBicubicOpImage(source, extender, renderHints, layout, transform,
interp, backgroundValues, setDestinationNoData, useROIAccessor, nodata);
} else if (bicubicInterp && isBinary) {
return new AffineGeneralOpImage(source, extender, renderHints, layout, transform,
interp, useROIAccessor, backgroundValues, setDestinationNoData, nodata);
} else {
return new AffineGeneralOpImage(source, extender, renderHints, layout, transform,
interp, useROIAccessor, backgroundValues, setDestinationNoData, nodata);
}
}
/**
* Creates a new instance of <code>AffineOpImage</code> in the renderable layer. This method satisfies the implementation of CRIF.
*/
public RenderedImage create(RenderContext renderContext, ParameterBlock paramBlock) {
return paramBlock.getRenderedSource(0);
}
/**
* Maps the output RenderContext into the RenderContext for the ith source. This method satisfies the implementation of CRIF.
*
* @param i The index of the source image.
* @param renderContext The renderContext being applied to the operation.
* @param paramBlock The ParameterBlock containing the sources and the translation factors.
* @param image The RenderableImageOp from which this method was called.
*/
public RenderContext mapRenderContext(int i, RenderContext renderContext,
ParameterBlock paramBlock, RenderableImage image) {
Object arg0 = paramBlock.getObjectParameter(0);
AffineTransform affine = (AffineTransform) arg0;
RenderContext RC = (RenderContext) renderContext.clone();
AffineTransform usr2dev = RC.getTransform();
usr2dev.concatenate(affine);
RC.setTransform(usr2dev);
return RC;
}
/**
* Gets the bounding box for the output of <code>AffineOpImage</code>. This method satisfies the implementation of CRIF.
*/
public Rectangle2D getBounds2D(ParameterBlock paramBlock) {
RenderableImage source = paramBlock.getRenderableSource(0);
Object arg0 = paramBlock.getObjectParameter(0);
AffineTransform forward_tr = (AffineTransform) arg0;
// Get the affine transform
double tr[];
tr = new double[6];
forward_tr.getMatrix(tr);
//
// Check and see if the affine transform is doing a copy.
//
if ((tr[0] == 1.0) && (tr[3] == 1.0) && (tr[2] == 0.0) && (tr[1] == 0.0) && (tr[4] == 0.0)
&& (tr[5] == 0.0)) {
return new Rectangle2D.Float(source.getMinX(), source.getMinY(), source.getWidth(),
source.getHeight());
}
//
// Check and see if the affine transform is in fact doing
// a Translate operation.
//
if ((tr[0] == 1.0) && (tr[3] == 1.0) && (tr[2] == 0.0) && (tr[1] == 0.0)
&& (Math.abs(tr[4] - (int) tr[4]) < TOLERANCE)
&& (Math.abs(tr[5] - (int) tr[5]) < TOLERANCE)) {
return new Rectangle2D.Float(source.getMinX() + (float) tr[4], source.getMinY()
+ (float) tr[5], source.getWidth(), source.getHeight());
}
//
// Check and see if the affine transform is in fact doing
// a Scale operation.
//
if ((tr[0] > 0.0) && (tr[2] == 0.0) && (tr[1] == 0.0) && (tr[3] > 0.0)) {
// Get the source dimensions
float x0 = source.getMinX();
float y0 = source.getMinY();
float w = source.getWidth();
float h = source.getHeight();
// Forward map the source using x0, y0, w and h
float d_x0 = x0 * (float) tr[0] + (float) tr[4];
float d_y0 = y0 * (float) tr[3] + (float) tr[5];
float d_w = w * (float) tr[0];
float d_h = h * (float) tr[3];
return new Rectangle2D.Float(d_x0, d_y0, d_w, d_h);
}
// It's an Affine
//
// Get sx0,sy0 coordinates and width & height of the source
//
float sx0 = source.getMinX();
float sy0 = source.getMinY();
float sw = source.getWidth();
float sh = source.getHeight();
//
// The 4 points (clockwise order) are
// (sx0, sy0), (sx0+sw, sy0)
// (sx0, sy0+sh), (sx0+sw, sy0+sh)
//
Point2D[] pts = new Point2D[4];
pts[0] = new Point2D.Float(sx0, sy0);
pts[1] = new Point2D.Float((sx0 + sw), sy0);
pts[2] = new Point2D.Float((sx0 + sw), (sy0 + sh));
pts[3] = new Point2D.Float(sx0, (sy0 + sh));
// Forward map
forward_tr.transform(pts, 0, pts, 0, 4);
float dx0 = Float.MAX_VALUE;
float dy0 = Float.MAX_VALUE;
float dx1 = -Float.MAX_VALUE;
float dy1 = -Float.MAX_VALUE;
for (int i = 0; i < 4; i++) {
float px = (float) pts[i].getX();
float py = (float) pts[i].getY();
dx0 = Math.min(dx0, px);
dy0 = Math.min(dy0, py);
dx1 = Math.max(dx1, px);
dy1 = Math.max(dy1, py);
}
//
// Get the width & height of the resulting bounding box.
// This is set on the layout
//
float lw = dx1 - dx0;
float lh = dy1 - dy0;
return new Rectangle2D.Float(dx0, dy0, lw, lh);
}
}