001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.ext.awt.image.rendered;
020:
021: import java.awt.Point;
022: import java.awt.Rectangle;
023: import java.awt.Shape;
024: import java.awt.Transparency;
025: import java.awt.color.ColorSpace;
026: import java.awt.image.ColorModel;
027: import java.awt.image.ComponentColorModel;
028: import java.awt.image.DataBuffer;
029: import java.awt.image.Raster;
030: import java.awt.image.RenderedImage;
031: import java.awt.image.SampleModel;
032: import java.awt.image.WritableRaster;
033: import java.util.HashMap;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Map;
037: import java.util.Set;
038: import java.util.Vector;
039:
040: import org.apache.batik.ext.awt.image.GraphicsUtil;
041:
042: // import org.apache.batik.ext.awt.image.DataBufferReclaimer;
043: // import java.awt.image.DataBufferInt;
044: // import java.awt.image.SinglePixelPackedSampleModel;
045:
046: /**
047: * This is an abstract base class that takes care of most of the
048: * normal issues surrounding the implementation of the CachableRed
049: * (RenderedImage) interface. It tries to make no assumptions about
050: * the subclass implementation.
051: *
052: * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
053: * @version $Id: AbstractRed.java 489226 2006-12-21 00:05:36Z cam $
054: */
055: public abstract class AbstractRed implements CachableRed {
056:
057: protected Rectangle bounds;
058: protected Vector srcs;
059: protected Map props;
060: protected SampleModel sm;
061: protected ColorModel cm;
062: protected int tileGridXOff, tileGridYOff;
063: protected int tileWidth, tileHeight;
064: protected int minTileX, minTileY;
065: protected int numXTiles, numYTiles;
066:
067: /**
068: * void constructor. The subclass must call one of the
069: * flavors of init before the object becomes usable.
070: * This is useful when the proper parameters to the init
071: * method need to be computed in the subclasses constructor.
072: */
073: protected AbstractRed() {
074: }
075:
076: /**
077: * Construct an Abstract RenderedImage from a bounds rect and props
078: * (may be null). The srcs Vector will be empty.
079: * @param bounds this defines the extent of the rable in the
080: * user coordinate system.
081: * @param props this initializes the props Map (may be null)
082: */
083: protected AbstractRed(Rectangle bounds, Map props) {
084: init((CachableRed) null, bounds, null, null, bounds.x,
085: bounds.y, props);
086: }
087:
088: /**
089: * Construct an Abstract RenderedImage from a source image and
090: * props (may be null).
091: * @param src will be the first (and only) member of the srcs
092: * Vector. Src is also used to set the bounds, ColorModel,
093: * SampleModel, and tile grid offsets.
094: * @param props this initializes the props Map. */
095: protected AbstractRed(CachableRed src, Map props) {
096: init(src, src.getBounds(), src.getColorModel(), src
097: .getSampleModel(), src.getTileGridXOffset(), src
098: .getTileGridYOffset(), props);
099: }
100:
101: /**
102: * Construct an Abstract RenderedImage from a source image, bounds
103: * rect and props (may be null).
104: * @param src will be the first (and only) member of the srcs
105: * Vector. Src is also used to set the ColorModel, SampleModel,
106: * and tile grid offsets.
107: * @param bounds The bounds of this image.
108: * @param props this initializes the props Map. */
109: protected AbstractRed(CachableRed src, Rectangle bounds, Map props) {
110: init(src, bounds, src.getColorModel(), src.getSampleModel(),
111: src.getTileGridXOffset(), src.getTileGridYOffset(),
112: props);
113: }
114:
115: /**
116: * Construct an Abstract RenderedImage from a source image, bounds
117: * rect and props (may be null).
118: * @param src if not null, will be the first (and only) member
119: * of the srcs Vector. Also if it is not null it provides the
120: * tile grid offsets, otherwise they are zero.
121: * @param bounds The bounds of this image.
122: * @param cm The ColorModel to use. If null it will default to
123: * ComponentColorModel.
124: * @param sm The sample model to use. If null it will construct
125: * a sample model the matches the given/generated ColorModel and is
126: * the size of bounds.
127: * @param props this initializes the props Map. */
128: protected AbstractRed(CachableRed src, Rectangle bounds,
129: ColorModel cm, SampleModel sm, Map props) {
130: init(src, bounds, cm, sm, (src == null) ? 0 : src
131: .getTileGridXOffset(), (src == null) ? 0 : src
132: .getTileGridYOffset(), props);
133: }
134:
135: /**
136: * Construct an Abstract Rable from a bounds rect and props
137: * (may be null). The srcs Vector will be empty.
138: * @param src will be the first (and only) member of the srcs
139: * Vector. Src is also used to set the ColorModel, SampleModel,
140: * and tile grid offsets.
141: * @param bounds this defines the extent of the rable in the
142: * user coordinate system.
143: * @param cm The ColorModel to use. If null it will default to
144: * ComponentColorModel.
145: * @param sm The sample model to use. If null it will construct
146: * a sample model the matches the given/generated ColorModel and is
147: * the size of bounds.
148: * @param tileGridXOff The x location of tile 0,0.
149: * @param tileGridYOff The y location of tile 0,0.
150: * @param props this initializes the props Map.
151: */
152: protected AbstractRed(CachableRed src, Rectangle bounds,
153: ColorModel cm, SampleModel sm, int tileGridXOff,
154: int tileGridYOff, Map props) {
155: init(src, bounds, cm, sm, tileGridXOff, tileGridYOff, props);
156: }
157:
158: /**
159: * This is one of two basic init function (this is for single
160: * source rendereds).
161: * It is provided so subclasses can compute various values
162: * before initializing all the state in the base class.
163: * You really should call this method before returning from
164: * your subclass constructor.
165: *
166: * @param src The source for the filter
167: * @param bounds The bounds of the image
168: * @param cm The ColorModel to use. If null it defaults to
169: * ComponentColorModel/ src's ColorModel.
170: * @param sm The Sample modle to use. If this is null it will
171: * use the src's sample model if that is null it will
172: * construct a sample model that matches the ColorModel
173: * and is the size of the whole image.
174: * @param tileGridXOff The x location of tile 0,0.
175: * @param tileGridYOff The y location of tile 0,0.
176: * @param props Any properties you want to associate with the image.
177: */
178: protected void init(CachableRed src, Rectangle bounds,
179: ColorModel cm, SampleModel sm, int tileGridXOff,
180: int tileGridYOff, Map props) {
181: this .srcs = new Vector(1);
182: if (src != null) {
183: this .srcs.add(src);
184: if (bounds == null)
185: bounds = src.getBounds();
186: if (cm == null)
187: cm = src.getColorModel();
188: if (sm == null)
189: sm = src.getSampleModel();
190: }
191:
192: this .bounds = bounds;
193: this .tileGridXOff = tileGridXOff;
194: this .tileGridYOff = tileGridYOff;
195:
196: this .props = new HashMap();
197: if (props != null) {
198: this .props.putAll(props);
199: }
200:
201: if (cm == null)
202: cm = new ComponentColorModel(ColorSpace
203: .getInstance(ColorSpace.CS_GRAY), new int[] { 8 },
204: false, false, Transparency.OPAQUE,
205: DataBuffer.TYPE_BYTE);
206:
207: this .cm = cm;
208:
209: if (sm == null)
210: sm = cm.createCompatibleSampleModel(bounds.width,
211: bounds.height);
212: this .sm = sm;
213:
214: // Recompute tileWidth/Height, minTileX/Y, numX/YTiles.
215: updateTileGridInfo();
216: }
217:
218: /**
219: * Construct an Abstract Rable from a List of sources a bounds rect
220: * and props (may be null).
221: * @param srcs This is used to initialize the srcs Vector. All
222: * the members of srcs must be CachableRed otherwise an error
223: * will be thrown.
224: * @param bounds this defines the extent of the rendered in pixels
225: * @param props this initializes the props Map.
226: */
227: protected AbstractRed(List srcs, Rectangle bounds, Map props) {
228: init(srcs, bounds, null, null, bounds.x, bounds.y, props);
229: }
230:
231: /**
232: * Construct an Abstract RenderedImage from a bounds rect,
233: * ColorModel (may be null), SampleModel (may be null) and props
234: * (may be null). The srcs Vector will be empty.
235: * @param srcs This is used to initialize the srcs Vector. All
236: * the members of srcs must be CachableRed otherwise an error
237: * will be thrown.
238: * @param bounds this defines the extent of the rendered in pixels
239: * @param cm The ColorModel to use. If null it will default to
240: * ComponentColorModel.
241: * @param sm The sample model to use. If null it will construct
242: * a sample model the matches the given/generated ColorModel and is
243: * the size of bounds.
244: * @param props this initializes the props Map.
245: */
246: protected AbstractRed(List srcs, Rectangle bounds, ColorModel cm,
247: SampleModel sm, Map props) {
248: init(srcs, bounds, cm, sm, bounds.x, bounds.y, props);
249: }
250:
251: /**
252: * Construct an Abstract RenderedImage from a bounds rect,
253: * ColorModel (may be null), SampleModel (may be null), tile grid
254: * offsets and props (may be null). The srcs Vector will be
255: * empty.
256: * @param srcs This is used to initialize the srcs Vector. All
257: * the members of srcs must be CachableRed otherwise an error
258: * will be thrown.
259: * @param bounds this defines the extent of the rable in the
260: * user coordinate system.
261: * @param cm The ColorModel to use. If null it will default to
262: * ComponentColorModel.
263: * @param sm The sample model to use. If null it will construct
264: * a sample model the matches the given/generated ColorModel and is
265: * the size of bounds.
266: * @param tileGridXOff The x location of tile 0,0.
267: * @param tileGridYOff The y location of tile 0,0.
268: * @param props this initializes the props Map.
269: */
270: protected AbstractRed(List srcs, Rectangle bounds, ColorModel cm,
271: SampleModel sm, int tileGridXOff, int tileGridYOff,
272: Map props) {
273: init(srcs, bounds, cm, sm, tileGridXOff, tileGridYOff, props);
274: }
275:
276: /**
277: * This is the basic init function.
278: * It is provided so subclasses can compute various values
279: * before initializing all the state in the base class.
280: * You really should call this method before returning from
281: * your subclass constructor.
282: *
283: * @param srcs The list of sources
284: * @param bounds The bounds of the image
285: * @param cm The ColorModel to use. If null it defaults to
286: * ComponentColorModel.
287: * @param sm The Sample modle to use. If this is null it will
288: * construct a sample model that matches the ColorModel
289: * and is the size of the whole image.
290: * @param tileGridXOff The x location of tile 0,0.
291: * @param tileGridYOff The y location of tile 0,0.
292: * @param props Any properties you want to associate with the image.
293: */
294: protected void init(List srcs, Rectangle bounds, ColorModel cm,
295: SampleModel sm, int tileGridXOff, int tileGridYOff,
296: Map props) {
297: this .srcs = new Vector();
298: if (srcs != null) {
299: this .srcs.addAll(srcs);
300: }
301:
302: if (srcs.size() != 0) {
303: CachableRed src = (CachableRed) srcs.get(0);
304: if (bounds == null)
305: bounds = src.getBounds();
306: if (cm == null)
307: cm = src.getColorModel();
308: if (sm == null)
309: sm = src.getSampleModel();
310: }
311:
312: this .bounds = bounds;
313: this .tileGridXOff = tileGridXOff;
314: this .tileGridYOff = tileGridYOff;
315: this .props = new HashMap();
316: if (props != null) {
317: this .props.putAll(props);
318: }
319:
320: if (cm == null)
321: cm = new ComponentColorModel(ColorSpace
322: .getInstance(ColorSpace.CS_GRAY), new int[] { 8 },
323: false, false, Transparency.OPAQUE,
324: DataBuffer.TYPE_BYTE);
325:
326: this .cm = cm;
327:
328: if (sm == null)
329: sm = cm.createCompatibleSampleModel(bounds.width,
330: bounds.height);
331: this .sm = sm;
332:
333: // Recompute tileWidth/Height, minTileX/Y, numX/YTiles.
334: updateTileGridInfo();
335: }
336:
337: /**
338: * This function computes all the basic information about the tile
339: * grid based on the data stored in sm, and tileGridX/YOff.
340: * It is responsible for updating tileWidth, tileHeight,
341: * minTileX/Y, and numX/YTiles.
342: */
343: protected void updateTileGridInfo() {
344: this .tileWidth = sm.getWidth();
345: this .tileHeight = sm.getHeight();
346:
347: int x1, y1, maxTileX, maxTileY;
348:
349: // This computes and caches important information about the
350: // structure of the tile grid in general.
351: minTileX = getXTile(bounds.x);
352: minTileY = getYTile(bounds.y);
353:
354: x1 = bounds.x + bounds.width - 1; // Xloc of right edge
355: maxTileX = getXTile(x1);
356: numXTiles = maxTileX - minTileX + 1;
357:
358: y1 = bounds.y + bounds.height - 1; // Yloc of right edge
359: maxTileY = getYTile(y1);
360: numYTiles = maxTileY - minTileY + 1;
361: }
362:
363: public Rectangle getBounds() {
364: return new Rectangle(getMinX(), getMinY(), getWidth(),
365: getHeight());
366: }
367:
368: public Vector getSources() {
369: return srcs;
370: }
371:
372: public ColorModel getColorModel() {
373: return cm;
374: }
375:
376: public SampleModel getSampleModel() {
377: return sm;
378: }
379:
380: public int getMinX() {
381: return bounds.x;
382: }
383:
384: public int getMinY() {
385: return bounds.y;
386: }
387:
388: public int getWidth() {
389: return bounds.width;
390: }
391:
392: public int getHeight() {
393: return bounds.height;
394: }
395:
396: public int getTileWidth() {
397: return tileWidth;
398: }
399:
400: public int getTileHeight() {
401: return tileHeight;
402: }
403:
404: public int getTileGridXOffset() {
405: return tileGridXOff;
406: }
407:
408: public int getTileGridYOffset() {
409: return tileGridYOff;
410: }
411:
412: public int getMinTileX() {
413: return minTileX;
414: }
415:
416: public int getMinTileY() {
417: return minTileY;
418: }
419:
420: public int getNumXTiles() {
421: return numXTiles;
422: }
423:
424: public int getNumYTiles() {
425: return numYTiles;
426: }
427:
428: public Object getProperty(String name) {
429: Object ret = props.get(name);
430: if (ret != null)
431: return ret;
432: Iterator i = srcs.iterator();
433: while (i.hasNext()) {
434: RenderedImage ri = (RenderedImage) i.next();
435: ret = ri.getProperty(name);
436: if (ret != null)
437: return ret;
438: }
439: return null;
440: }
441:
442: public String[] getPropertyNames() {
443: Set keys = props.keySet();
444: String[] ret = new String[keys.size()];
445: keys.toArray(ret);
446:
447: // Iterator iter = keys.iterator();
448: // int i=0;
449: // while (iter.hasNext()) {
450: // ret[i++] = (String)iter.next();
451: // }
452:
453: Iterator iter = srcs.iterator();
454: while (iter.hasNext()) {
455: RenderedImage ri = (RenderedImage) iter.next();
456: String[] srcProps = ri.getPropertyNames();
457: if (srcProps.length != 0) {
458: String[] tmp = new String[ret.length + srcProps.length];
459: System.arraycopy(ret, 0, tmp, 0, ret.length);
460: /// ??? System.arraycopy((tmp,ret.length,srcProps,0,srcProps.length);
461: System.arraycopy(srcProps, 0, tmp, ret.length,
462: srcProps.length);
463: ret = tmp;
464: }
465: }
466:
467: return ret;
468: }
469:
470: public Shape getDependencyRegion(int srcIndex, Rectangle outputRgn) {
471: if ((srcIndex < 0) || (srcIndex > srcs.size()))
472: throw new IndexOutOfBoundsException(
473: "Nonexistant source requested.");
474:
475: // Return empty rect if they don't intersect.
476: if (!outputRgn.intersects(bounds))
477: return new Rectangle();
478:
479: // We only depend on our source for stuff that is inside
480: // our bounds...
481: return outputRgn.intersection(bounds);
482: }
483:
484: public Shape getDirtyRegion(int srcIndex, Rectangle inputRgn) {
485: if (srcIndex != 0)
486: throw new IndexOutOfBoundsException(
487: "Nonexistant source requested.");
488:
489: // Return empty rect if they don't intersect.
490: if (!inputRgn.intersects(bounds))
491: return new Rectangle();
492:
493: // Changes in the input region don't propogate outside our
494: // bounds.
495: return inputRgn.intersection(bounds);
496: }
497:
498: // This is not included but can be implemented by the following.
499: // In which case you _must_ reimplement getTile.
500: // public WritableRaster copyData(WritableRaster wr) {
501: // copyToRaster(wr);
502: // return wr;
503: // }
504:
505: public Raster getTile(int tileX, int tileY) {
506: WritableRaster wr = makeTile(tileX, tileY);
507: return copyData(wr);
508: }
509:
510: public Raster getData() {
511: return getData(bounds);
512: }
513:
514: public Raster getData(Rectangle rect) {
515: SampleModel smRet = sm.createCompatibleSampleModel(rect.width,
516: rect.height);
517:
518: Point pt = new Point(rect.x, rect.y);
519: WritableRaster wr = Raster.createWritableRaster(smRet, pt);
520:
521: // System.out.println("GD DB: " + wr.getDataBuffer().getSize());
522: return copyData(wr);
523: }
524:
525: /**
526: * Returns the x index of tile under xloc.
527: * @param xloc the x location (in pixels) to get tile for.
528: * @return The tile index under xloc (may be outside tile grid).
529: */
530: public final int getXTile(int xloc) {
531: int tgx = xloc - tileGridXOff;
532: // We need to round to -infinity...
533: if (tgx >= 0)
534: return tgx / tileWidth;
535: else
536: return (tgx - tileWidth + 1) / tileWidth;
537: }
538:
539: /**
540: * Returns the y index of tile under yloc.
541: * @param yloc the y location (in pixels) to get tile for.
542: * @return The tile index under yloc (may be outside tile grid).
543: */
544: public final int getYTile(int yloc) {
545: int tgy = yloc - tileGridYOff;
546: // We need to round to -infinity...
547: if (tgy >= 0)
548: return tgy / tileHeight;
549: else
550: return (tgy - tileHeight + 1) / tileHeight;
551: }
552:
553: /**
554: * Copies data from this images tile grid into wr. wr may
555: * extend outside the bounds of this image in which case the
556: * data in wr outside the bounds will not be touched.
557: * @param wr Raster to fill with image data.
558: */
559: public void copyToRaster(WritableRaster wr) {
560: int tx0 = getXTile(wr.getMinX());
561: int ty0 = getYTile(wr.getMinY());
562: int tx1 = getXTile(wr.getMinX() + wr.getWidth() - 1);
563: int ty1 = getYTile(wr.getMinY() + wr.getHeight() - 1);
564:
565: if (tx0 < minTileX)
566: tx0 = minTileX;
567: if (ty0 < minTileY)
568: ty0 = minTileY;
569:
570: if (tx1 >= minTileX + numXTiles)
571: tx1 = minTileX + numXTiles - 1;
572: if (ty1 >= minTileY + numYTiles)
573: ty1 = minTileY + numYTiles - 1;
574:
575: final boolean is_INT_PACK = GraphicsUtil.is_INT_PACK_Data(
576: getSampleModel(), false);
577:
578: for (int y = ty0; y <= ty1; y++)
579: for (int x = tx0; x <= tx1; x++) {
580: Raster r = getTile(x, y);
581: if (is_INT_PACK)
582: GraphicsUtil.copyData_INT_PACK(r, wr);
583: else
584: GraphicsUtil.copyData_FALLBACK(r, wr);
585: }
586: }
587:
588: // static DataBufferReclaimer reclaim = new DataBufferReclaimer();
589:
590: /**
591: * This is a helper function that will create the tile requested
592: * Including properly subsetting the bounds of the tile to the
593: * bounds of the current image.
594: * @param tileX The x index of the tile to be built
595: * @param tileY The y index of the tile to be built
596: * @return The tile requested
597: * @exception IndexOutOfBoundsException if the requested tile index
598: * falles outside of the bounds of the tile grid for the image.
599: */
600: public WritableRaster makeTile(int tileX, int tileY) {
601: if ((tileX < minTileX) || (tileX >= minTileX + numXTiles)
602: || (tileY < minTileY)
603: || (tileY >= minTileY + numYTiles))
604: throw new IndexOutOfBoundsException("Requested Tile ("
605: + tileX + ',' + tileY
606: + ") lies outside the bounds of image");
607:
608: Point pt = new Point(tileGridXOff + tileX * tileWidth,
609: tileGridYOff + tileY * tileHeight);
610:
611: WritableRaster wr;
612: wr = Raster.createWritableRaster(sm, pt);
613: // if (!(sm instanceof SinglePixelPackedSampleModel))
614: // wr = Raster.createWritableRaster(sm, pt);
615: // else {
616: // SinglePixelPackedSampleModel sppsm;
617: // sppsm = (SinglePixelPackedSampleModel)sm;
618: // int stride = sppsm.getScanlineStride();
619: // int sz = stride*sppsm.getHeight();
620: //
621: // int [] data = reclaim.request(sz);
622: // DataBuffer db = new DataBufferInt(data, sz);
623: //
624: // reclaim.register(db);
625: //
626: // wr = Raster.createWritableRaster(sm, db, pt);
627: // }
628:
629: // System.out.println("MT DB: " + wr.getDataBuffer().getSize());
630:
631: int x0 = wr.getMinX();
632: int y0 = wr.getMinY();
633: int x1 = x0 + wr.getWidth() - 1;
634: int y1 = y0 + wr.getHeight() - 1;
635:
636: if ((x0 < bounds.x) || (x1 >= (bounds.x + bounds.width))
637: || (y0 < bounds.y)
638: || (y1 >= (bounds.y + bounds.height))) {
639: // Part of this raster lies outside our bounds so subset
640: // it so it only advertises the stuff inside our bounds.
641: if (x0 < bounds.x)
642: x0 = bounds.x;
643: if (y0 < bounds.y)
644: y0 = bounds.y;
645: if (x1 >= (bounds.x + bounds.width))
646: x1 = bounds.x + bounds.width - 1;
647: if (y1 >= (bounds.y + bounds.height))
648: y1 = bounds.y + bounds.height - 1;
649:
650: wr = wr.createWritableChild(x0, y0, x1 - x0 + 1, y1 - y0
651: + 1, x0, y0, null);
652: }
653: return wr;
654: }
655:
656: public static void copyBand(Raster src, int srcBand,
657: WritableRaster dst, int dstBand) {
658: Rectangle srcR = new Rectangle(src.getMinX(), src.getMinY(),
659: src.getWidth(), src.getHeight());
660: Rectangle dstR = new Rectangle(dst.getMinX(), dst.getMinY(),
661: dst.getWidth(), dst.getHeight());
662:
663: Rectangle cpR = srcR.intersection(dstR);
664:
665: int[] samples = null;
666: for (int y = cpR.y; y < cpR.y + cpR.height; y++) {
667: samples = src.getSamples(cpR.x, y, cpR.width, 1, srcBand,
668: samples);
669: dst.setSamples(cpR.x, y, cpR.width, 1, dstBand, samples);
670: }
671: }
672: }
|