1 /*
   2  * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /* ********************************************************************
  27  **********************************************************************
  28  **********************************************************************
  29  *** COPYRIGHT (c) Eastman Kodak Company, 1997                      ***
  30  *** As  an unpublished  work pursuant to Title 17 of the United    ***
  31  *** States Code.  All rights reserved.                             ***
  32  **********************************************************************
  33  **********************************************************************
  34  **********************************************************************/
  35 
  36 package java.awt.image.renderable;
  37 import java.awt.geom.AffineTransform;
  38 import java.awt.geom.Rectangle2D;
  39 import java.awt.image.RenderedImage;
  40 import java.awt.RenderingHints;
  41 import java.util.Hashtable;
  42 import java.util.Vector;
  43 
  44 /**
  45  * This class handles the renderable aspects of an operation with help
  46  * from its associated instance of a ContextualRenderedImageFactory.
  47  */
  48 public class RenderableImageOp implements RenderableImage {
  49 
  50     /** A ParameterBlock containing source and parameters. */
  51     ParameterBlock paramBlock;
  52 
  53     /** The associated ContextualRenderedImageFactory. */
  54     ContextualRenderedImageFactory myCRIF;
  55 
  56     /** The bounding box of the results of this RenderableImageOp. */
  57     Rectangle2D boundingBox;
  58 
  59 
  60     /**
  61      * Constructs a RenderedImageOp given a
  62      * ContextualRenderedImageFactory object, and
  63      * a ParameterBlock containing RenderableImage sources and other
  64      * parameters.  Any RenderedImage sources referenced by the
  65      * ParameterBlock will be ignored.
  66      *
  67      * @param CRIF a ContextualRenderedImageFactory object
  68      * @param paramBlock a ParameterBlock containing this operation's source
  69      *        images and other parameters necessary for the operation
  70      *        to run.
  71      */
  72     public RenderableImageOp(ContextualRenderedImageFactory CRIF,
  73                              ParameterBlock paramBlock) {
  74         this.myCRIF = CRIF;
  75         this.paramBlock = (ParameterBlock) paramBlock.clone();
  76     }
  77 
  78     /**
  79      * Returns a vector of RenderableImages that are the sources of
  80      * image data for this RenderableImage. Note that this method may
  81      * return an empty vector, to indicate that the image has no sources,
  82      * or null, to indicate that no information is available.
  83      *
  84      * @return a (possibly empty) Vector of RenderableImages, or null.
  85      */
  86     @SuppressWarnings("unchecked")
  87     public Vector<RenderableImage> getSources() {
  88         return (Vector<RenderableImage>)(Vector)getRenderableSources();
  89     }
  90 
  91     private Vector<Object> getRenderableSources() {
  92         Vector<Object> sources = null;
  93 
  94         if (paramBlock.getNumSources() > 0) {
  95             sources = new Vector<>();
  96             int i = 0;
  97             while (i < paramBlock.getNumSources()) {
  98                 Object o = paramBlock.getSource(i);
  99                 if (o instanceof RenderableImage) {
 100                     sources.add((RenderableImage)o);
 101                     i++;
 102                 } else {
 103                     break;
 104                 }
 105             }
 106         }
 107         return sources;
 108     }
 109 
 110     /**
 111      * Gets a property from the property set of this image.
 112      * If the property name is not recognized, java.awt.Image.UndefinedProperty
 113      * will be returned.
 114      *
 115      * @param name the name of the property to get, as a String.
 116      * @return a reference to the property Object, or the value
 117      *         java.awt.Image.UndefinedProperty.
 118      */
 119     public Object getProperty(String name) {
 120         return myCRIF.getProperty(paramBlock, name);
 121     }
 122 
 123     /**
 124      * Return a list of names recognized by getProperty.
 125      * @return a list of property names.
 126      */
 127     public String[] getPropertyNames() {
 128         return myCRIF.getPropertyNames();
 129     }
 130 
 131     /**
 132      * Returns true if successive renderings (that is, calls to
 133      * createRendering() or createScaledRendering()) with the same arguments
 134      * may produce different results.  This method may be used to
 135      * determine whether an existing rendering may be cached and
 136      * reused.  The CRIF's isDynamic method will be called.
 137      * @return <code>true</code> if successive renderings with the
 138      *         same arguments might produce different results;
 139      *         <code>false</code> otherwise.
 140      */
 141     public boolean isDynamic() {
 142         return myCRIF.isDynamic();
 143     }
 144 
 145     /**
 146      * Gets the width in user coordinate space.  By convention, the
 147      * usual width of a RenderableImage is equal to the image's aspect
 148      * ratio (width divided by height).
 149      *
 150      * @return the width of the image in user coordinates.
 151      */
 152     public float getWidth() {
 153         if (boundingBox == null) {
 154             boundingBox = myCRIF.getBounds2D(paramBlock);
 155         }
 156         return (float)boundingBox.getWidth();
 157     }
 158 
 159     /**
 160      * Gets the height in user coordinate space.  By convention, the
 161      * usual height of a RenderedImage is equal to 1.0F.
 162      *
 163      * @return the height of the image in user coordinates.
 164      */
 165     public float getHeight() {
 166         if (boundingBox == null) {
 167             boundingBox = myCRIF.getBounds2D(paramBlock);
 168         }
 169         return (float)boundingBox.getHeight();
 170     }
 171 
 172     /**
 173      * Gets the minimum X coordinate of the rendering-independent image data.
 174      */
 175     public float getMinX() {
 176         if (boundingBox == null) {
 177             boundingBox = myCRIF.getBounds2D(paramBlock);
 178         }
 179         return (float)boundingBox.getMinX();
 180     }
 181 
 182     /**
 183      * Gets the minimum Y coordinate of the rendering-independent image data.
 184      */
 185     public float getMinY() {
 186         if (boundingBox == null) {
 187             boundingBox = myCRIF.getBounds2D(paramBlock);
 188         }
 189         return (float)boundingBox.getMinY();
 190     }
 191 
 192     /**
 193      * Change the current ParameterBlock of the operation, allowing
 194      * editing of image rendering chains.  The effects of such a
 195      * change will be visible when a new rendering is created from
 196      * this RenderableImageOp or any dependent RenderableImageOp.
 197      *
 198      * @param paramBlock the new ParameterBlock.
 199      * @return the old ParameterBlock.
 200      * @see #getParameterBlock
 201      */
 202     public ParameterBlock setParameterBlock(ParameterBlock paramBlock) {
 203         ParameterBlock oldParamBlock = this.paramBlock;
 204         this.paramBlock = (ParameterBlock)paramBlock.clone();
 205         return oldParamBlock;
 206     }
 207 
 208     /**
 209      * Returns a reference to the current parameter block.
 210      * @return the <code>ParameterBlock</code> of this
 211      *         <code>RenderableImageOp</code>.
 212      * @see #setParameterBlock(ParameterBlock)
 213      */
 214     public ParameterBlock getParameterBlock() {
 215         return paramBlock;
 216     }
 217 
 218     /**
 219      * Creates a RenderedImage instance of this image with width w, and
 220      * height h in pixels.  The RenderContext is built automatically
 221      * with an appropriate usr2dev transform and an area of interest
 222      * of the full image.  All the rendering hints come from hints
 223      * passed in.
 224      *
 225      * <p> If w == 0, it will be taken to equal
 226      * Math.round(h*(getWidth()/getHeight())).
 227      * Similarly, if h == 0, it will be taken to equal
 228      * Math.round(w*(getHeight()/getWidth())).  One of
 229      * w or h must be non-zero or else an IllegalArgumentException
 230      * will be thrown.
 231      *
 232      * <p> The created RenderedImage may have a property identified
 233      * by the String HINTS_OBSERVED to indicate which RenderingHints
 234      * were used to create the image.  In addition any RenderedImages
 235      * that are obtained via the getSources() method on the created
 236      * RenderedImage may have such a property.
 237      *
 238      * @param w the width of rendered image in pixels, or 0.
 239      * @param h the height of rendered image in pixels, or 0.
 240      * @param hints a RenderingHints object containing hints.
 241      * @return a RenderedImage containing the rendered data.
 242      */
 243     public RenderedImage createScaledRendering(int w, int h,
 244                                                RenderingHints hints) {
 245         // DSR -- code to try to get a unit scale
 246         double sx = (double)w/getWidth();
 247         double sy = (double)h/getHeight();
 248         if (Math.abs(sx/sy - 1.0) < 0.01) {
 249             sx = sy;
 250         }
 251         AffineTransform usr2dev = AffineTransform.getScaleInstance(sx, sy);
 252         RenderContext newRC = new RenderContext(usr2dev, hints);
 253         return createRendering(newRC);
 254     }
 255 
 256     /**
 257      * Gets a RenderedImage instance of this image with a default
 258      * width and height in pixels.  The RenderContext is built
 259      * automatically with an appropriate usr2dev transform and an area
 260      * of interest of the full image.  All the rendering hints come
 261      * from hints passed in.  Implementors of this interface must be
 262      * sure that there is a defined default width and height.
 263      *
 264      * @return a RenderedImage containing the rendered data.
 265      */
 266     public RenderedImage createDefaultRendering() {
 267         AffineTransform usr2dev = new AffineTransform(); // Identity
 268         RenderContext newRC = new RenderContext(usr2dev);
 269         return createRendering(newRC);
 270     }
 271 
 272     /**
 273      * Creates a RenderedImage which represents this
 274      * RenderableImageOp (including its Renderable sources) rendered
 275      * according to the given RenderContext.
 276      *
 277      * <p> This method supports chaining of either Renderable or
 278      * RenderedImage operations.  If sources in
 279      * the ParameterBlock used to construct the RenderableImageOp are
 280      * RenderableImages, then a three step process is followed:
 281      *
 282      * <ol>
 283      * <li> mapRenderContext() is called on the associated CRIF for
 284      * each RenderableImage source;
 285      * <li> createRendering() is called on each of the RenderableImage sources
 286      * using the backwards-mapped RenderContexts obtained in step 1,
 287      * resulting in a rendering of each source;
 288      * <li> ContextualRenderedImageFactory.create() is called
 289      * with a new ParameterBlock containing the parameters of
 290      * the RenderableImageOp and the RenderedImages that were created by the
 291      * createRendering() calls.
 292      * </ol>
 293      *
 294      * <p> If the elements of the source Vector of
 295      * the ParameterBlock used to construct the RenderableImageOp are
 296      * instances of RenderedImage, then the CRIF.create() method is
 297      * called immediately using the original ParameterBlock.
 298      * This provides a basis case for the recursion.
 299      *
 300      * <p> The created RenderedImage may have a property identified
 301      * by the String HINTS_OBSERVED to indicate which RenderingHints
 302      * (from the RenderContext) were used to create the image.
 303      * In addition any RenderedImages
 304      * that are obtained via the getSources() method on the created
 305      * RenderedImage may have such a property.
 306      *
 307      * @param renderContext The RenderContext to use to perform the rendering.
 308      * @return a RenderedImage containing the desired output image.
 309      */
 310     public RenderedImage createRendering(RenderContext renderContext) {
 311         RenderedImage image = null;
 312         RenderContext rcOut = null;
 313 
 314         // Clone the original ParameterBlock; if the ParameterBlock
 315         // contains RenderableImage sources, they will be replaced by
 316         // RenderedImages.
 317         ParameterBlock renderedParamBlock = (ParameterBlock)paramBlock.clone();
 318         Vector<Object> sources = getRenderableSources();
 319 
 320         try {
 321             // This assumes that if there is no renderable source, that there
 322             // is a rendered source in paramBlock
 323 
 324             if (sources != null) {
 325                 Vector<Object> renderedSources = new Vector<>();
 326                 for (int i = 0; i < sources.size(); i++) {
 327                     rcOut = myCRIF.mapRenderContext(i, renderContext,
 328                                                     paramBlock, this);
 329                     RenderedImage rdrdImage =
 330                         ((RenderableImage)sources.elementAt(i)).createRendering(rcOut);
 331                     if (rdrdImage == null) {
 332                         return null;
 333                     }
 334 
 335                     // Add this rendered image to the ParameterBlock's
 336                     // list of RenderedImages.
 337                     renderedSources.addElement(rdrdImage);
 338                 }
 339 
 340                 if (renderedSources.size() > 0) {
 341                     renderedParamBlock.setSources(renderedSources);
 342                 }
 343             }
 344 
 345             return myCRIF.create(renderContext, renderedParamBlock);
 346         } catch (ArrayIndexOutOfBoundsException e) {
 347             // This should never happen
 348             return null;
 349         }
 350     }
 351 }