All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jaitools.jiffle.JiffleBuilder 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;

import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRenderedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Map;

import org.jaitools.CollectionFactory;
import org.jaitools.imageutils.ImageUtils;
import org.jaitools.jiffle.runtime.CoordinateTransform;
import org.jaitools.jiffle.runtime.IdentityCoordinateTransform;
import org.jaitools.jiffle.runtime.JiffleDirectRuntime;

/**
 * A builder class which makes it easier to compile and run basic Jiffle scripts.
 * 

* When working with Jiffle objects directly you end up writing a certain * amount of boiler-plate code for image parameters etc. JiffleBuilder offers * concise, chained methods to help you get the job done with fewer keystrokes. *


 * // A script to sum values from two source images
 * String sumScript = "dest = foo + bar;" ;
 *
 * RenderedImage fooImg = ...
 * RenderedImage barImg = ...
 *
 * JiffleBuilder jb = new JiffleBuilder();
 * jb.script(sumScript).source("foo", fooImg).script("bar", barImg);
 *
 * // We can get the builder to create the destination image for us
 * jb.dest("dest", fooImg.getWidth(), fooImg.getHeight());
 *
 * // Run the script
 * jb.getRuntime().run();
 *
 * // Since we asked the builder to create the destination image we
 * // now need to get a reference to it
 * RenderedImage destImg = jb.getImage("dest");
 * 
* When a script does not use any source images, {@code JiffleBuilder} makes * for very concise code: *

 * String script = "waves = sin( 4 * M_PI * x() / width() );" ;
 * JiffleBuilder jb = new JiffleBuilder();
 * RenderedImage wavesImg = jb.script(script).dest("waves", 500, 200).run().getImage("waves");
 * 
* {@code JiffleBuilder} also provides support for setting world units and * coordinate transforms. * * @author Michael Bedward * @since 0.1 * @version $Id$ */ public class JiffleBuilder { private static final double EPS = 1.0e-8; private static class WorldInfo { Rectangle2D bounds; double xres; double yres; } private WorldInfo worldInfo; /* * We use this class, rather than a private collection of WeakReferences, * to ensure that both weak and strong references are visible to the * client. Otherwise, the weak references get garbage collected too soon. */ private static class ImageRef { Object ref; boolean weak; ImageRef(RenderedImage image, boolean weak) { if (weak) { ref = new WeakReference(image); } else { ref = image; } this.weak = weak; } RenderedImage get() { if (weak) { RenderedImage image = ((WeakReference) ref).get(); return image; } else { return (RenderedImage) ref; } } } private String script; private final Map imageParams; private final Map images; private CoordinateTransform _defaultTransform; private final Map transforms; /** * Creates a new JiffleBuilder instance. */ public JiffleBuilder() { imageParams = CollectionFactory.orderedMap(); images = CollectionFactory.orderedMap(); transforms = CollectionFactory.orderedMap(); } /** * Clears all attributes in this builder. If destination images * were created using the {@code dest} methods with image bounds * arguments they will also be freed. */ public void clear() { worldInfo = null; _defaultTransform = null; script = null; imageParams.clear(); images.clear(); transforms.clear(); } /** * Sets the bound and resolution of the processing area. If the client * does not explicitly set the processing area the default is used * (first destination or source image bounds and resolution). * * @param worldBounds bounds in world units * @param xres pixel width in world units * @param yres pixel height in world units * * @return the instance of this class to allow method chaining */ public JiffleBuilder worldAndRes(Rectangle2D worldBounds, double xres, double yres) { if (worldBounds == null || worldBounds.isEmpty()) { throw new IllegalArgumentException("bounds must not be null or empty"); } if (xres <= EPS) { throw new IllegalArgumentException("xres must be greater than zero"); } if (xres <= EPS) { throw new IllegalArgumentException("xres must be greater than zero"); } return doSetWorld(worldBounds, xres, yres); } /** * Sets the bound and resolution of the processing area. If the client * does not explicitly set the processing area the default is used * (first destination or source image bounds and resolution). * * @param worldBounds bounds in world units * @param numX number of pixels in the X direction * @param numY number of pixels in the Y direction * * @return the instance of this class to allow method chaining */ public JiffleBuilder worldAndNumPixels(Rectangle2D worldBounds, int numX, int numY) { if (worldBounds == null || worldBounds.isEmpty()) { throw new IllegalArgumentException("bounds must not be null or empty"); } if (numX <= 0) { throw new IllegalArgumentException("numX must be greater than zero"); } if (numY <= 0) { throw new IllegalArgumentException("numY must be greater than zero"); } double xres = worldBounds.getWidth() / numX; double yres = worldBounds.getHeight() / numY; return doSetWorld(worldBounds, xres, yres); } private JiffleBuilder doSetWorld(Rectangle2D worldBounds, double xres, double yres) { worldInfo = new WorldInfo(); worldInfo.bounds = worldBounds; worldInfo.xres = xres; worldInfo.yres = yres; return this; } /** * Sets the script to be compiled. * * @param script the script * * @return the instance of this class to allow method chaining */ public JiffleBuilder script(String script) { this.script = script; return this; } /** * Reads the script from {@code scriptFile}. * * @param scriptFile file containing the script * * @return the instance of this class to allow method chaining * @throws JiffleException if there were problems reading the file */ public JiffleBuilder script(File scriptFile) throws JiffleException { script = readScriptFile(scriptFile); return this; } /** * Associates a variable name with a source image. The default coordinate * system will be used for this image. * The image will be stored by the builder as a weak reference. * * @param varName variable name * @param sourceImage the source image * @return the instance of this class to allow method chaining */ public JiffleBuilder source(String varName, RenderedImage sourceImage) { return source(varName, sourceImage, null); } /** * Associates a variable name with a source image and coordinate transform. * The image will be stored by the builder as a weak reference. * * @param varName variable name * @param sourceImage the source image * @param transform the transform to convert world coordinates to this image's * pixel coordinates * * @return the instance of this class to allow method chaining */ public JiffleBuilder source(String varName, RenderedImage sourceImage, CoordinateTransform transform) { imageParams.put(varName, Jiffle.ImageRole.SOURCE); // store as weak reference images.put(varName, new ImageRef(sourceImage, true)); transforms.put(varName, transform); return this; } /** * Creates a new destination image and associates it with a variable name * in the script. *

* Note: a {@code JiffleBuilder} maintains only {@code WeakReferences} * to all source images and any destination images passed to it via * the {@link #dest(String, WritableRenderedImage)} method. However, * a strong reference is stored to any destination images created with this * method. This can be freed later by calling {@link #clear()} or * {@link #removeImage(String varName)}. * * @param varName variable name * @param destBounds the bounds of the new destination image * * @return the instance of this class to allow method chaining */ public JiffleBuilder dest(String varName, Rectangle destBounds) { if (destBounds == null || destBounds.isEmpty()) { throw new IllegalArgumentException("destBounds argument cannot be null or empty"); } return dest(varName, destBounds.x, destBounds.y, destBounds.width, destBounds.height); } /** * Creates a new destination image and associates it with a variable name * in the script. *

* Note: a {@code JiffleBuilder} maintains only {@code WeakReferences} * to all source images and any destination images passed to it via * the {@link #dest(String, WritableRenderedImage)} method. However, * a strong reference is stored to any destination images created with this * method. This can be freed later by calling {@link #clear()} or * {@link #removeImage(String varName)}. * * @param varName variable name * @param destBounds the bounds of the new destination image * @param transform the transform to convert world coordinates to this image's * pixel coordinates * * @return the instance of this class to allow method chaining */ public JiffleBuilder dest(String varName, Rectangle destBounds, CoordinateTransform transform) { if (destBounds == null || destBounds.isEmpty()) { throw new IllegalArgumentException("destBounds argument cannot be null or empty"); } return dest(varName, destBounds.x, destBounds.y, destBounds.width, destBounds.height, transform); } /** * Creates a new destination image and associates it with a variable name * in the script. The minimum pixel X and Y ordinates of the destination * image will be 0. *

* Note: a {@code JiffleBuilder} maintains only {@code WeakReferences} * to all source images and any destination images passed to it via * the {@link #dest(String, WritableRenderedImage)} method. However, * a strong reference is stored to any destination images created with this * method. This can be freed later by calling {@link #clear()} or * {@link #removeImage(String varName)}. * * @param varName variable name * @param width image width (pixels) * @param height image height (pixels) * * @return the instance of this class to allow method chaining */ public JiffleBuilder dest(String varName, int width, int height) { return dest(varName, 0, 0, width, height); } /** * Creates a new destination image and associates it with a variable name * in the script. The minimum pixel X and Y ordinates of the destination * image will be 0. *

* Note: a {@code JiffleBuilder} maintains only {@code WeakReferences} * to all source images and any destination images passed to it via * the {@link #dest(String, WritableRenderedImage)} method. However, * a strong reference is stored to any destination images created with this * method. This can be freed later by calling {@link #clear()} or * {@link #removeImage(String varName)}. * * @param varName variable name * @param width image width (pixels) * @param height image height (pixels) * @param transform the transform to convert world coordinates to this image's * pixel coordinates * * @return the instance of this class to allow method chaining */ public JiffleBuilder dest(String varName, int width, int height, CoordinateTransform transform) { return dest(varName, 0, 0, width, height, transform); } /** * Creates a new destination image and associates it with a variable name * in the script. *

* Note: a {@code JiffleBuilder} maintains only {@code WeakReferences} * to all source images and any destination images passed to it via * the {@link #dest(String, WritableRenderedImage)} method. However, * a strong reference is stored to any destination images created with this * method. This can be freed later by calling {@link #clear()} or * {@link #removeImage(String varName)}. * * @param varName variable name * @param minx minimum pixel X ordinate * @param miny minimum pixel Y ordinate * @param width image width (pixels) * @param height image height (pixels) * * @return the instance of this class to allow method chaining */ public JiffleBuilder dest(String varName, int minx, int miny, int width, int height) { return dest(varName, minx, miny, width, height, null); } /** * Creates a new destination image and associates it with a variable name * in the script. *

* Note: a {@code JiffleBuilder} maintains only {@code WeakReferences} * to all source images and any destination images passed to it via * the {@link #dest(String, WritableRenderedImage)} method. However, * a strong reference is stored to any destination images created with this * method. This can be freed later by calling {@link #clear()} or * {@link #removeImage(String varName)}. * * @param varName variable name * @param minx minimum pixel X ordinate * @param miny minimum pixel Y ordinate * @param width image width (pixels) * @param height image height (pixels) * @param transform the transform to convert world coordinates to this image's * pixel coordinates * * @return the instance of this class to allow method chaining */ public JiffleBuilder dest(String varName, int minx, int miny, int width, int height, CoordinateTransform transform) { WritableRenderedImage image = ImageUtils.createConstantImage(minx, miny, width, height, 0d); imageParams.put(varName, Jiffle.ImageRole.DEST); // store as strong reference images.put(varName, new ImageRef(image, false)); transforms.put(varName, transform); return this; } /** * Sets a destination image associated with a variable name in the script. *

* See {@link #dest(String, WritableRenderedImage, CoordinateTransform)} * for more details about this method. * * @param varName variable name * @param destImage the destination image * * @return the instance of this class to allow method chaining */ public JiffleBuilder dest(String varName, WritableRenderedImage destImage) { return dest(varName, destImage, null); } /** * Sets a destination image associated with a variable name in the script. *

* Note: The builder will only hold a Weak reference to {@code destImg} so * it's not a good idea to create an image on the fly when calling this * method... *


     * // Creating image on the fly
     * builder.dest("foo", ImageUtils.createConstantImage(width, height, 0d), transform);
     *
     * // Later - oops, null is returned here
     * RenderedImage img = builder.getImage("foo");
     * 
* To avoid this problem, create your image locally... *

     * WritableRenderedImage img = ImageUtils.createConstantImage(width, height, 0d);
     * builder.dest("foo", img, transform);
     * 
* Or use on of the {@code dest} methods with image bounds arguments to * create it for you *

     * builder.dest("foo", width, height, transform);
     *
     * // Now, we can retrieve the image successfully
     * RenderedImage img = builder.getImage("foo");
     * 
* * @param varName variable name * @param destImage the destination image * @param transform the transform to convert world coordinates to this image's * pixel coordinates * * @return the instance of this class to allow method chaining */ public JiffleBuilder dest(String varName, WritableRenderedImage destImage, CoordinateTransform transform) { imageParams.put(varName, Jiffle.ImageRole.DEST); // store as weak reference images.put(varName, new ImageRef(destImage, true)); transforms.put(varName, transform); return this; } /** * Sets a default {@code CoordinateTransform} instance to use with all * images that are passed to the builder without an explicit transform * of their own. If {@code transform} is {@code null}, the system default * transform will be used for any such images. * * @param transform a transform to use as the default; or {@code null} for * the system default transform * * @return the instance of this class to allow method chaining * * @see org.jaitools.jiffle.runtime.JiffleRuntime#setDefaultTransform(CoordinateTransform) */ public JiffleBuilder defaultTransform(CoordinateTransform transform) { _defaultTransform = transform == null ? new IdentityCoordinateTransform() : transform; return this; } /** * Runs the script. Equivalent to calling * {@code builder.getRuntime().evaluateAll(null)}. * * @return the instance of this class to allow method chaining * * @throws JiffleException if the script has not been set yet or if * compilation errors occur */ public JiffleBuilder run() throws JiffleException { getRuntime().evaluateAll(null); return this; } /** * Creates a runtime object for the currently set script and images. * * @return an instance of {@link JiffleDirectRuntime} * * @throws JiffleException if the script has not been set yet or if * compilation errors occur */ public JiffleDirectRuntime getRuntime() throws JiffleException { if (script == null) { throw new IllegalStateException("Jiffle script has not been set yet"); } Jiffle jiffle = new Jiffle(script, imageParams); JiffleDirectRuntime runtime = jiffle.getRuntimeInstance(); runtime.setDefaultTransform(_defaultTransform); if (worldInfo != null) { runtime.setWorldByResolution(worldInfo.bounds, worldInfo.xres, worldInfo.yres); } for (String var : images.keySet()) { RenderedImage img = images.get(var).get(); if (img == null) { throw new JiffleException( "Image for variable " + var + " has been garbage collected"); } CoordinateTransform transform = transforms.get(var); switch (imageParams.get(var)) { case SOURCE: runtime.setSourceImage(var, img, transform); break; case DEST: runtime.setDestinationImage(var, (WritableRenderedImage)img, transform); break; } } return runtime; } /** * Gets the Java run-time class code generated from the compiled script. * * @return the run-time source code * * @throws JiffleException if the script has not been set yet or if * compilation errors occur */ public String getRuntimeSource() throws JiffleException { if (script == null) { throw new IllegalStateException("Jiffle script has not been set yet"); } Jiffle jiffle = new Jiffle(script, imageParams); return jiffle.getRuntimeSource(Jiffle.RuntimeModel.DIRECT, true); } /** * Get an image associated with a script variable name. The image must * have been previously suppolied to the builder using the (@code source} * method or one of the {@code dest} methods. *

* In the case of a destination image the object returned can be cast * to {@link WritableRenderedImage}. * * @param varName variable name * * @return the associated image or {@code null} if the variable name is * not recognized or the image has since been garbage collected */ public RenderedImage getImage(String varName) { ImageRef ref = images.get(varName); if (ref != null) { return ref.get(); } return null; } /** * Removes an image associated with a script variable name. The image should * have been previously suppolied to the builder using the (@code source} * method or one of the {@code dest} methods. *

* In the case of a destination image the object returned can be cast * to {@link WritableRenderedImage}. *

* Note: Thie method also removes any {@code CoordinateTransform} * associated with the image. * * @param varName variable name * * @return the associated image or {@code null} if the variable name is * not recognized or the image has since been garbage collected */ public RenderedImage removeImage(String varName) { ImageRef ref = images.remove(varName); transforms.remove(varName); if (ref != null) { return ref.get(); } return null; } private String readScriptFile(File scriptFile) throws JiffleException { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(scriptFile)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { line = line.trim(); if (line.length() > 0) { sb.append(line); sb.append('\n'); // put the newline back on for the parser } } return sb.toString(); } catch (IOException ex) { throw new JiffleException("Could not read the script file", ex); } finally { if (reader != null) { try { reader.close(); } catch (IOException ignored) { } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy