org.jaitools.jiffle.runtime.AbstractJiffleRuntime Maven / Gradle / Ivy
/*
* Copyright (c) 2011, Michael Bedward. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jaitools.jiffle.runtime;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jaitools.jiffle.Jiffle;
import org.jaitools.jiffle.JiffleException;
/**
* Provides default implementations of {@link JiffleRuntime} methods plus
* some common fields. The fields include those involved in handling image-scope
* variables and script options; an instance of {@link JiffleFunctions}; and an
* integer stack used in evaluating {@code con} statements.
*
* @author Michael Bedward
* @since 0.1
* @version $Id$
*/
public abstract class AbstractJiffleRuntime implements JiffleRuntime {
private static final double EPS = 1.0e-8d;
private enum Dim { XDIM, YDIM };
private Map _imageParams;
/** Processing area bounds in world units. */
private Rectangle2D _worldBounds;
/** Pixel width in world units. */
private double _xres;
/** Pixel height in world units. */
private double _yres;
/** Flags whether bounds and pixel dimensions have been set. */
private boolean _worldSet;
/** Number of pixels calculated from bounds and pixel dimensions. */
private long _numPixels;
private class TransformInfo {
CoordinateTransform transform;
boolean isDefault;
}
/**
* A default transform to apply to all images set without an explicit
* transform.
*/
private CoordinateTransform _defaultTransform = new IdentityCoordinateTransform();
/** World to image coordinate transforms with image name as key. */
private Map _transformLookup;
/**
* Holds information about an image-scope variable.
* This class is only public to work around a problem in the
* Janino compiler involving private nested classes. It is
* not intended for client use.
*/
public class ImageScopeVar {
/** Variable name. */
public String name;
/** Whether a default value was provided in the script init block. */
public boolean hasDefaultValue;
/** Whether a run-time value has been set. */
public boolean isSet;
/** The current value. */
public double value;
/**
* Constructor.
* @param name variable name
* @param hasDefaultValue whether a default value is defined in the script
*/
public ImageScopeVar(String name, boolean hasDefaultValue) {
this.name = name;
this.hasDefaultValue = hasDefaultValue;
}
}
// Used to size / resize the _vars array as required
private static final int VAR_ARRAY_CHUNK = 100;
/** Image-scope variables. */
protected ImageScopeVar[] _vars = new ImageScopeVar[VAR_ARRAY_CHUNK];
/** Whether the image-scope variables have been initialized. */
protected boolean _imageScopeVarsInitialized;
/** The number of image-scope variables defined. */
protected int _numVars;
/** Advertizes the image-scope variable getter syntax to source generators. */
public static final String VAR_STRING = "_vars[_VAR_].value";
/** Whether the outside option is set. */
protected boolean _outsideValueSet;
/**
* The value to return for out-of-bounds image data requests if the
* outside option is set.
*/
protected double _outsideValue;
/**
* A stack of integer values used in the evaluation of if statements.
*/
protected IntegerStack _stk;
/**
* Provides runtime function support.
*/
protected final JiffleFunctions _FN;
/**
* Creates a new instance of this class and initializes its
* {@link JiffleFunctions} and {@link IntegerStack} objects.
*/
public AbstractJiffleRuntime() {
_FN = new JiffleFunctions();
_stk = new IntegerStack();
_transformLookup = new HashMap();
_xres = Double.NaN;
_yres = Double.NaN;
}
/**
* {@inheritDoc}
*/
public void setImageParams(Map imageParams) {
this._imageParams = new HashMap();
for (Object oname : imageParams.keySet()) {
String name = (String) oname;
Jiffle.ImageRole role = (Jiffle.ImageRole) imageParams.get(oname);
this._imageParams.put(name, role);
}
}
/**
* {@inheritDoc}
*/
public String[] getSourceVarNames() {
return doGetImageVarNames(Jiffle.ImageRole.SOURCE);
}
/**
* {@inheritDoc}
*/
public String[] getDestinationVarNames() {
return doGetImageVarNames(Jiffle.ImageRole.DEST);
}
private String[] doGetImageVarNames(Jiffle.ImageRole role) {
List names = new ArrayList();
for (String name : _imageParams.keySet()) {
if (_imageParams.get(name) == role) {
names.add(name);
}
}
return names.toArray(new String[0]);
}
/**
* {@inheritDoc}
*/
public void setWorldByResolution(Rectangle2D bounds, double xres, double yres) {
if (bounds == null || bounds.isEmpty()) {
throw new IllegalArgumentException("bounds cannot be null or empty");
}
if (xres < EPS || yres < EPS) {
throw new IllegalArgumentException("xres and yres but must be greater than 0");
}
doSetWorld(bounds, xres, yres);
}
/**
* {@inheritDoc}
*/
public void setWorldByNumPixels(Rectangle2D bounds, int numX, int numY) {
if (bounds == null || bounds.isEmpty()) {
throw new IllegalArgumentException("bounds cannot be null or empty");
}
if (numX <= 0 || numY <= 0) {
throw new IllegalArgumentException("numX and numY must be greater than 0");
}
doSetWorld(bounds, bounds.getWidth() / numX, bounds.getHeight() / numY);
}
/**
* {@inheritDoc}
*/
public boolean isWorldSet() {
return _worldSet;
}
/**
* {@inheritDoc}
*/
public Double getVar(String varName) {
int index = getVarIndex(varName);
if (index < 0) {
return null;
}
return _vars[index].isSet ? _vars[index].value : null;
}
/**
* {@inheritDoc}
*/
public void setVar(String varName, Double value) throws JiffleRuntimeException {
int index = getVarIndex(varName);
if (index < 0) {
throw new JiffleRuntimeException("Undefined variable: " + varName);
}
setVarValue(index, value);
}
/**
* {@inheritDoc}
*/
public String[] getVarNames() {
String[] names = new String[_numVars];
for (int i = 0; i < _numVars; i++) {
names[i] = _vars[i].name;
}
return names;
}
/**
* {@inheritDoc}
*/
public double getMinX() {
return _worldBounds.getMinX();
}
/**
* {@inheritDoc}
*/
public double getMaxX() {
return _worldBounds.getMaxX();
}
/**
* {@inheritDoc}
*/
public double getMinY() {
return _worldBounds.getMinY();
}
/**
* {@inheritDoc}
*/
public double getMaxY() {
return _worldBounds.getMaxY();
}
/**
* {@inheritDoc}
*/
public double getWidth() {
return _worldBounds.getWidth();
}
/**
* {@inheritDoc}
*/
public double getHeight() {
return _worldBounds.getHeight();
}
/**
* {@inheritDoc}
*/
public double getXRes() {
return _xres;
}
/**
* {@inheritDoc}
*/
public double getYRes() {
return _yres;
}
public long getNumPixels() {
if (!_worldSet) {
throw new IllegalStateException("Processing area has not been set");
}
return _numPixels;
}
/**
* Sets a coordinate transform to use with the image represented by
* {@code imageVarName}.
*
* @param imageVarName variable name
* @param tr the transform or {@code null} for the default transform
*
* @throws WorldNotSetException if world bounds and resolution are not yet set
*/
protected void setTransform(String imageVarName, CoordinateTransform tr)
throws WorldNotSetException {
TransformInfo info = new TransformInfo();
if (tr == null) {
info.transform = _defaultTransform;
info.isDefault = true;
} else {
if (!isWorldSet()) {
throw new WorldNotSetException();
}
info.transform = tr;
info.isDefault = false;
}
_transformLookup.put(imageVarName, info);
}
/**
* {@inheritDoc}
*/
public void setDefaultTransform(CoordinateTransform tr) throws JiffleException {
if (tr != null) {
if (!isWorldSet()) {
throw new JiffleException(
"Setting a default coordinate tranform without having "
+ "first set the world bounds and resolution");
}
} else {
tr = new IdentityCoordinateTransform();
}
_defaultTransform = tr;
for (String name : _transformLookup.keySet()) {
TransformInfo info = _transformLookup.get(name);
if (info.isDefault) {
info.transform = _defaultTransform;
_transformLookup.put(name, info);
}
}
}
/**
* Gets the coordinate transform to use with the image represented by
* {@code imageVarName}.
*
* @param imageVarName variable name
*
* @return the coordinate transform
*/
protected CoordinateTransform getTransform(String imageVarName) {
return _transformLookup.get(imageVarName).transform;
}
/**
* Sets the value of an image-scope variable. If {@code value} is {@code null}
* the variable is set to its default value if one is defined, otherwise an
* exception is thrown.
*
* @param index variable index
* @param value the new value or {@code null} for default value
* @throws JiffleRuntimeException if {@code value} is {@code null} but no default
* value is defined for the variable
*/
protected void setVarValue(int index, Double value) throws JiffleRuntimeException {
if (value == null) {
if (!_vars[index].hasDefaultValue) {
throw new JiffleRuntimeException(
"Value cannot be null for variable with no default: " + _vars[index].name);
}
_imageScopeVarsInitialized = false;
_vars[index].isSet = false;
} else {
_vars[index].value = value;
_vars[index].isSet = true;
}
}
/**
* Gets the index for an image-scope variable by name.
*
* @param varName variable name
* @return the index or -1 if the name is not found
*/
protected int getVarIndex(String varName) {
for (int i = 0; i < _numVars; i++) {
if (_vars[i].name.equals(varName)) {
return i;
}
}
return -1;
}
/**
* Initializes image-scope variables. These are fields in the runtime class.
* They are initialized in a separate method rather than the constructor
* because they may depend on expressions involving values which are not
* known until the processing area is set (e.g. Jiffle's width() function).
*
* @throws JiffleRuntimeException if any variables do not have either a
* default or provided value
*/
protected void initImageScopeVars() {
for (int i = 0; i < _numVars; i++) {
if (!_vars[i].isSet) {
Double value = getDefaultValue(i);
if (value == null) {
throw new JiffleRuntimeException(
"No default value set for " + _vars[i].name);
}
_vars[i].value = value;
_vars[i].isSet = true;
}
}
_imageScopeVarsInitialized = true;
}
/**
* Gets the default value for an image-scope variable. This method is
* overridden as part of the generated run-time class code.
*
* @param index the index of the variable
* @return the default value or {@code null} if one is not defined
*/
protected abstract Double getDefaultValue(int index);
/**
* Initializes runtime class fields related to Jiffle script options.
*/
protected abstract void initOptionVars();
/**
* Registers a variable as having image scope.
*
* @param name variable name
* @param hasDefault whether the variable has a default value
*/
protected void registerVar(String name, boolean hasDefault) {
// check that the variable is not already registered
if (getVarIndex(name) >= 0) {
throw new JiffleRuntimeException("Variable already defined: " + name);
}
_numVars++ ;
ImageScopeVar var = new ImageScopeVar(name, hasDefault);
if (_numVars > _vars.length) {
growVarsArray();
}
_vars[_numVars - 1] = var;
}
private void growVarsArray() {
ImageScopeVar[] temp = _vars;
_vars = new ImageScopeVar[_vars.length + VAR_ARRAY_CHUNK];
System.arraycopy(temp, 0, _vars, 0, temp.length);
}
/**
* Helper for {@link #setWorldByNumPixels(Rectangle2D, int, int)} and
* {@link #setWorldByResolution(Rectangle2D, double, double)} methods.
*
* @param bounds world bounds
* @param xres pixel width
* @param yres pixel height
*/
private void doSetWorld(Rectangle2D bounds, double xres, double yres) {
checkResValue(xres, Dim.XDIM, bounds);
checkResValue(yres, Dim.YDIM, bounds);
_worldBounds = new Rectangle2D.Double(
bounds.getMinX(), bounds.getMinY(),
bounds.getWidth(), bounds.getHeight());
_xres = xres;
_yres = yres;
_worldSet = true;
}
/**
* Helper method for {@link #setWorldByResolution(Rectangle2D, double, double)} to
* check the validity of a pixel dimension.
*
* @param value dimension in world units
* @param dim axis: Dim.XDIM or Dim.YDIM
* @param bounds world area bounds
*/
private void checkResValue(double value, Dim dim, Rectangle2D bounds) {
String name = dim == Dim.XDIM ? "xres" : "yres";
if (Double.isInfinite(value)) {
throw new IllegalArgumentException(name + " cannot be infinite");
}
if (Double.isNaN(value)) {
throw new IllegalArgumentException(name + " cannot be NaN");
}
if (dim == Dim.XDIM && value > bounds.getWidth()) {
throw new IllegalArgumentException(name + "should be less than processing area width");
} else if (dim == Dim.YDIM && value > bounds.getHeight()) {
throw new IllegalArgumentException(name + "should be less than processing area height");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy