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      */
RenderableImageOp(ContextualRenderedImageFactory CRIF, ParameterBlock paramBlock)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      */
getSources()86     public Vector<RenderableImage> getSources() {
87         return getRenderableSources();
88     }
89 
getRenderableSources()90     private Vector<RenderableImage> getRenderableSources() {
91         Vector<RenderableImage> sources = null;
92 
93         if (paramBlock.getNumSources() > 0) {
94             sources = new Vector<>();
95             int i = 0;
96             while (i < paramBlock.getNumSources()) {
97                 Object o = paramBlock.getSource(i);
98                 if (o instanceof RenderableImage) {
99                     sources.add((RenderableImage)o);
100                     i++;
101                 } else {
102                     break;
103                 }
104             }
105         }
106         return sources;
107     }
108 
109     /**
110      * Gets a property from the property set of this image.
111      * If the property name is not recognized, java.awt.Image.UndefinedProperty
112      * will be returned.
113      *
114      * @param name the name of the property to get, as a String.
115      * @return a reference to the property Object, or the value
116      *         java.awt.Image.UndefinedProperty.
117      */
getProperty(String name)118     public Object getProperty(String name) {
119         return myCRIF.getProperty(paramBlock, name);
120     }
121 
122     /**
123      * Return a list of names recognized by getProperty.
124      * @return a list of property names.
125      */
getPropertyNames()126     public String[] getPropertyNames() {
127         return myCRIF.getPropertyNames();
128     }
129 
130     /**
131      * Returns true if successive renderings (that is, calls to
132      * createRendering() or createScaledRendering()) with the same arguments
133      * may produce different results.  This method may be used to
134      * determine whether an existing rendering may be cached and
135      * reused.  The CRIF's isDynamic method will be called.
136      * @return {@code true} if successive renderings with the
137      *         same arguments might produce different results;
138      *         {@code false} otherwise.
139      */
isDynamic()140     public boolean isDynamic() {
141         return myCRIF.isDynamic();
142     }
143 
144     /**
145      * Gets the width in user coordinate space.  By convention, the
146      * usual width of a RenderableImage is equal to the image's aspect
147      * ratio (width divided by height).
148      *
149      * @return the width of the image in user coordinates.
150      */
getWidth()151     public float getWidth() {
152         if (boundingBox == null) {
153             boundingBox = myCRIF.getBounds2D(paramBlock);
154         }
155         return (float)boundingBox.getWidth();
156     }
157 
158     /**
159      * Gets the height in user coordinate space.  By convention, the
160      * usual height of a RenderedImage is equal to 1.0F.
161      *
162      * @return the height of the image in user coordinates.
163      */
getHeight()164     public float getHeight() {
165         if (boundingBox == null) {
166             boundingBox = myCRIF.getBounds2D(paramBlock);
167         }
168         return (float)boundingBox.getHeight();
169     }
170 
171     /**
172      * Gets the minimum X coordinate of the rendering-independent image data.
173      */
getMinX()174     public float getMinX() {
175         if (boundingBox == null) {
176             boundingBox = myCRIF.getBounds2D(paramBlock);
177         }
178         return (float)boundingBox.getMinX();
179     }
180 
181     /**
182      * Gets the minimum Y coordinate of the rendering-independent image data.
183      */
getMinY()184     public float getMinY() {
185         if (boundingBox == null) {
186             boundingBox = myCRIF.getBounds2D(paramBlock);
187         }
188         return (float)boundingBox.getMinY();
189     }
190 
191     /**
192      * Change the current ParameterBlock of the operation, allowing
193      * editing of image rendering chains.  The effects of such a
194      * change will be visible when a new rendering is created from
195      * this RenderableImageOp or any dependent RenderableImageOp.
196      *
197      * @param paramBlock the new ParameterBlock.
198      * @return the old ParameterBlock.
199      * @see #getParameterBlock
200      */
setParameterBlock(ParameterBlock paramBlock)201     public ParameterBlock setParameterBlock(ParameterBlock paramBlock) {
202         ParameterBlock oldParamBlock = this.paramBlock;
203         this.paramBlock = (ParameterBlock)paramBlock.clone();
204         return oldParamBlock;
205     }
206 
207     /**
208      * Returns a reference to the current parameter block.
209      * @return the {@code ParameterBlock} of this
210      *         {@code RenderableImageOp}.
211      * @see #setParameterBlock(ParameterBlock)
212      */
getParameterBlock()213     public ParameterBlock getParameterBlock() {
214         return paramBlock;
215     }
216 
217     /**
218      * Creates a RenderedImage instance of this image with width w, and
219      * height h in pixels.  The RenderContext is built automatically
220      * with an appropriate usr2dev transform and an area of interest
221      * of the full image.  All the rendering hints come from hints
222      * passed in.
223      *
224      * <p> If w == 0, it will be taken to equal
225      * Math.round(h*(getWidth()/getHeight())).
226      * Similarly, if h == 0, it will be taken to equal
227      * Math.round(w*(getHeight()/getWidth())).  One of
228      * w or h must be non-zero or else an IllegalArgumentException
229      * will be thrown.
230      *
231      * <p> The created RenderedImage may have a property identified
232      * by the String HINTS_OBSERVED to indicate which RenderingHints
233      * were used to create the image.  In addition any RenderedImages
234      * that are obtained via the getSources() method on the created
235      * RenderedImage may have such a property.
236      *
237      * @param w the width of rendered image in pixels, or 0.
238      * @param h the height of rendered image in pixels, or 0.
239      * @param hints a RenderingHints object containing hints.
240      * @return a RenderedImage containing the rendered data.
241      */
createScaledRendering(int w, int h, RenderingHints hints)242     public RenderedImage createScaledRendering(int w, int h,
243                                                RenderingHints hints) {
244         // DSR -- code to try to get a unit scale
245         double sx = (double)w/getWidth();
246         double sy = (double)h/getHeight();
247         if (Math.abs(sx/sy - 1.0) < 0.01) {
248             sx = sy;
249         }
250         AffineTransform usr2dev = AffineTransform.getScaleInstance(sx, sy);
251         RenderContext newRC = new RenderContext(usr2dev, hints);
252         return createRendering(newRC);
253     }
254 
255     /**
256      * Gets a RenderedImage instance of this image with a default
257      * width and height in pixels.  The RenderContext is built
258      * automatically with an appropriate usr2dev transform and an area
259      * of interest of the full image.  All the rendering hints come
260      * from hints passed in.  Implementors of this interface must be
261      * sure that there is a defined default width and height.
262      *
263      * @return a RenderedImage containing the rendered data.
264      */
createDefaultRendering()265     public RenderedImage createDefaultRendering() {
266         AffineTransform usr2dev = new AffineTransform(); // Identity
267         RenderContext newRC = new RenderContext(usr2dev);
268         return createRendering(newRC);
269     }
270 
271     /**
272      * Creates a RenderedImage which represents this
273      * RenderableImageOp (including its Renderable sources) rendered
274      * according to the given RenderContext.
275      *
276      * <p> This method supports chaining of either Renderable or
277      * RenderedImage operations.  If sources in
278      * the ParameterBlock used to construct the RenderableImageOp are
279      * RenderableImages, then a three step process is followed:
280      *
281      * <ol>
282      * <li> mapRenderContext() is called on the associated CRIF for
283      * each RenderableImage source;
284      * <li> createRendering() is called on each of the RenderableImage sources
285      * using the backwards-mapped RenderContexts obtained in step 1,
286      * resulting in a rendering of each source;
287      * <li> ContextualRenderedImageFactory.create() is called
288      * with a new ParameterBlock containing the parameters of
289      * the RenderableImageOp and the RenderedImages that were created by the
290      * createRendering() calls.
291      * </ol>
292      *
293      * <p> If the elements of the source Vector of
294      * the ParameterBlock used to construct the RenderableImageOp are
295      * instances of RenderedImage, then the CRIF.create() method is
296      * called immediately using the original ParameterBlock.
297      * This provides a basis case for the recursion.
298      *
299      * <p> The created RenderedImage may have a property identified
300      * by the String HINTS_OBSERVED to indicate which RenderingHints
301      * (from the RenderContext) were used to create the image.
302      * In addition any RenderedImages
303      * that are obtained via the getSources() method on the created
304      * RenderedImage may have such a property.
305      *
306      * @param renderContext The RenderContext to use to perform the rendering.
307      * @return a RenderedImage containing the desired output image.
308      */
createRendering(RenderContext renderContext)309     public RenderedImage createRendering(RenderContext renderContext) {
310         RenderedImage image = null;
311         RenderContext rcOut = null;
312 
313         // Clone the original ParameterBlock; if the ParameterBlock
314         // contains RenderableImage sources, they will be replaced by
315         // RenderedImages.
316         ParameterBlock renderedParamBlock = (ParameterBlock)paramBlock.clone();
317         Vector<? extends Object> sources = getRenderableSources();
318 
319         try {
320             // This assumes that if there is no renderable source, that there
321             // is a rendered source in paramBlock
322 
323             if (sources != null) {
324                 Vector<Object> renderedSources = new Vector<>();
325                 for (int i = 0; i < sources.size(); i++) {
326                     rcOut = myCRIF.mapRenderContext(i, renderContext,
327                                                     paramBlock, this);
328                     RenderedImage rdrdImage =
329                         ((RenderableImage)sources.elementAt(i)).createRendering(rcOut);
330                     if (rdrdImage == null) {
331                         return null;
332                     }
333 
334                     // Add this rendered image to the ParameterBlock's
335                     // list of RenderedImages.
336                     renderedSources.addElement(rdrdImage);
337                 }
338 
339                 if (renderedSources.size() > 0) {
340                     renderedParamBlock.setSources(renderedSources);
341                 }
342             }
343 
344             return myCRIF.create(renderContext, renderedParamBlock);
345         } catch (ArrayIndexOutOfBoundsException e) {
346             // This should never happen
347             return null;
348         }
349     }
350 }
351