@(#)EditRender.txt 1.7 Proposal: Support for Editing Rendered Op Chains ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Brian Burkhalter Created: 1 December 1999 Revised: 12/18/00 CHANGES DURING COMMUNITY REVIEW ------------------------------- . Clarified definition of an "invalid region" via modifying the specification of the RenderingChangeEvent constructor. . Modified description of value returned by OperationDescriptor[Impl].getInvalidRegion() per the clarified definition. . Added Raster[] getTiles(RenderedImage owner, Point[] tileIndices) to TileCache. . Defined "InvalidRegion" events. . Specified that a RenderedOp is automatically a sink of PlanarImage and CollectionImage sources. . Added notion that a RenderedOp responds also to CollectionChangeEvents from CollectionOp sources and to "InvalidRegion" events from RenderedImage sources. . Remove section describing (and therefore the concept of) instance caching. . Clarified portions of the section on rendering regeneration by a RenderedOp in response to a RenderingChangeEvent. PROPOSAL -------- 1. Motivation In the JAI 1.1 Expert Group meetings there was evinced a strong interest on the part of several members to have the ability to edit nodes of a rendered chain even after the node has been rendered ("frozen"). The members understood that the original intent of the JAI design was that this capability was to be addressed via the pathway of renderable chains. Despite the fact that problems in the implementation of renderable chains were to be corrected they would still like to have the ability to edit rendered chains. The following proposal addresses this request. Note that implementation of this proposal would in effect eliminate the concept of "frozen" nodes in rendered chains at least in the sense of the original intent. In the context of the suggested modification a node which had previously been considered "frozen" would now be able to be "thawed" but in such an eventuality would be required to fire an event to all listeners interested in the attribute which has changed. 1.1 "Frozen" Chains Currently a node in a rendered chain is "frozen" when any RenderedImage method or any of a certain few number of other methods are invoked on the chain. This is equivalent to generating a rendering of the node. 2. Proposal 2.1 API Changes 2.1.1 RenderingChangeEvent Create a new class javax.media.jai.RenderingChangeEvent defined as follows: import java.awt.Shape; /** * Class representing the event that occurs when a RenderedOp * node is re-rendered. */ public class RenderingChangeEvent extends PropertyChangeEventJAI { private Shape invalidRegion; /** * Constructs a RenderingChangeEvent. The inherited * getSource() method of the event would return the * RenderedOp source; the inherited methods * getOldValue() and getNewValue() * would return the old and new renderings, respectively. If * either the new rendering or the invalid region is null, the * data of the node's rendering need to be re-requested. * *

The invalid region should be null if there is no * area of the extant rendering which remains valid. If the invalid * region is empty, this serves to indicate that all pixels within the * bounds of the old rendering remain valid. Pixels outside the image * bounds proper but within the bounds of all tiles of the image are * not guaranteed to be valid of the invalid region is empty. */ public RenderingChangeEvent(RenderedOp source, PlanarImage oldRendering, PlanarImage newRendering, Shape invalidRegion) {} /** * Returns an object which represents the region over which the * the two renderings should differ. * * @return The region over which the two renderings differ or * null to indicate that they differ everywhere. */ public Shape getInvalidRegion() {} } PropertyChangeEventJAI is defined in the "Improved Property Management" proposal. The inherited method getSource() would return the RenderedOp source as an Object; the inherited methods getOldValue() and getNewValue() would return the old and new renderings, respectively, as Objects. The getPropertyName() method would return "Rendering". If either the new rendering or the invalid region is null, then the entire rendering must be regenerated. 2.1.2 OperationDescriptor 2.1.2.1 OperationDescriptor Interface Add a method specification: /** * Calculates the region over which two distinct renderings * of an operation may be expected to differ. * *

The class of the returned object will vary as a function of * the nature of the operation. For rendered and renderable two- * dimensional images this should be an instance of a class which * implements java.awt.Shape. * * @return The region over which the data of two renderings of this * operation may be expected to be invalid or null * if there is no common region of validity. If an empty * java.awt.Shape is returned, this indicates * that all pixels within the bounds of the old rendering * remain valid. * @throws IllegalArgumentException if registryModeName * is null or if the operation requires either * sources or parameters and either oldParamBlock * or newParamBlock is null. * @throws IllegalArgumentException if oldParamBlock or * newParamBlock do not contain sufficient sources * or parameters for the operation in question. */ Object getInvalidRegion(String registryModeName, ParameterBlock oldParamBlock, RenderingHints oldHints, ParameterBlock newParamBlock, RenderingHints newHints); 2.1.2.2 OperationDescriptorImpl Class Add a method implementation: /** * Calculates the region over which two distinct renderings * of an operation may be expected to differ. * *

The class of the returned object will vary as a function of * the nature of the operation. For rendered and renderable two- * dimensional images this will be an instance of a class which * implements java.awt.Shape. * * @return null to indicate that there is no * common region of validity. * @throws IllegalArgumentException if modeName * is null or if the operation requires either * sources or parameters and either oldParamBlock * or newParamBlock is null. * @throws IllegalArgumentException if oldParamBlock or * newParamBlock do not contain sufficient sources * or parameters for the operation in question. * * @since Java Advanced Imaging 1.1 */ public Object getInvalidRegion(String modeName, ParameterBlock oldParamBlock, RenderingHints oldHints, ParameterBlock newParamBlock, RenderingHints newHints) {} 2.1.3 RenderedOp 2.1.3.1 Make RenderedOp a PropertyChangeListener import java.beans.PropertyChangeListener; /** * Implementation of PropertyChangeListener. * *

When invoked with an event which is an instance of * RenderingChangeEvent the node will respond by * re-rendering itself while retaining any tiles possible. */ public void propertyChange(PropertyChangeEvent evt) {} 2.1.3.2 Make RenderedOp a PropertyChangeEvent source: Note: These methods are in fact added to PlanarImage, the superclass of RenderedOp. /** * Add a PropertyChangeListener to the listener list. The * listener is registered for all properties. */ public void addPropertyChangeListener(PropertyChangeListener listener) {} /** * Add a PropertyChangeListener for a specific property. The * listener will be invoked only when a call on * firePropertyChange names that specific property. The case of * the name is ignored. */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {} /** * Remove a PropertyChangeListener from the listener list. This * removes a PropertyChangeListener that was registered for all * properties. */ public void removePropertyChangeListener(PropertyChangeListener listener){} /** * Remove a PropertyChangeListener for a specific property. The case * of the name is ignored. */ public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener){} 2.1.3.3 Add some accessors and mutators: /** * Sets all the parameters of this node. * This is a convenience method that invokes * setParameterBlock() and so adheres to the same event * firing behavior. */ public synchronized void setParameters(Vector parameters) {} /** * Sets a hint in the RenderingHints of this node. This * is a convenience method which calls setRenderingHints() * and so adheres to the same event firing behavior. * * @throws NullPointerException * if the key or value is null. * @throws IllegalArgumentException * value is not appropriate for the specified key. */ public synchronized void setRenderingHint(RenderingHints.Key key, Object value) {} /** * Gets a hint from the RenderingHints of this node. * * @return the value associated with the specified key or * null if the key is not mapped to any value. */ public synchronized Object getRenderingHint(RenderingHints.Key key) {} 2.1.3.4 Add Object sink accessor and mutator: Note: These methods are in fact added to PlanarImage, the superclass of RenderedOp. The previously existing methods which accept a PlanarImage are deprecated. /** * Adds an Object sink to the list of sinks. * * @return true if the element was added, * false otherwise. * * @throws IllegalArgumentException if * sink is null. */ protected synchronized boolean addSink(Object sink) {} /** * Removes an Object sink from the list of sinks. * * @return true if the element was present, * false otherwise. * * @throws IllegalArgumentException if * sink is null. */ protected synchronized boolean removeSink(Object sink) {} 2.1.3.5 Add resetProperties() /** * Resets the PropertySource. If the parameter is * true then the property environment is completely * reset; if false then only cached properties are * cleared, i.e., those which were derived from the property * environment and are now stored in the local cache. */ protected synchronized void resetProperties(boolean resetPropertySource) {} 2.1.4 TileCache Add two method specifications: /** * Retrieves an array of all tiles in the cache which are owned by the * specified image. * * @param owner The RenderedImage to which the tiles belong. * @return An array of all tiles owned by the specified image or * null if there are none currently in the cache. * @since 1.1 */ Raster[] getTiles(RenderedImage owner); /** * Returns an array of tile Rasters from the cache. * Any or all of the elements of the returned array may be * null if the corresponding tile is not in the cache. * The length of the returned array must be the same as that of the * parameter array and the ith Raster in the * returned array must correspond to the ith tile index in * the parameter array. * * @param owner The RenderedImage that the tile belongs to. * @param tileIndices An array of Points containing the * tileX and tileY indices for each tile. * @since 1.1 */ Raster[] getTiles(RenderedImage owner, Point[] tileIndices); This method will also of course be added to the Sun TileCache implementation. This method allows a RenderedOp to re-use tiles of a previous rendering when it is re-rendered assuming that there is a valid region over which the old and new renderings are identical. For example: Shape invalidRegion = operationDescriptor.getInvalidRegion(/* parameters omitted */); // If validRegion non-empty, re-render node (omitted). if(invalidRegion != null && !invalidRegion.isEmpty()) { TileCache oldCache = oldRendering.getTileCache(); TileCache newCache = newRendering.getTileCache(); Raster[] tiles = oldCache.getTiles(oldRendering); for(int i = 0; i < tiles.length; i++) { Raster tile = tiles[i]; Rectangle bounds = tile.getBounds(); if(!invalidRegion.intersects(bounds)) { newCache.add(newRendering, newRendering.XToTileX(bounds.x), newRendering.YToTileY(bounds.y), tile); } } 2.2 Behavioral Changes to RenderedOp 2.2.1 ParameterBlock Caching Modify the RenderedOp constructors and setParameterBlock() method to create a shallow clone of the ParameterBlock rather than saving it by reference. This will prevent modifications to the ParameterBlock from not being detected by the RenderedOp. The setSources() and setParameters() methods should create a shallow clone of their respective arguments before setting them on the parameter block. 2.2.2 RenderingHints Caching Modify the RenderedOp constructors and setRenderingHints() method to create a shallow clone of the RenderingHints rather than saving it by reference. This will prevent modifications to the RenderingHints from not being detected by the RenderedOp. 2.2.3 RenderedOp Event Chaining When the rendering of a node is created, the node should be added as a sink of any PlanarImage or CollectionImage sources of the node. As described above, this will effectively cause the destination RenderedOp to be a listener of the "Rendering" event on the source RenderedOps, the "Collection" event on the source CollectionOps, and the "InvalidRegion" event on the source PlanarImages as long as the destination RenderedOp exists. ("InvalidRegion" events are defined as being PropertyChangeEventJAI instances with the name "InvalidRegion" and with a java.awt.Shape value for each of getOldValue() and getNewValue(). These old and new values indicate the previous and current values of the emitting RenderedImage's invalid region.) If a node source which is a RenderedOp is removed either outright or as a result of being replaced by another source and the node has already been rendered, then the destination RenderedOp should be removed as a sink from the source RenderedOp. 2.2.4 State Caching When the node is rendered the state of the node should be saved for reference. The state consists of the following variables: the operation name, the OperationRegistry, the ParameterBlock (rendering - not node - sources, and parameters), and the RenderingHints. The ParameterBlock and RenderingHints should be saved as shallow clones. 2.2.5 Events 2.2.5.1 Response as PropertyChangeListener 2.2.5.1.1 The node's rendering is an instance of OpImage When propertyChange() is invoked on the RenderedOp with an object of class RenderingChangeEvent, the region of the current rendering which may be retained will be determined by using the mapSourceRect() methods of the OpImage rendering. If the property change is due to a local node attribute having been edited then the region will be obtained via the getInvalidRegion() of the associated OperationDescriptor. The archived state of the current rendering will provide the old ParameterBlock and both sets of RenderingHints and the new ParameterBlock will be derived as a shallow clone of the old ParameterBlock with the appropriate source being replaced by the new rendering of the source node. A new rendering will be created using the new source image. If the retained region is null no further local action is necessary. If the retained region is non-null then the actions to be performed depend on whether the images both have a TileCache. If either rendering has no cache then there are no actions to be effected related to the cache. If both renderings have a cache then all tiles of the old rendering should be retrieved from its cache using TileCache.getTiles() and those which overlap the retained region should be added to the cache of the new rendering. Note that this last procedure is the same regardless of whether both renderings share the same TileCache. Finally the RenderingChangeEvent is propagated to any registered PropertyChangeListeners and to any sinks which are PropertyChangeListeners. The old and new renderings invalid region are set to appropriate values. Note that both the retained region and the new rendering may be null (in fact if one is null both should be null) which indicates that the node's rendering has been completely invalidated and so the node must be re-rendered. 2.2.5.1.2 The node's rendering is not an instance of OpImage The rendering is re-rendered and a RenderingChangeEvent is propagated with a null valid region. 2.2.5.1.3 Node Property Management All locally cached properties which were not added by invoking setProperty() directly on the RenderedOp will be cleared from the local cache. The internal PropertySource object will be reset to null. 2.2.5.2 RenderingChangeEvent Firing A RenderingChangeEvent will be fired if subsequent to having been rendered by whatever means the following condition obtains: (A) Any of the critical attributes is changed (edited), i.e., the operation name, operation registry, sources, parameters, or rendering hints. (Note that a deferred parameter might be "edited" by virtue of responding to an event itself.) OR (B) The node receives a RenderingChangeEvent from a RenderedOp source, a CollectionChangeEvent from a CollectionOp source, or an "InvalidRegion" event from a RenderedImage source. AND (C) The old and new renderings differ over some non-empty region. wherein, to be precise, the grouping of the above conditions is (A v B) ^ C. This of course assumes that there is at least one registered listener of the "Rendering" event. 2.2.5.3 PropertyChangeEvent Firing Modifying any of the "critical attributes" of the RenderedOp, i.e., the operation name, OperationRegistry, ParameterBlock (sources and parameters), or RenderingHints would cause a PropertyChangeEvent event to be fired immediately to all registered listeners of this event. The source of the event object would in all cases be the RenderedOp in question and the property names would be: Property Name -------- ---- Operation Name "OperationName" OperationRegistry "OperationRegistry" ParameterBlock "ParameterBlock" RenderingHints "RenderingHints" The old and new values would obviously be the before and after values of the respective entities. Appendix: Examples of RenderingChangeEvent Firing Example 1: A -> B -> C In this case A has already been rendered and a critical attribute of A is edited after the rendering. 1) Edit A, e.g., change a source, parameter, or hint. 2) A calls OperationDescriptor.getInvalidRegion() to determine what region may be retained. 3) There are two possibilities: a) the invalid region is non-null: A re-renders itself and copies the tiles of the old rendering which do not intersect the invalid region to the cache of the new rendering. b) the valid region is null: A sets its rendering to null 4) A fires an RCE with the new value set to either the new rendering (case 3a) or to null (case 3b). 5) B receives the RCE from A. 6) There are two possibilities: a) B has not been rendered: the RCE has no net effect as the source of B is the RenderedOp A and not A's rendering. The RCE is no forwarded this terminates the event propagation. b) B has been rendered: there are two possibilities: i) A's new rendering is null: B sets its rendering to null and fires an RCE with new value null. ii) A's new rendering is non-null: B returns to algorithm step 2) above. Example 2: A -> DP -> B -> C DP denotes a DeferredProperty which is a parameter to node B. Node A is not a source of node B. 1-4) Same steps as example 1. 5) DP receives the RCE from A. 6) DP notifies all Observers that its new value is null. 7) B's internal Observer of DP receives notice from DP. 8) B's internal Observer sets node param to new DP. This is effectively the same as step one in example A. 9) B continues from item 2 in example 1 (replace A by B and B by C).