001: /*
002: * $RCSfile: PixelReplacementImage.java,v $
003: *
004: *
005: * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * - Redistribution of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * - Redistribution in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * Neither the name of Sun Microsystems, Inc. or the names of
020: * contributors may be used to endorse or promote products derived
021: * from this software without specific prior written permission.
022: *
023: * This software is provided "AS IS," without a warranty of any
024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035: * POSSIBILITY OF SUCH DAMAGES.
036: *
037: * You acknowledge that this software is not designed or intended for
038: * use in the design, construction, operation or maintenance of any
039: * nuclear facility.
040: *
041: * $Revision: 1.1 $
042: * $Date: 2005/02/11 05:01:55 $
043: * $State: Exp $
044: */
045: package com.sun.media.jai.imageioimpl;
046:
047: import java.awt.Point;
048: import java.awt.Rectangle;
049: import java.awt.Shape;
050: import java.awt.image.Raster;
051: import java.awt.image.RenderedImage;
052: import java.beans.PropertyChangeEvent;
053: import java.beans.PropertyChangeListener;
054: import java.io.IOException;
055: import java.io.OutputStream;
056: import java.util.Map;
057: import java.util.Vector;
058: import javax.media.jai.ImageLayout;
059: import javax.media.jai.OpImage;
060: import javax.media.jai.PlanarImage;
061: import javax.media.jai.RenderedOp;
062: import javax.media.jai.PropertyChangeEventJAI;
063: import javax.media.jai.RenderingChangeEvent;
064: import javax.imageio.ImageWriter;
065: import javax.imageio.ImageWriteParam;
066: import javax.imageio.stream.ImageOutputStream;
067:
068: /**
069: * Implementation of <code>PlanarImage</code> for the "ImageWrite" operation
070: * for the case of <code>ImageWriter</code>s which can replace pixels. The
071: * sole purpose of this class is to respond to "invalidregion" events so
072: * as to update the written image.
073: */
074: final class PixelReplacementImage extends PlanarImage implements
075: PropertyChangeListener {
076:
077: /**
078: * The <code>ImageWriteParam</code> used in writing the image.
079: */
080: private ImageWriteParam param;
081:
082: /**
083: * The <code>ImageWriter</code> used to write the image.
084: */
085: private ImageWriter writer;
086:
087: /**
088: * The index of the image to be write.
089: */
090: private int imageIndex;
091:
092: /**
093: * A stream to be closed when the instance is disposed; may be null.
094: */
095: private ImageOutputStream streamToClose;
096:
097: /**
098: * Creates a <code>Vector</code> containing a single element.
099: */
100: private static Vector createVector(Object element) {
101: Vector v = new Vector(1);
102: v.add(element);
103: return v;
104: }
105:
106: /**
107: * XXX
108: */
109: PixelReplacementImage(RenderedImage source, Map configuration,
110: ImageWriteParam param, ImageWriter writer, int imageIndex,
111: ImageOutputStream streamToClose) throws IOException {
112: super (new ImageLayout(source), // Layout same as source.
113: createVector(source), configuration);
114:
115: // Verify that the writer can replace pixels.
116: if (!writer.canReplacePixels(imageIndex)) {
117: throw new IllegalArgumentException(
118: "!writer.canReplacePixels(imageIndex)");
119: }
120:
121: // Set the instance variables from the parameters.
122: // XXX Should ImageWriteParam original settings be cached for
123: // testing later to see whether anything important has changed?
124: this .param = param;
125: this .writer = writer;
126: this .imageIndex = imageIndex;
127: this .streamToClose = streamToClose;
128: }
129:
130: /**
131: * Close an <code>ImageOutputStream</code> passed in.
132: */
133: public void dispose() {
134: if (streamToClose != null) {
135: try {
136: streamToClose.close();
137: } catch (IOException e) {
138: // Ignore it.
139: }
140: }
141:
142: super .dispose();
143: }
144:
145: /**
146: * Gets a tile.
147: *
148: * @param tileX The X index of the tile.
149: * @param tileY The Y index of the tile.
150: */
151: public Raster getTile(int tileX, int tileY) {
152: return getSourceImage(0).getTile(tileX, tileY);
153: }
154:
155: // --- PropertyChangeListener implementation ---
156:
157: // XXX Doc
158: public void propertyChange(PropertyChangeEvent evt) {
159: PlanarImage source = getSourceImage(0);
160: Object eventSource = evt.getSource();
161:
162: //
163: // Process the event if the writer can replace pixels,
164: // the event source is the source of this OpImage,
165: // and the event name is "invalidregion".
166: //
167: if ((evt instanceof PropertyChangeEventJAI
168: && evt.getPropertyName().equalsIgnoreCase(
169: "invalidregion") && eventSource.equals(source))
170: || (evt instanceof RenderingChangeEvent
171: && evt.getOldValue().equals(source)
172: && eventSource instanceof RenderedOp && evt
173: .getNewValue().equals(
174: ((RenderedOp) eventSource)
175: .getRendering()))) {
176:
177: // Get the invalid region information.
178: Shape srcInvalidRegion = null;
179:
180: if (evt instanceof RenderingChangeEvent) {
181: // RenderingChangeEvent presumably from a source RenderedOp.
182: RenderingChangeEvent rcEvent = (RenderingChangeEvent) evt;
183:
184: // Get the invalidated region of the source.
185: srcInvalidRegion = rcEvent.getInvalidRegion();
186:
187: // Reset this image's source.
188: source = (PlanarImage) evt.getNewValue();
189: setSource(source, 0);
190:
191: // If entire source is invalid replace with source bounds.
192: if (srcInvalidRegion == null) {
193: srcInvalidRegion = ((PlanarImage) rcEvent
194: .getOldValue()).getBounds();
195: }
196: } else {
197: // Get the invalidated region of the source.
198: Object evtNewValue = (Shape) evt.getNewValue();
199:
200: // Continue if the value class is correct.
201: if (evtNewValue instanceof Shape) {
202: srcInvalidRegion = (Shape) evtNewValue;
203:
204: // If entire source is invalid replace with source bounds.
205: if (srcInvalidRegion == null) {
206: srcInvalidRegion = source.getBounds();
207: }
208: }
209: }
210:
211: // Return if the invalid portion could not be determined.
212: if (srcInvalidRegion == null) {
213: return;
214: }
215:
216: // Return if the invalid region does not overlap the param region.
217: if (param != null) {
218: Rectangle sourceRegion = param.getSourceRegion();
219: if (sourceRegion != null
220: && !srcInvalidRegion.intersects(sourceRegion)) {
221: return;
222: }
223: } else {
224: param = writer.getDefaultWriteParam();
225: }
226:
227: // Get indices of all tiles overlapping the invalid region.
228: Point[] tileIndices = source
229: .getTileIndices(srcInvalidRegion.getBounds());
230:
231: // Should not happen but return if tileIndices is null.
232: if (tileIndices == null)
233: return;
234:
235: // Get subsampling values.
236: int gridX = minX + param.getSubsamplingXOffset();
237: int gridY = minY + param.getSubsamplingYOffset();
238: int stepX = param.getSourceXSubsampling();
239: int stepY = param.getSourceYSubsampling();
240: boolean isSubsampling = stepX != 1 || stepY != 1
241: || gridX != minX || gridY != minY;
242:
243: // Loop over affected tiles.
244: int numTiles = tileIndices.length;
245: for (int i = 0; i < numTiles; i++) {
246: // Save the next tile index.
247: Point tileIndex = tileIndices[i];
248:
249: // Compute tile bounds.
250: Rectangle tileRect = source.getTileRect(tileIndex.x,
251: tileIndex.y);
252:
253: // Replace if bounds intersect invalid region.
254: if (srcInvalidRegion.intersects(tileRect)) {
255: // Get the source tile.
256: Raster raster = source.getTile(tileIndex.x,
257: tileIndex.y);
258:
259: Rectangle destRect;
260: if (isSubsampling) {
261: int destMinX = (tileRect.x - gridX + stepX - 1)
262: / stepX;
263: int destMinY = (tileRect.y - gridY + stepY - 1)
264: / stepY;
265: int destMaxX = (tileRect.x + tileRect.width
266: - gridX + stepX - 1)
267: / stepX;
268: int destMaxY = (tileRect.y + tileRect.height
269: - gridY + stepY - 1)
270: / stepY;
271: destRect = new Rectangle(destMinX, destMinY,
272: destMaxX - destMinX, destMaxY
273: - destMinY);
274: } else {
275: destRect = tileRect;
276: }
277:
278: // Replace the pixels.
279: try {
280: synchronized (writer) {
281: writer.prepareReplacePixels(imageIndex,
282: destRect);
283: param.setDestinationOffset(destRect
284: .getLocation());
285: writer.replacePixels(raster, param);
286: writer.endReplacePixels();
287: }
288: } catch (IOException e) {
289: throw new RuntimeException(e);
290: }
291: }
292: }
293: }
294: }
295: }
|