/* 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.border;
import it.geosolutions.jaiext.range.Range;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import javax.media.jai.BorderExtender;
import javax.media.jai.JAI;
import javax.media.jai.OperationDescriptorImpl;
import javax.media.jai.OperationNode;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.RenderedOp;
import javax.media.jai.registry.RenderedRegistryMode;
/**
* An <code>OperationDescriptor</code> describing the "Border" operation.
*
* <p>
* The Border operation adds a border around a rendered image. The size of the border is specified in pixels by the left, right, top, and bottom
* padding parameters, corresponding to the four sides of the source image. These paddings may not be less than 0.
*
* <p>
* The pixel values of the added border area will be set according to the algorithm of the <code>BorderExtender</code> passed as a parameter. The
* <code>BorderExtender</code>s provide the ability to extend the border by:
* <ul>
* <li>filling it with zeros (<code>BorderExtenderZero</code>);
* <li>filling it with constants (<code>BorderExtenderConstant</code>);
* <li>copying the edge and corner pixels (<code>BorderExtenderCopy</code>);
* <li>reflecting about the edges of the image (<code>BorderExtenderReflect</code>); or,
* <li>"wrapping" the image plane toroidally, that is, joining opposite edges of the image (<code>BorderExtenderWrap</code>).
* </ul>
*
* <p>
* If No Data are present the user can provide a Range of No Data for handling the No Data values and a Destination No Data value for setting the
* output No Data value.
* </p>
*
* <p>
* <table border=1>
* <caption>Resource List</caption>
* <tr>
* <th>Name</th>
* <th>Value</th>
* </tr>
* <tr>
* <td>GlobalName</td>
* <td>Border</td>
* </tr>
* <tr>
* <td>LocalName</td>
* <td>Border</td>
* </tr>
* <tr>
* <td>Vendor</td>
* <td>it.geosolutions.jaiext</td>
* </tr>
* <tr>
* <td>Description</td>
* <td>Operation which adds borders to the input image.</td>
* </tr>
* <tr>
* <td>DocURL</td>
* <td>Not Defined</td>
* </tr>
* <tr>
* <td>Version</td>
* <td>1.0</td>
* </tr>
* <tr>
* <td>arg0Desc</td>
* <td>Image's left padding.</td>
* </tr>
* <tr>
* <td>arg1Desc</td>
* <td>Image's right padding.</td>
* </tr>
* <tr>
* <td>arg2Desc</td>
* <td>Image's top padding.</td>
* </tr>
* <tr>
* <td>arg3Desc</td>
* <td>Image's bottom padding.</td>
* </tr>
* <tr>
* <td>arg4Desc</td>
* <td>Border Extender used.</td>
* </tr>
* <tr>
* <td>arg5Desc</td>
* <td>No Data Range used.</td>
* </tr>
* <tr>
* <td>arg6Desc</td>
* <td>Destination No Data value.</td>
* </tr>
* </table>
* </p>
*
* <p>
* <table border=1>
* <caption>Parameter List</caption>
* <tr>
* <th>Name</th>
* <th>Class Type</th>
* <th>Default Value</th>
* </tr>
* <tr>
* <td>leftPad</td>
* <td>java.lang.Integer</td>
* <td>0</td>
* <tr>
* <td>rightPad</td>
* <td>java.lang.Integer</td>
* <td>0</td>
* <tr>
* <td>topPad</td>
* <td>java.lang.Integer</td>
* <td>0</td>
* <tr>
* <td>bottomPad</td>
* <td>java.lang.Integer</td>
* <td>0</td>
* <tr>
* <td>type</td>
* <td>javax.media.jai.BorderExtender</td>
* <td>javax.media.jai.BorderExtenderZero</td>
* <tr>
* <td>noData</td>
* <td>it.geosolutions.jaiext.range.Range</td>
* <td>null</td>
* <tr>
* <tr>
* <td>destNoData</td>
* <td>java.lang.Double</td>
* <td>0</td>
* </table>
* </p>
*
*/
public class BorderDescriptor extends OperationDescriptorImpl {
/**
* The resource strings that provide the general documentation and specify the parameter list for this operation.
*/
private static final String[][] resources = { { "GlobalName", "Border" },
{ "LocalName", "Border" }, { "Vendor", "it.geosolutions.jaiext" },
{ "Description", "Operation which adds borders to the input image" },
{ "DocURL", "Not Defined" }, { "Version", "1.0" },
{ "arg0Desc", "Image's left padding" }, { "arg1Desc", "Image's right padding" },
{ "arg2Desc", "Image's top padding" }, { "arg3Desc", "Image's bottom padding" },
{ "arg4Desc", "Border Extender used" }, { "arg5Desc", "No Data Range used" },
{ "arg6Desc", "Destination No Data value" } };
/** The parameter name list for this operation. */
private static final String[] paramNames = { "leftPad", "rightPad", "topPad", "bottomPad",
"type", "noData", "destNoData" };
/** The parameter class list for this operation. */
private static final Class[] paramClasses = { java.lang.Integer.class, java.lang.Integer.class,
java.lang.Integer.class, java.lang.Integer.class, javax.media.jai.BorderExtender.class,
it.geosolutions.jaiext.range.Range.class, java.lang.Double.class };
/** The parameter default value list for this operation. */
private static final Object[] paramDefaults = { 0, 0, 0, 0,
BorderExtender.createInstance(BorderExtender.BORDER_ZERO), null, 0d };
/** Constructor. */
public BorderDescriptor() {
super(resources, 1, paramClasses, paramNames, paramDefaults);
}
/**
* Calculates the region over which two distinct renderings of the "Border" operation may be expected to differ.
*
* <p>
* The operation returns a <code>Shape</code> or <code>null</code> in the rendered mode and <code>null</code> in all other modes.
*
* @param modeName The name of the mode.
* @param oldParamBlock The previous sources and parameters.
* @param oldHints The previous hints.
* @param newParamBlock The current sources and parameters.
* @param newHints The current hints.
* @param node The affected node in the processing chain (ignored).
*
* @return The region over which the data of two renderings of this operation may be expected to be invalid or <code>null</code> if there is no
* common region of validity. A non-<code>null</code> empty region indicates that the operation would produce identical data over the
* bounds of the old rendering although perhaps not over the area occupied by the <i>tiles</i> of the old rendering.
*
* @throws IllegalArgumentException if <code>modeName</code> is <code>null</code> or if the operation requires either sources or parameters and
* either <code>oldParamBlock</code> or <code>newParamBlock</code> is <code>null</code>.
* @throws IllegalArgumentException if <code>oldParamBlock</code> or <code>newParamBlock</code> do not contain sufficient sources or parameters
* for the operation in question.
*/
public Object getInvalidRegion(String modeName, ParameterBlock oldParamBlock,
RenderingHints oldHints, ParameterBlock newParamBlock, RenderingHints newHints,
OperationNode node) {
if ((modeName == null)
|| ((getNumSources() > 0 || getNumParameters() > 0) && (oldParamBlock == null || newParamBlock == null))) {
throw new IllegalArgumentException("Some parameters are null");
}
int numSources = getNumSources();
if ((numSources > 0)
&& (oldParamBlock.getNumSources() != numSources || newParamBlock.getNumSources() != numSources)) {
throw new IllegalArgumentException("The number of source images is different");
}
int numParams = getParameterListDescriptor(modeName).getNumParameters();
if ((numParams > 0)
&& (oldParamBlock.getNumParameters() != numParams || newParamBlock
.getNumParameters() != numParams)) {
throw new IllegalArgumentException("The number of input parameters is different");
}
// Return null if the RenderingHints, source, left padding, or
// top padding changed.
if (!modeName.equalsIgnoreCase(RenderedRegistryMode.MODE_NAME)
|| (oldHints == null && newHints != null) || (oldHints != null && newHints == null)
|| (oldHints != null && !oldHints.equals(newHints))
|| !oldParamBlock.getSource(0).equals(newParamBlock.getSource(0))
|| oldParamBlock.getIntParameter(0) != // left pad
newParamBlock.getIntParameter(0) || oldParamBlock.getIntParameter(2) != // top pad
newParamBlock.getIntParameter(2)) {
return null;
}
Shape invalidRegion = null;
if (!oldParamBlock.getObjectParameter(4).equals(newParamBlock.getObjectParameter(4))) {
// BorderExtender changed.
// Get source and the left and top padding.
RenderedImage src = oldParamBlock.getRenderedSource(0);
int leftPad = oldParamBlock.getIntParameter(0);
int topPad = oldParamBlock.getIntParameter(2);
// Get source bounds.
Rectangle srcBounds = new Rectangle(src.getMinX(), src.getMinY(), src.getWidth(),
src.getHeight());
// Get destination bounds.
Rectangle dstBounds = new Rectangle(srcBounds.x - leftPad, srcBounds.y - topPad,
srcBounds.width + leftPad + oldParamBlock.getIntParameter(1), srcBounds.height
+ topPad + oldParamBlock.getIntParameter(3));
// Determine invalid area by subtracting source bounds.
Area invalidArea = new Area(dstBounds);
invalidArea.subtract(new Area(srcBounds));
invalidRegion = invalidArea;
} else if ((newParamBlock.getIntParameter(1) < // new R < old R
oldParamBlock.getIntParameter(1) && newParamBlock.getIntParameter(3) <= // new B <= old B
oldParamBlock.getIntParameter(3))
|| (newParamBlock.getIntParameter(3) < // new B < old B
oldParamBlock.getIntParameter(3) && newParamBlock.getIntParameter(1) <= // new R <= old R
oldParamBlock.getIntParameter(1))) {
// One or both right and bottom padding decreased.
// Get source and the left and top padding.
RenderedImage src = oldParamBlock.getRenderedSource(0);
int leftPad = oldParamBlock.getIntParameter(0);
int topPad = oldParamBlock.getIntParameter(2);
// Get source bounds.
Rectangle srcBounds = new Rectangle(src.getMinX(), src.getMinY(), src.getWidth(),
src.getHeight());
// Get old destination bounds.
Rectangle oldBounds = new Rectangle(srcBounds.x - leftPad, srcBounds.y - topPad,
srcBounds.width + leftPad + oldParamBlock.getIntParameter(1), srcBounds.height
+ topPad + oldParamBlock.getIntParameter(3));
// Get new destination bounds.
Rectangle newBounds = new Rectangle(srcBounds.x - leftPad, srcBounds.y - topPad,
srcBounds.width + leftPad + newParamBlock.getIntParameter(1), srcBounds.height
+ topPad + newParamBlock.getIntParameter(3));
// Determine invalid area by subtracting new from old bounds.
Area invalidArea = new Area(oldBounds);
invalidArea.subtract(new Area(newBounds));
invalidRegion = invalidArea;
} else {
// Either nothing changed or one or both of the right and bottom
// padding was increased.
invalidRegion = new Rectangle();
}
return invalidRegion;
}
/**
* Adds a border around an image.
*
* <p>
* Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes
* {@link JAI#create(String,ParameterBlock,RenderingHints)}.
*
*
* @param source0 <code>RenderedImage</code> source 0.
* @param leftPad The image's left padding. May be <code>null</code>.
* @param rightPad The image's right padding. May be <code>null</code>.
* @param topPad The image's top padding. May be <code>null</code>.
* @param bottomPad The image's bottom padding. May be <code>null</code>.
* @param type The border type. May be <code>null</code>.
* @param noData No Data Range used. May be <code>null</code>.
* @param destinationNoData Value for the output No Data.
* @param hints The <code>RenderingHints</code> to use. May be <code>null</code>.
* @return The <code>RenderedOp</code> destination.
* @throws IllegalArgumentException if <code>source0</code> is <code>null</code>.
*/
public static RenderedOp create(RenderedImage source0, int leftPad, int rightPad, int topPad,
int bottomPad, BorderExtender type, Range noData, double destinationNoData,
RenderingHints hints) {
ParameterBlockJAI pb = new ParameterBlockJAI("Border", RenderedRegistryMode.MODE_NAME);
// Setting of the source
pb.setSource("source0", source0);
// Setting of the parameters
pb.setParameter("leftPad", leftPad);
pb.setParameter("rightPad", rightPad);
pb.setParameter("topPad", topPad);
pb.setParameter("bottomPad", bottomPad);
pb.setParameter("type", type);
pb.setParameter("noData", noData);
pb.setParameter("destNoData", destinationNoData);
return JAI.create("Border", pb, hints);
}
}