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