0001: /*
0002: * $RCSfile: PointOpImage.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.1 $
0009: * $Date: 2005/02/11 04:57:15 $
0010: * $State: Exp $
0011: */
0012: package javax.media.jai;
0013:
0014: import java.awt.Point;
0015: import java.awt.Rectangle;
0016: import java.awt.image.ColorModel;
0017: import java.awt.image.ComponentSampleModel;
0018: import java.awt.image.DataBuffer;
0019: import java.awt.image.IndexColorModel;
0020: import java.awt.image.MultiPixelPackedSampleModel;
0021: import java.awt.image.Raster;
0022: import java.awt.image.RenderedImage;
0023: import java.awt.image.SampleModel;
0024: import java.awt.image.SinglePixelPackedSampleModel;
0025: import java.awt.image.WritableRaster;
0026: import java.awt.image.WritableRenderedImage;
0027: import java.lang.reflect.Method;
0028: import java.util.Map;
0029: import java.util.Vector;
0030: import java.security.AccessController;
0031: import java.security.PrivilegedAction;
0032: import javax.media.jai.util.CaselessStringKey;
0033: import com.sun.media.jai.util.ImageUtil;
0034: import com.sun.media.jai.util.JDKWorkarounds;
0035:
0036: /**
0037: * An abstract base class for image operators that require only the
0038: * (x, y) pixel from each source image in order to compute the
0039: * destination pixel (x, y).
0040: *
0041: * <p> <code>PointOpImage</code> is intended as a convenient
0042: * superclass for <code>OpImage</code>s that only need to look at each
0043: * destination pixel's corresponding source pixels. Some examples are
0044: * lookup, contrast adjustment, pixel arithmetic, and color space
0045: * conversion.
0046: *
0047: * @see OpImage
0048: */
0049: public abstract class PointOpImage extends OpImage {
0050:
0051: /* Flag indicating that dispose() has been invoked. */
0052: private boolean isDisposed = false;
0053:
0054: /* Flag indicating whether the various flags have been set. */
0055: private boolean areFieldsInitialized = false;
0056:
0057: /* Flag indicating that in-place compatibility should be checked. */
0058: private boolean checkInPlaceOperation = false;
0059:
0060: /* Flag indicating whether in-place operation is enabled. */
0061: private boolean isInPlaceEnabled = false;
0062:
0063: /// BEGIN: Variable fields used only when in-place operation is enabled.
0064: /* The first source cast to a WritableRenderedImage. */
0065: private WritableRenderedImage source0AsWritableRenderedImage;
0066:
0067: /* The first source cast to an OpImage. */
0068: private OpImage source0AsOpImage;
0069:
0070: /* Flag indicating whether the first source is a WritableRenderedImage. */
0071: private boolean source0IsWritableRenderedImage;
0072: /// END: Variable fields use only when in-place operation is enabled.
0073:
0074: /* Flag indicating whether the bounds are the same as for all sources. */
0075: private boolean sameBounds;
0076:
0077: /* Flag indicating whether the tile grid is the same as for all sources. */
0078: private boolean sameTileGrid;
0079:
0080: // END in-place fields.
0081:
0082: /** Fills in the default layout settings. */
0083: private static ImageLayout layoutHelper(ImageLayout layout,
0084: Vector sources, Map config) {
0085: int numSources = sources.size();
0086:
0087: if (numSources < 1) {
0088: throw new IllegalArgumentException(JaiI18N
0089: .getString("Generic5"));
0090: }
0091:
0092: RenderedImage source0 = (RenderedImage) sources.get(0);
0093: Rectangle isect = new Rectangle(source0.getMinX(), source0
0094: .getMinY(), source0.getWidth(), source0.getHeight());
0095:
0096: Rectangle rect = new Rectangle();
0097: for (int i = 1; i < numSources; i++) {
0098: RenderedImage s = (RenderedImage) sources.get(i);
0099: rect.setBounds(s.getMinX(), s.getMinY(), s.getWidth(), s
0100: .getHeight());
0101: isect = isect.intersection(rect);
0102: }
0103:
0104: if (isect.isEmpty()) {
0105: throw new IllegalArgumentException(JaiI18N
0106: .getString("PointOpImage0"));
0107: }
0108:
0109: if (layout == null) {
0110: layout = new ImageLayout(isect.x, isect.y, isect.width,
0111: isect.height);
0112: } else {
0113: layout = (ImageLayout) layout.clone();
0114: if (!layout.isValid(ImageLayout.MIN_X_MASK)) {
0115: layout.setMinX(isect.x);
0116: }
0117: if (!layout.isValid(ImageLayout.MIN_Y_MASK)) {
0118: layout.setMinY(isect.y);
0119: }
0120: if (!layout.isValid(ImageLayout.WIDTH_MASK)) {
0121: layout.setWidth(isect.width);
0122: }
0123: if (!layout.isValid(ImageLayout.HEIGHT_MASK)) {
0124: layout.setHeight(isect.height);
0125: }
0126:
0127: Rectangle r = new Rectangle(layout.getMinX(null), layout
0128: .getMinY(null), layout.getWidth(null), layout
0129: .getHeight(null));
0130: if (r.isEmpty()) {
0131: throw new IllegalArgumentException(JaiI18N
0132: .getString("PointOpImage1"));
0133: }
0134:
0135: if (!isect.contains(r)) {
0136: throw new IllegalArgumentException(JaiI18N
0137: .getString("PointOpImage2"));
0138: }
0139: }
0140:
0141: // If no SampleModel is given, create a new SampleModel (and perhaps
0142: // ColorModel) corresponding to the minimum band count and maximum
0143: // data depth.
0144: if (numSources > 1
0145: && !layout.isValid(ImageLayout.SAMPLE_MODEL_MASK)) {
0146: // Determine the min number of bands and max range of data
0147:
0148: SampleModel sm = source0.getSampleModel();
0149: ColorModel cm = source0.getColorModel();
0150: int dtype0 = getAppropriateDataType(sm);
0151: int bands0 = getBandCount(sm, cm);
0152: int dtype = dtype0;
0153: int bands = bands0;
0154:
0155: for (int i = 1; i < numSources; i++) {
0156: RenderedImage source = (RenderedImage) sources.get(i);
0157: sm = source.getSampleModel();
0158: cm = source.getColorModel();
0159: int sourceBands = getBandCount(sm, cm);
0160:
0161: dtype = mergeTypes(dtype, getPixelType(sm));
0162: bands = Math.min(bands, sourceBands);
0163: }
0164:
0165: // Force data type to byte for multi-band bilevel data.
0166: if (dtype == PixelAccessor.TYPE_BIT && bands > 1) {
0167: dtype = DataBuffer.TYPE_BYTE;
0168: }
0169:
0170: // Set a new SampleModel if and only if that of source 0 is
0171: // not compatible.
0172: SampleModel sm0 = source0.getSampleModel();
0173: if (dtype != sm0.getDataType()
0174: || bands != sm0.getNumBands()) {
0175: int tw = layout.getTileWidth(source0);
0176: int th = layout.getTileHeight(source0);
0177: SampleModel sampleModel;
0178: if (dtype == PixelAccessor.TYPE_BIT) {
0179: sampleModel = new MultiPixelPackedSampleModel(
0180: DataBuffer.TYPE_BYTE, tw, th, 1);
0181: } else {
0182: sampleModel = RasterFactory
0183: .createPixelInterleavedSampleModel(dtype,
0184: tw, th, bands);
0185: }
0186:
0187: layout.setSampleModel(sampleModel);
0188:
0189: // Set a new ColorModel only if this one is incompatible.
0190: if (cm != null
0191: && !JDKWorkarounds.areCompatibleDataModels(
0192: sampleModel, cm)) {
0193: cm = ImageUtil.getCompatibleColorModel(sampleModel,
0194: config);
0195: layout.setColorModel(cm);
0196: }
0197: }
0198: }
0199:
0200: return layout;
0201: }
0202:
0203: /**
0204: * Returns the pixel type.
0205: */
0206: private static int getPixelType(SampleModel sampleModel) {
0207: return ImageUtil.isBinary(sampleModel) ? PixelAccessor.TYPE_BIT
0208: : sampleModel.getDataType();
0209: }
0210:
0211: /**
0212: * Returns the number of bands.
0213: */
0214: private static int getBandCount(SampleModel sampleModel,
0215: ColorModel colorModel) {
0216: if (ImageUtil.isBinary(sampleModel)) {
0217: return 1;
0218: } else if (colorModel instanceof IndexColorModel) {
0219: return colorModel.getNumComponents();
0220: } else {
0221: return sampleModel.getNumBands();
0222: }
0223: }
0224:
0225: /**
0226: * Determine the appropriate data type for the SampleModel.
0227: */
0228: private static int getAppropriateDataType(SampleModel sampleModel) {
0229: int dataType = sampleModel.getDataType();
0230: int retVal = dataType;
0231:
0232: if (ImageUtil.isBinary(sampleModel)) {
0233: retVal = PixelAccessor.TYPE_BIT;
0234: } else if (dataType == DataBuffer.TYPE_USHORT
0235: || dataType == DataBuffer.TYPE_INT) {
0236: boolean canUseBytes = true;
0237: boolean canUseShorts = true;
0238:
0239: int[] ss = sampleModel.getSampleSize();
0240: for (int i = 0; i < ss.length; i++) {
0241: if (ss[i] > 16) {
0242: canUseBytes = false;
0243: canUseShorts = false;
0244: break;
0245: }
0246: if (ss[i] > 8) {
0247: canUseBytes = false;
0248: }
0249: }
0250:
0251: if (canUseBytes) {
0252: retVal = DataBuffer.TYPE_BYTE;
0253: } else if (canUseShorts) {
0254: retVal = DataBuffer.TYPE_USHORT;
0255: }
0256: }
0257:
0258: return retVal;
0259: }
0260:
0261: /**
0262: * Returns a type (one of the enumerated constants from
0263: * DataBuffer) that has sufficent range to contain values from
0264: * either of two given types. This corresponds to an upwards move
0265: * in the type lattice.
0266: *
0267: * <p> Note that the merge of SHORT and USHORT is INT, so it is not
0268: * correct to simply use the larger of the types.
0269: */
0270: private static int mergeTypes(int type0, int type1) {
0271: if (type0 == type1) {
0272: return type0;
0273: }
0274:
0275: // Default to second type.
0276: int type = type1;
0277:
0278: // Use switch logic to avoid depending on monotonicity of
0279: // DataBuffer.TYPE_*.
0280: switch (type0) {
0281: case PixelAccessor.TYPE_BIT:
0282: case DataBuffer.TYPE_BYTE:
0283: // Do nothing.
0284: break;
0285: case DataBuffer.TYPE_SHORT:
0286: if (type1 == DataBuffer.TYPE_BYTE) {
0287: type = DataBuffer.TYPE_SHORT;
0288: } else if (type1 == DataBuffer.TYPE_USHORT) {
0289: type = DataBuffer.TYPE_INT;
0290: }
0291: break;
0292: case DataBuffer.TYPE_USHORT:
0293: if (type1 == DataBuffer.TYPE_BYTE) {
0294: type = DataBuffer.TYPE_USHORT;
0295: } else if (type1 == DataBuffer.TYPE_SHORT) {
0296: type = DataBuffer.TYPE_INT;
0297: }
0298: break;
0299: case DataBuffer.TYPE_INT:
0300: if (type1 == DataBuffer.TYPE_BYTE
0301: || type1 == DataBuffer.TYPE_SHORT
0302: || type1 == DataBuffer.TYPE_USHORT) {
0303: type = DataBuffer.TYPE_INT;
0304: }
0305: break;
0306: case DataBuffer.TYPE_FLOAT:
0307: if (type1 != DataBuffer.TYPE_DOUBLE) {
0308: type = DataBuffer.TYPE_FLOAT;
0309: }
0310: break;
0311: case DataBuffer.TYPE_DOUBLE:
0312: type = DataBuffer.TYPE_DOUBLE;
0313: break;
0314: }
0315:
0316: return type;
0317: }
0318:
0319: /**
0320: * Constructor.
0321: *
0322: * <p> There must be at least one valid source supplied via the
0323: * <code>sources</code> argument. However, there is no upper limit
0324: * on the number of sources this image may have.
0325: *
0326: * <p> The image's layout is encapsulated in the <code>layout</code>
0327: * argument. If the image bounds are supplied they must be contained
0328: * within the intersected source bounds which must be non-empty.
0329: * If the bounds are not supplied, they are calculated to be the
0330: * intersection of the bounds of all sources.
0331: *
0332: * <p> If no <code>SampleModel</code> is specified in the layout, a new
0333: * <code>SampleModel</code> will be created. This <code>SampleModel</code>
0334: * will have a number of bands equal to the minimum band count of all
0335: * sources and a depth which can accomodate the data of all sources.
0336: * The band count of sources which have an <code>IndexColorModel</code>
0337: * will be set to the number of components of the
0338: * <code>IndexColorModel</code> instead of to the number of bands of the
0339: * <code>SampleModel</code>.
0340: *
0341: * <p> In all cases, the layout is forwarded to the <code>OpImage</code>
0342: * constructor which sets the default layout values in the standard way.
0343: *
0344: * @param layout The layout parameters of the destination image.
0345: * @param sources The source images.
0346: * @param configuration Configurable attributes of the image including
0347: * configuration variables indexed by
0348: * <code>RenderingHints.Key</code>s and image properties indexed
0349: * by <code>String</code>s or <code>CaselessStringKey</code>s.
0350: * This is simply forwarded to the superclass constructor.
0351: * @param cobbleSources <code>true</code> if computeRect() expects
0352: * contiguous sources.
0353: *
0354: * @throws IllegalArgumentException If <code>sources</code> or any
0355: * object in <code>sources</code> is <code>null</code>.
0356: * @throws IllegalArgumentException if <code>sources</code> does not
0357: * contain at least one element.
0358: * @throws ClassCastException If any object in <code>sources</code>
0359: * is not a <code>RenderedImage</code>.
0360: * @throws IllegalArgumentException If combining the intersected
0361: * source bounds with the user-specified bounds, if any,
0362: * yields an empty rectangle, or the user-specified image bounds
0363: * extends beyond the intersection of all the source bounds.
0364: *
0365: * @since JAI 1.1
0366: */
0367: public PointOpImage(Vector sources, ImageLayout layout,
0368: Map configuration, boolean cobbleSources) {
0369: super (checkSourceVector(sources, true), layoutHelper(layout,
0370: sources, configuration), configuration, cobbleSources);
0371: }
0372:
0373: /**
0374: * Constructs a <code>PointOpImage</code> with one source image.
0375: * The image layout is computed as described in the constructor
0376: * taking a <code>Vector</code> of sources.
0377: *
0378: * @param layout The layout parameters of the destination image.
0379: * @param source The source image.
0380: * @param configuration Configurable attributes of the image including
0381: * configuration variables indexed by
0382: * <code>RenderingHints.Key</code>s and image properties indexed
0383: * by <code>String</code>s or <code>CaselessStringKey</code>s.
0384: * This is simply forwarded to the superclass constructor.
0385: * @param cobbleSources Indicates whether <code>computeRect()</code>
0386: * expects contiguous sources.
0387: *
0388: * @throws IllegalArgumentException if <code>source</code>
0389: * is <code>null</code>.
0390: *
0391: * @since JAI 1.1
0392: */
0393: public PointOpImage(RenderedImage source, ImageLayout layout,
0394: Map configuration, boolean cobbleSources) {
0395: this (vectorize(source), // vectorize() checks for null source.
0396: layout, configuration, cobbleSources);
0397: }
0398:
0399: /**
0400: * Constructs a <code>PointOpImage</code> with two source images.
0401: * The image layout is computed as described in the constructor
0402: * taking a <code>Vector</code> of sources.
0403: *
0404: * @param layout The layout parameters of the destination image.
0405: * @param source0 The first source image.
0406: * @param source1 The second source image.
0407: * @param configuration Configurable attributes of the image including
0408: * configuration variables indexed by
0409: * <code>RenderingHints.Key</code>s and image properties indexed
0410: * by <code>String</code>s or <code>CaselessStringKey</code>s.
0411: * This is simply forwarded to the superclass constructor.
0412: * @param cobbleSources Indicates whether <code>computeRect()</code>
0413: * expects contiguous sources.
0414: *
0415: * @throws IllegalArgumentException if <code>source0</code> or
0416: * <code>source1</code> is <code>null</code>.
0417: *
0418: * @since JAI 1.1
0419: */
0420: public PointOpImage(RenderedImage source0, RenderedImage source1,
0421: ImageLayout layout, Map configuration, boolean cobbleSources) {
0422: this (vectorize(source0, source1), // vectorize() checks for null sources.
0423: layout, configuration, cobbleSources);
0424: }
0425:
0426: /**
0427: * Constructs a <code>PointOpImage</code> with three source
0428: * images. The image layout is computed as described in the
0429: * constructor taking a <code>Vector</code> of sources.
0430: *
0431: * @param layout The layout parameters of the destination image.
0432: * @param source0 The first source image.
0433: * @param source1 The second source image.
0434: * @param source2 The third source image.
0435: * @param configuration Configurable attributes of the image including
0436: * configuration variables indexed by
0437: * <code>RenderingHints.Key</code>s and image properties indexed
0438: * by <code>String</code>s or <code>CaselessStringKey</code>s.
0439: * This is simply forwarded to the superclass constructor.
0440: * @param cobbleSources Indicates whether <code>computeRect()</code>
0441: * expects contiguous sources.
0442: *
0443: * @throws IllegalArgumentException if <code>source0</code> or
0444: * <code>source1</code> or <code>source2</code> is
0445: * <code>null</code>.
0446: *
0447: * @since JAI 1.1
0448: */
0449: public PointOpImage(RenderedImage source0, RenderedImage source1,
0450: RenderedImage source2, ImageLayout layout,
0451: Map configuration, boolean cobbleSources) {
0452: this (vectorize(source0, source1, source2), // vectorize() checks null
0453: layout, configuration, cobbleSources);
0454:
0455: }
0456:
0457: /*
0458: * Initialize flags and instance variables.
0459: */
0460: private synchronized void initializeFields() {
0461:
0462: if (areFieldsInitialized)
0463: return;
0464:
0465: PlanarImage source0 = getSource(0);
0466:
0467: if (checkInPlaceOperation) {
0468: // Set the in-place operation flag.
0469: // XXX: In-place operation could work equally well when source0
0470: // is a WritableRenderedImage. However this could produce
0471: // unexpected results so consequently it would be desirable if
0472: // some kind of hint could be set that in-place operation is to
0473: // be used. The following statement should then be changed such
0474: // that instead of
0475: //
0476: // source0 instanceof OpImage &&
0477: //
0478: // we have
0479: //
0480: // (source0 instanceof OpImage ||
0481: // (source0 instanceof WritableRenderedImage &&
0482: // isInPlaceHintSet))
0483: //
0484: Vector source0Sinks = source0.getSinks();
0485: isInPlaceEnabled = source0 != null
0486: && getTileGridXOffset() == source0
0487: .getTileGridXOffset()
0488: && getTileGridYOffset() == source0
0489: .getTileGridYOffset()
0490: && getBounds().equals(source0.getBounds())
0491: && source0 instanceof OpImage
0492: && hasCompatibleSampleModel(source0)
0493: && !(source0Sinks != null && source0Sinks.size() > 1);
0494:
0495: // Ensure that source0 computes unique tiles, i.e.,
0496: // computesUniqueTiles() returns false. This disqualifies
0497: // for example in-place operations when source0 is an instance
0498: // of NullOpImage or of a subclass of NullOpImage or
0499: // SourcelessOpImage which does not override
0500: // computesUniqueTiles() to return true.
0501: if (isInPlaceEnabled
0502: && !((OpImage) source0).computesUniqueTiles()) {
0503: isInPlaceEnabled = false;
0504: }
0505:
0506: // Unset the in-place flag if getTile() is overridden by the
0507: // class of which source0 is an instance.
0508: if (isInPlaceEnabled) {
0509: try {
0510: Method getTileMethod = source0
0511: .getClass()
0512: .getMethod(
0513: "getTile",
0514: new Class[] { int.class, int.class });
0515: Class opImageClass = Class
0516: .forName("javax.media.jai.OpImage");
0517: Class declaringClass = getTileMethod
0518: .getDeclaringClass();
0519:
0520: // Unset in-place flag if getTile() is overridden.
0521: if (!declaringClass.equals(opImageClass)) {
0522: isInPlaceEnabled = false;
0523: }
0524: } catch (ClassNotFoundException e) {
0525: isInPlaceEnabled = false;
0526: } catch (NoSuchMethodException e) {
0527: isInPlaceEnabled = false;
0528: }
0529: }
0530:
0531: // Set local fields as a function of the in-place operation flag.
0532: if (isInPlaceEnabled) {
0533: // Set the flag indicating source0's type.
0534: source0IsWritableRenderedImage = source0 instanceof WritableRenderedImage;
0535:
0536: // Cast the first source to one of the cached image variables.
0537: if (source0IsWritableRenderedImage) {
0538: source0AsWritableRenderedImage = (WritableRenderedImage) source0;
0539: } else {
0540: source0AsOpImage = (OpImage) source0;
0541: }
0542: }
0543:
0544: // Unset this flag.
0545: checkInPlaceOperation = false;
0546: }
0547:
0548: // Get the number of sources.
0549: int numSources = getNumSources();
0550:
0551: // Initialize the bounds and tile grid flags.
0552: sameBounds = true;
0553: sameTileGrid = true;
0554:
0555: // Loop over all sources or until both flags are false.
0556: for (int i = 0; i < numSources && (sameBounds || sameTileGrid); i++) {
0557: PlanarImage source = getSource(i);
0558:
0559: // Update the bounds flag.
0560: if (sameBounds) {
0561: sameBounds = sameBounds && minX == source.minX
0562: && minY == source.minY && width == source.width
0563: && height == source.height;
0564: }
0565:
0566: // Update the tile grid flag.
0567: if (sameTileGrid) {
0568: sameTileGrid = sameTileGrid
0569: && tileGridXOffset == source.tileGridXOffset
0570: && tileGridYOffset == source.tileGridYOffset
0571: && tileWidth == source.tileWidth
0572: && tileHeight == source.tileHeight;
0573: }
0574: }
0575:
0576: // Set this flag.
0577: areFieldsInitialized = true;
0578: }
0579:
0580: /*
0581: * Check whether the <code>SampleModel</code> of the argument
0582: * <code>PlanarImage</code>is compatible with that of this
0583: * <code>PointOpImage</code>.
0584: *
0585: * @param src The <code>PlanarImage</code> whose <code>SampleModel</code>
0586: * is to be checked.
0587: * @return Whether the parameter has a compatible <code>SampleModel</code>.
0588: */
0589: private boolean hasCompatibleSampleModel(PlanarImage src) {
0590: SampleModel srcSM = src.getSampleModel();
0591: int numBands = sampleModel.getNumBands();
0592:
0593: boolean isCompatible = srcSM.getTransferType() == sampleModel
0594: .getTransferType()
0595: && srcSM.getWidth() == sampleModel.getWidth()
0596: && srcSM.getHeight() == sampleModel.getHeight()
0597: && srcSM.getNumBands() == numBands
0598: && srcSM.getClass().equals(sampleModel.getClass());
0599:
0600: if (isCompatible) {
0601: if (sampleModel instanceof ComponentSampleModel) {
0602: ComponentSampleModel smSrc = (ComponentSampleModel) srcSM;
0603: ComponentSampleModel smDst = (ComponentSampleModel) sampleModel;
0604: isCompatible = isCompatible
0605: && smSrc.getPixelStride() == smDst
0606: .getPixelStride()
0607: && smSrc.getScanlineStride() == smDst
0608: .getScanlineStride();
0609: int[] biSrc = smSrc.getBankIndices();
0610: int[] biDst = smDst.getBankIndices();
0611: int[] boSrc = smSrc.getBandOffsets();
0612: int[] boDst = smDst.getBandOffsets();
0613: for (int b = 0; b < numBands && isCompatible; b++) {
0614: isCompatible = isCompatible && biSrc[b] == biDst[b]
0615: && boSrc[b] == boDst[b];
0616: }
0617: } else if (sampleModel instanceof SinglePixelPackedSampleModel) {
0618: SinglePixelPackedSampleModel smSrc = (SinglePixelPackedSampleModel) srcSM;
0619: SinglePixelPackedSampleModel smDst = (SinglePixelPackedSampleModel) sampleModel;
0620: isCompatible = isCompatible
0621: && smSrc.getScanlineStride() == smDst
0622: .getScanlineStride();
0623: int[] bmSrc = smSrc.getBitMasks();
0624: int[] bmDst = smDst.getBitMasks();
0625: for (int b = 0; b < numBands && isCompatible; b++) {
0626: isCompatible = isCompatible && bmSrc[b] == bmDst[b];
0627: }
0628: } else if (sampleModel instanceof MultiPixelPackedSampleModel) {
0629: MultiPixelPackedSampleModel smSrc = (MultiPixelPackedSampleModel) srcSM;
0630: MultiPixelPackedSampleModel smDst = (MultiPixelPackedSampleModel) sampleModel;
0631: isCompatible = isCompatible
0632: && smSrc.getPixelBitStride() == smDst
0633: .getPixelBitStride()
0634: && smSrc.getScanlineStride() == smDst
0635: .getScanlineStride()
0636: && smSrc.getDataBitOffset() == smDst
0637: .getDataBitOffset();
0638: } else {
0639: isCompatible = false;
0640: }
0641: }
0642:
0643: return isCompatible;
0644: }
0645:
0646: /**
0647: * Causes a flag to be set to indicate that in-place operation should
0648: * be permitted if the image bounds, tile grid offset, tile dimensions,
0649: * and SampleModels of the source and destination images are compatible.
0650: * This method should be invoked in the constructor of the implementation
0651: * of a given operation only if that implementation is amenable to
0652: * in-place computation. Invocation of this method is a necessary but
0653: * not a sufficient condition for in-place computation actually to occur.
0654: * If the system property "javax.media.jai.PointOpImage.InPlace" is equal
0655: * to the string "false" in a case-insensitive fashion then in-place
0656: * operation will not be permitted.
0657: */
0658: protected void permitInPlaceOperation() {
0659: // Retrieve the in-place property.
0660: Object inPlaceProperty = null;
0661: try {
0662: inPlaceProperty = AccessController
0663: .doPrivileged(new PrivilegedAction() {
0664: public Object run() {
0665: String name = "javax.media.jai.PointOpImage.InPlace";
0666: return System.getProperty(name);
0667: }
0668: });
0669: } catch (SecurityException se) {
0670: /// as if the property isn't set
0671: }
0672: // Set the flag to false if and only if the property is set to
0673: // the string "false" (case-insensitive).
0674: checkInPlaceOperation = !(inPlaceProperty != null
0675: && inPlaceProperty instanceof String && ((String) inPlaceProperty)
0676: .equalsIgnoreCase("false"));
0677: }
0678:
0679: /**
0680: * Indicates whether the operation is being effected directly on the
0681: * associated colormap. This method will in general return
0682: * <code>true</code> if the image is the destination of a unary,
0683: * shift-invariant operation with an <code>IndexColorModel</code> equal
0684: * to that of its unique source.
0685: *
0686: * <p> When this method returns <code>true</code> the
0687: * <code>computeTile()</code> method in this class will return either
0688: * a copy of the corresponding region of the first source image or,
0689: * if the operation is being performed in place, the corresponding
0690: * tile of the first source image.
0691: *
0692: * <p> The implementation in this class always returns <code>false</code>.
0693: *
0694: * @since JAI 1.1
0695: */
0696: protected boolean isColormapOperation() {
0697: return false;
0698: }
0699:
0700: /**
0701: * Computes a tile. If source cobbling was requested at
0702: * construction time, the source tile boundaries are overlayed
0703: * onto the destination and <code>computeRect(Raster[],
0704: * WritableRaster, Rectangle)</code> is called for each of the
0705: * resulting regions. Otherwise, <code>computeRect(PlanarImage[],
0706: * WritableRaster, Rectangle)</code> is called once to compute the
0707: * entire active area of the tile.
0708: *
0709: * <p> The image bounds may be larger than the bounds of the
0710: * source image. In this case, samples for which there are no
0711: * corresponding sources are set to zero.
0712: *
0713: * @param tileX The X index of the tile.
0714: * @param tileY The Y index of the tile.
0715: */
0716: public Raster computeTile(int tileX, int tileY) {
0717: if (!cobbleSources) {
0718: return super .computeTile(tileX, tileY);
0719: }
0720:
0721: // Make sure the fields are initialized.
0722: initializeFields();
0723:
0724: // Get a WritableRaster to represent this tile.
0725: WritableRaster dest = null;
0726: if (isInPlaceEnabled) {
0727: if (source0IsWritableRenderedImage) {
0728: // Check one out from the WritableRenderedImage source.
0729: dest = source0AsWritableRenderedImage.getWritableTile(
0730: tileX, tileY);
0731: } else { // source0 is OpImage
0732: // Re-use one from the OpImage source.
0733: // First check whether the source raster is cached.
0734: Raster raster = source0AsOpImage.getTileFromCache(
0735: tileX, tileY);
0736:
0737: if (raster == null) {
0738: // Compute the tile.
0739: try {
0740: raster = source0AsOpImage.computeTile(tileX,
0741: tileY);
0742: if (raster instanceof WritableRaster) {
0743: dest = (WritableRaster) raster;
0744: }
0745: } catch (Exception e) {
0746: // Do nothing: this catch is simply in case the
0747: // OpImage in question does not itself implement
0748: // computeTile() in which case it may be resolved
0749: // to OpImage.computeTile() which will throw an
0750: // Exception.
0751: }
0752: }
0753: }
0754: }
0755:
0756: // Set tile recycling flag.
0757: boolean recyclingSource0Tile = dest != null;
0758:
0759: if (!recyclingSource0Tile) {
0760: // Create a new WritableRaster.
0761: Point org = new Point(tileXToX(tileX), tileYToY(tileY));
0762: dest = createWritableRaster(sampleModel, org);
0763: }
0764:
0765: // Colormap operation: return the source Raster if operating
0766: // in place or a copy thereof otherwise.
0767: if (isColormapOperation()) {
0768: if (!recyclingSource0Tile) {
0769: PlanarImage src = getSource(0);
0770: Raster srcTile = null;
0771: Rectangle srcRect = null;
0772: Rectangle dstRect = dest.getBounds();
0773:
0774: // Confirm that the tile grids of the source and destination
0775: // are the same
0776: if (sameTileGrid) {
0777: // Tile grids are aligned so the tile indices correspond
0778: // to pixels at the same locations in source and destination
0779: srcTile = getSource(0).getTile(tileX, tileY);
0780: } else if (dstRect.intersects(src.getBounds())) {
0781: // Tile grids are not aligned but the destination rectangle
0782: // intersects the source bounds so get the data using
0783: // the destination rectangle
0784: srcTile = src.getData(dstRect);
0785: } else {
0786: // The destination rectangle does not interest the source
0787: // bounds so just return the destination.
0788: return dest;
0789: }
0790:
0791: srcRect = srcTile.getBounds();
0792:
0793: // Ensure that the source tile doesn't lie outside the
0794: // destination tile.
0795: if (!dstRect.contains(srcRect)) {
0796: srcRect = dstRect.intersection(srcRect);
0797: srcTile = srcTile.createChild(srcTile.getMinX(),
0798: srcTile.getMinY(), srcRect.width,
0799: srcRect.height, srcRect.x, srcRect.y, null);
0800: }
0801:
0802: JDKWorkarounds.setRect(dest, srcTile, 0, 0);
0803: }
0804: return dest;
0805: }
0806:
0807: // Output bounds are initially equal to the tile bounds.
0808: int destMinX = dest.getMinX();
0809: int destMinY = dest.getMinY();
0810: int destMaxX = destMinX + dest.getWidth();
0811: int destMaxY = destMinY + dest.getHeight();
0812:
0813: // Clip output bounds to the dest image bounds.
0814: Rectangle bounds = getBounds();
0815: if (destMinX < bounds.x) {
0816: destMinX = bounds.x;
0817: }
0818: int boundsMaxX = bounds.x + bounds.width;
0819: if (destMaxX > boundsMaxX) {
0820: destMaxX = boundsMaxX;
0821: }
0822: if (destMinY < bounds.y) {
0823: destMinY = bounds.y;
0824: }
0825: int boundsMaxY = bounds.y + bounds.height;
0826: if (destMaxY > boundsMaxY) {
0827: destMaxY = boundsMaxY;
0828: }
0829:
0830: // Get the number of sources.
0831: int numSrcs = getNumSources();
0832:
0833: // Branch to actual destination rectangle computation as a
0834: // function of in-place operation and layout compatibility.
0835: if (recyclingSource0Tile && numSrcs == 1) {
0836: // Recycling tile from a single source.
0837: Raster[] sources = new Raster[] { dest };
0838: Rectangle destRect = new Rectangle(destMinX, destMinY,
0839: destMaxX - destMinX, destMaxY - destMinY);
0840: computeRect(sources, dest, destRect);
0841: } else if (recyclingSource0Tile && sameBounds && sameTileGrid) {
0842: // Recycling tile from first of layout-compatible sources.
0843: Raster[] sources = new Raster[numSrcs];
0844: sources[0] = dest;
0845: for (int i = 1; i < numSrcs; i++) {
0846: sources[i] = getSource(i).getTile(tileX, tileY);
0847: }
0848: Rectangle destRect = new Rectangle(destMinX, destMinY,
0849: destMaxX - destMinX, destMaxY - destMinY);
0850: computeRect(sources, dest, destRect);
0851: } else {
0852: // Clip against source bounds only if necessary.
0853: if (!sameBounds) {
0854: // Clip output bounds to each source image bounds
0855: for (int i = recyclingSource0Tile ? 1 : 0; i < numSrcs; i++) {
0856: bounds = getSource(i).getBounds();
0857: if (destMinX < bounds.x) {
0858: destMinX = bounds.x;
0859: }
0860: boundsMaxX = bounds.x + bounds.width;
0861: if (destMaxX > boundsMaxX) {
0862: destMaxX = boundsMaxX;
0863: }
0864: if (destMinY < bounds.y) {
0865: destMinY = bounds.y;
0866: }
0867: boundsMaxY = bounds.y + bounds.height;
0868: if (destMaxY > boundsMaxY) {
0869: destMaxY = boundsMaxY;
0870: }
0871:
0872: if (destMinX >= destMaxX || destMinY >= destMaxY) {
0873: return dest; // no corresponding source region
0874: }
0875: }
0876: }
0877:
0878: // Initialize the (possibly clipped) destination Rectangle.
0879: Rectangle destRect = new Rectangle(destMinX, destMinY,
0880: destMaxX - destMinX, destMaxY - destMinY);
0881:
0882: // Allocate memory for source Rasters.
0883: Raster[] sources = new Raster[numSrcs];
0884:
0885: if (sameTileGrid) {
0886: // All sources share the tile grid of the destination so
0887: // there is no need for splits.
0888: if (recyclingSource0Tile) {
0889: sources[0] = dest;
0890: }
0891: for (int i = recyclingSource0Tile ? 1 : 0; i < numSrcs; i++) {
0892: sources[i] = getSource(i).getTile(tileX, tileY);
0893: }
0894:
0895: computeRect(sources, dest, destRect);
0896: } else {
0897: //
0898: // The tileWidth and tileHeight of the source image
0899: // may differ from this tileWidth and tileHeight.
0900: //
0901: IntegerSequence xSplits = new IntegerSequence(destMinX,
0902: destMaxX);
0903: xSplits.insert(destMinX);
0904: xSplits.insert(destMaxX);
0905:
0906: IntegerSequence ySplits = new IntegerSequence(destMinY,
0907: destMaxY);
0908: ySplits.insert(destMinY);
0909: ySplits.insert(destMaxY);
0910:
0911: for (int i = recyclingSource0Tile ? 1 : 0; i < numSrcs; i++) {
0912: PlanarImage s = getSource(i);
0913: s.getSplits(xSplits, ySplits, destRect);
0914: }
0915:
0916: //
0917: // Divide destRect into sub rectangles based on the source
0918: // splits, and compute each sub rectangle separately.
0919: //
0920: int x1, x2, y1, y2, w, h;
0921: Rectangle subRect = new Rectangle();
0922:
0923: ySplits.startEnumeration();
0924: for (y1 = ySplits.nextElement(); ySplits
0925: .hasMoreElements(); y1 = y2) {
0926: y2 = ySplits.nextElement();
0927: h = y2 - y1;
0928:
0929: xSplits.startEnumeration();
0930: for (x1 = xSplits.nextElement(); xSplits
0931: .hasMoreElements(); x1 = x2) {
0932: x2 = xSplits.nextElement();
0933: w = x2 - x1;
0934:
0935: // Get sources.
0936: if (recyclingSource0Tile) {
0937: sources[0] = dest;
0938: }
0939: for (int i = recyclingSource0Tile ? 1 : 0; i < numSrcs; i++) {
0940: PlanarImage s = getSource(i);
0941: int tx = s.XToTileX(x1);
0942: int ty = s.YToTileY(y1);
0943: sources[i] = s.getTile(tx, ty);
0944: }
0945:
0946: subRect.x = x1;
0947: subRect.y = y1;
0948: subRect.width = w;
0949: subRect.height = h;
0950: computeRect(sources, dest, subRect);
0951: }
0952: }
0953: }
0954: }
0955:
0956: if (recyclingSource0Tile && source0IsWritableRenderedImage) {
0957: source0AsWritableRenderedImage.releaseWritableTile(tileX,
0958: tileY);
0959: }
0960:
0961: return dest;
0962: }
0963:
0964: /**
0965: * Returns a conservative estimate of the destination region that
0966: * can potentially be affected by the pixels of a rectangle of a
0967: * given source. The resulting <code>Rectangle</code> is <u>not</u>
0968: * clipped to the destination image bounds.
0969: *
0970: * @param sourceRect the <code>Rectangle</code> in source coordinates.
0971: * @param sourceIndex the index of the source image.
0972: * @return a <code>Rectangle</code> indicating the potentially affected
0973: * destination region, or <code>null</code> if the region is unknown.
0974: * @throws IllegalArgumentException if <code>sourceIndex</code> is
0975: * negative or greater than the index of the last source.
0976: * @throws IllegalArgumentException if <code>sourceRect</code> is
0977: * <code>null</code>.
0978: */
0979: public final Rectangle mapSourceRect(Rectangle sourceRect,
0980: int sourceIndex) {
0981: if (sourceRect == null) {
0982: throw new IllegalArgumentException(JaiI18N
0983: .getString("Generic0"));
0984: }
0985:
0986: if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
0987: throw new IllegalArgumentException(JaiI18N
0988: .getString("Generic1"));
0989: }
0990: return new Rectangle(sourceRect);
0991: }
0992:
0993: /**
0994: * Returns a conservative estimate of the region of a specific
0995: * source that is required in order to compute the pixels of a
0996: * given destination rectangle. The resulting <code>Rectangle</code>
0997: * is <u>not</u> clipped to the source image bounds.
0998: *
0999: * @param destRect the <code>Rectangle</code> in source coordinates.
1000: * @param sourceIndex the index of the source image.
1001: * @return a <code>Rectangle</code> indicating the potentially affected
1002: * destination region.
1003: *
1004: * @throws IllegalArgumentException if <code>sourceIndex</code> is
1005: * negative or greater than the index of the last source.
1006: * @throws IllegalArgumentException if <code>destRect</code> is
1007: * <code>null</code>.
1008: */
1009: public final Rectangle mapDestRect(Rectangle destRect,
1010: int sourceIndex) {
1011: if (destRect == null) {
1012: throw new IllegalArgumentException(JaiI18N
1013: .getString("Generic0"));
1014: }
1015:
1016: if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
1017: throw new IllegalArgumentException(JaiI18N
1018: .getString("Generic1"));
1019: }
1020: return new Rectangle(destRect);
1021: }
1022:
1023: /**
1024: * Disposes of any remaining tiles in the <code>TileCache</code>.
1025: *
1026: * <p>If <code>cache</code> is non-<code>null</code>, in place operation
1027: * is enabled, and <code>tileRecycler</code> is non-<code>null</code>,
1028: * then all tiles owned by this specific image are removed from the cache.
1029: * Subsequent to this <code>super.dispose()</code> is invoked.</p>
1030: *
1031: * @since JAI 1.1.2
1032: */
1033: public synchronized void dispose() {
1034: if (isDisposed) {
1035: return;
1036: }
1037:
1038: isDisposed = true;
1039:
1040: if (cache != null && isInPlaceEnabled && tileRecycler != null) {
1041: cache.removeTiles(this);
1042: }
1043:
1044: super.dispose();
1045: }
1046: }
|