0001: /*
0002: * $RCSfile: ROI.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:18 $
0010: * $State: Exp $
0011: */
0012: package javax.media.jai;
0013:
0014: import com.sun.media.jai.util.ImageUtil;
0015: import java.awt.Point;
0016: import java.awt.Rectangle;
0017: import java.awt.RenderingHints;
0018: import java.awt.Shape;
0019: import java.awt.geom.AffineTransform;
0020: import java.awt.geom.Area;
0021: import java.awt.geom.Point2D;
0022: import java.awt.geom.Rectangle2D;
0023: import java.awt.image.BufferedImage;
0024: import java.awt.image.DataBuffer;
0025: import java.awt.image.MultiPixelPackedSampleModel;
0026: import java.awt.image.Raster;
0027: import java.awt.image.RenderedImage;
0028: import java.awt.image.SampleModel;
0029: import java.awt.image.WritableRaster;
0030: import java.awt.image.renderable.ParameterBlock;
0031: import java.awt.image.renderable.RenderedImageFactory;
0032: import java.io.IOException;
0033: import java.io.ObjectInputStream;
0034: import java.io.ObjectOutputStream;
0035: import java.io.Serializable;
0036: import java.util.Arrays;
0037: import java.util.Hashtable;
0038: import java.util.LinkedList;
0039: import java.util.ListIterator;
0040: import java.util.Vector;
0041: import javax.media.jai.iterator.RandomIter;
0042: import javax.media.jai.iterator.RandomIterFactory;
0043: import javax.media.jai.iterator.RectIter;
0044: import javax.media.jai.iterator.RectIterFactory;
0045: import javax.media.jai.remote.SerializableState;
0046: import javax.media.jai.remote.SerializerFactory;
0047:
0048: /**
0049: * The parent class for representations of a region of interest of an
0050: * image (currently only single band images with integral data types
0051: * are supported).
0052: * This class represents region information in image form, and
0053: * can thus be used as a fallback where a <code>Shape</code>
0054: * representation is unavailable. Where possible, subclasses such as
0055: * ROIShape are used since they provide a more compact means of
0056: * storage for large regions.
0057: *
0058: * <p> The getAsShape() method may be called optimistically on any
0059: * instance of ROI; however, it may return null to indicate that a
0060: * <code>Shape</code> representation of the ROI is not available. In
0061: * this case, getAsImage() should be called as a fallback.
0062: *
0063: * <p> Inclusion and exclusion of pixels is defined by a threshold value.
0064: * Pixel values greater than or equal to the threshold indicate inclusion.
0065: *
0066: */
0067: public class ROI implements Serializable {
0068:
0069: /** A RandomIter used to grab pixels from the ROI. */
0070: private transient RandomIter iter = null;
0071:
0072: /** The <code>PlanarImage</code> representation of the ROI. */
0073: transient PlanarImage theImage = null;
0074:
0075: /** The inclusion/exclusion threshold of the ROI. */
0076: int threshold = 127;
0077:
0078: /**
0079: * Merge a <code>LinkedList</code> of <code>Rectangle</code>s
0080: * representing run lengths of pixels in the ROI into a minimal
0081: * list wherein vertically abutting <code>Rectangle</code>s are
0082: * merged. The operation is effected in place.
0083: *
0084: * @param rectList The list of run length <code>Rectangle</code>s.
0085: * @throws IllegalArgumentException if rectList is null.
0086: * @return The merged list.
0087: */
0088: protected static LinkedList mergeRunLengthList(LinkedList rectList) {
0089:
0090: if (rectList == null) {
0091: throw new IllegalArgumentException(JaiI18N
0092: .getString("Generic0"));
0093: }
0094:
0095: // Merge the run length rectangles if more than one was detected.
0096: if (rectList.size() > 1) {
0097: // Traverse the list sequentially merging all subsequent
0098: // vertically abutting Rectangles with the same abscissa
0099: // origin and width with the current starting Rectangle.
0100: for (int mergeIndex = 0; mergeIndex < rectList.size() - 1; mergeIndex++) {
0101:
0102: ListIterator rectIter = rectList
0103: .listIterator(mergeIndex);
0104: Rectangle mergeRect = (Rectangle) rectIter.next();
0105:
0106: while (rectIter.hasNext()) {
0107: Rectangle runRect = (Rectangle) rectIter.next();
0108:
0109: // Calculate ordinate value of abutting rectangle.
0110: int abuttingY = mergeRect.y + mergeRect.height;
0111:
0112: if (runRect.y == abuttingY
0113: && runRect.x == mergeRect.x
0114: && runRect.width == mergeRect.width) {
0115: mergeRect = new Rectangle(mergeRect.x,
0116: mergeRect.y, mergeRect.width,
0117: mergeRect.height + runRect.height);
0118:
0119: // Remove "runRect" from the list.
0120: rectIter.remove();
0121:
0122: // Replace "mergeRect" with updated version.
0123: rectList.set(mergeIndex, (Object) mergeRect);
0124: } else if (runRect.y > abuttingY) {
0125: // All Rectangles in the list with index greater than
0126: // mergeIndex are runlength Rectangles and are sorted
0127: // in non-decreasing ordinate order. Therefore there
0128: // are no more Rectangles which could possibly be
0129: // merged with mergeRect.
0130: break;
0131: }
0132: }
0133: }
0134: }
0135:
0136: return rectList;
0137: }
0138:
0139: /**
0140: * The default constructor.
0141: *
0142: * Using this constructor means that the subclass must override
0143: * all methods that reference theImage.
0144: */
0145: protected ROI() {
0146: }
0147:
0148: /**
0149: * Constructs an ROI from a RenderedImage. The inclusion
0150: * threshold is taken to be halfway between the minimum and maximum
0151: * sample values specified by the image's SampleModel.
0152: *
0153: * @param im A single-banded RenderedImage.
0154: *
0155: * @throws IllegalArgumentException if im is null.
0156: * @throws IllegalArgumentException if im does not have exactly one band
0157: */
0158: public ROI(RenderedImage im) {
0159: this (im, 127);
0160: }
0161:
0162: /**
0163: * Constructs an ROI from a RenderedImage. The inclusion
0164: * threshold is specified explicitly.
0165: *
0166: * @param im A single-banded RenderedImage.
0167: * @param threshold The desired inclusion threshold.
0168: *
0169: * @throws IllegalArgumentException if im is null.
0170: * @throws IllegalArgumentException if im does not have exactly one band
0171: */
0172: public ROI(RenderedImage im, int threshold) {
0173:
0174: if (im == null) {
0175: throw new IllegalArgumentException(JaiI18N
0176: .getString("Generic0"));
0177: }
0178:
0179: SampleModel sm = im.getSampleModel();
0180:
0181: if (sm.getNumBands() != 1) {
0182: throw new IllegalArgumentException(JaiI18N
0183: .getString("ROI0"));
0184: }
0185:
0186: this .threshold = threshold;
0187:
0188: // If the image is already binary and the threshold is >1
0189: // then there is no work to do.
0190: if ((threshold >= 1) && ImageUtil.isBinary(sm)) {
0191: theImage = PlanarImage.wrapRenderedImage(im);
0192:
0193: // Otherwise binarize the image for efficiency.
0194: } else {
0195:
0196: ParameterBlockJAI pbj = new ParameterBlockJAI("binarize");
0197:
0198: pbj.setSource("source0", im);
0199: pbj.setParameter("threshold", (double) threshold);
0200:
0201: theImage = JAI.create("binarize", pbj, null);
0202: }
0203: }
0204:
0205: /** Get the iterator, construct it if need be. */
0206: private RandomIter getIter() {
0207: if (iter == null) {
0208: iter = RandomIterFactory.create(theImage, null);
0209: }
0210: return iter;
0211: }
0212:
0213: /** Returns the inclusion/exclusion threshold value. */
0214: public int getThreshold() {
0215: return threshold;
0216: }
0217:
0218: /** Sets the inclusion/exclusion threshold value. */
0219: public void setThreshold(int threshold) {
0220: this .threshold = threshold;
0221: ((RenderedOp) theImage).setParameter((double) threshold, 0);
0222: iter = null;
0223: getIter();
0224: }
0225:
0226: /** Returns the bounds of the ROI as a <code>Rectangle</code>. */
0227: public Rectangle getBounds() {
0228: return new Rectangle(theImage.getMinX(), theImage.getMinY(),
0229: theImage.getWidth(), theImage.getHeight());
0230: }
0231:
0232: /** Returns the bounds of the ROI as a <code>Rectangle2D</code>. */
0233: public Rectangle2D getBounds2D() {
0234: return new Rectangle2D.Float((float) theImage.getMinX(),
0235: (float) theImage.getMinY(),
0236: (float) theImage.getWidth(), (float) theImage
0237: .getHeight());
0238: }
0239:
0240: /**
0241: * Returns <code>true</code> if the ROI contains a given Point.
0242: *
0243: * @param p A Point identifying the pixel to be queried.
0244: * @throws IllegalArgumentException if p is null.
0245: * @return <code>true</code> if the pixel lies within the ROI.
0246: */
0247: public boolean contains(Point p) {
0248: if (p == null) {
0249: throw new IllegalArgumentException(JaiI18N
0250: .getString("Generic0"));
0251: }
0252:
0253: return contains(p.x, p.y);
0254: }
0255:
0256: /**
0257: * Returns <code>true</code> if the ROI contains a given Point2D.
0258: *
0259: * @param p A Point2D identifying the pixel to be queried.
0260: * @throws IllegalArgumentException if p is null.
0261: * @return <code>true</code> if the pixel lies within the ROI.
0262: */
0263: public boolean contains(Point2D p) {
0264: if (p == null) {
0265: throw new IllegalArgumentException(JaiI18N
0266: .getString("Generic0"));
0267: }
0268:
0269: return contains((int) p.getX(), (int) p.getY());
0270: }
0271:
0272: /**
0273: * Returns <code>true</code> if the ROI contains the point (x, y).
0274: *
0275: * @param x An int specifying the X coordinate of the pixel to be queried.
0276: * @param y An int specifying the Y coordinate of the pixel to be queried.
0277: * @return <code>true</code> if the pixel lies within the ROI.
0278: */
0279: public boolean contains(int x, int y) {
0280: int minX = theImage.getMinX();
0281: int minY = theImage.getMinY();
0282:
0283: return (x >= minX && x < minX + theImage.getWidth())
0284: && (y >= minY && y < minY + theImage.getHeight())
0285: && (getIter().getSample(x, y, 0) >= 1);
0286: }
0287:
0288: /**
0289: * Returns <code>true</code> if the ROI contain the point (x, y).
0290: *
0291: * @param x A double specifying the X coordinate of the pixel
0292: * to be queried.
0293: * @param y A double specifying the Y coordinate of the pixel
0294: * to be queried.
0295: * @return <code>true</code> if the pixel lies within the ROI.
0296: */
0297: public boolean contains(double x, double y) {
0298: return contains((int) x, (int) y);
0299: }
0300:
0301: /**
0302: * Returns <code>true</code> if a given <code>Rectangle</code> is
0303: * entirely included within the ROI.
0304: *
0305: * @param rect A <code>Rectangle</code> specifying the region to be tested
0306: * for inclusion.
0307: * @throws IllegalArgumentException if rect is null.
0308: * @return <code>true</code> if the rectangle is entirely
0309: * contained within the ROI.
0310: */
0311: public boolean contains(Rectangle rect) {
0312: if (rect == null) {
0313: throw new IllegalArgumentException(JaiI18N
0314: .getString("Generic0"));
0315: }
0316:
0317: if (!rect.equals(rect.intersection(getBounds()))) {
0318: return false;
0319: }
0320:
0321: byte[] packedData = ImageUtil.getPackedBinaryData(theImage
0322: .getData(), rect);
0323:
0324: // ImageUtil.getPackedBinaryData does not zero out the extra
0325: // bits used to pad to the nearest byte - therefore ignore
0326: // these bits.
0327: int leftover = rect.width % 8;
0328:
0329: if (leftover == 0) {
0330:
0331: for (int i = 0; i < packedData.length; i++)
0332: if ((packedData[i] & 0xff) != 0xff)
0333: return false;
0334:
0335: } else {
0336:
0337: int mask = ((1 << leftover) - 1) << (8 - leftover);
0338:
0339: for (int y = 0, k = 0; y < rect.height; y++) {
0340: for (int x = 0; x < rect.width - leftover; x += 8, k++) {
0341: if ((packedData[k] & 0xff) != 0xff)
0342: return false;
0343: }
0344:
0345: if ((packedData[k] & mask) != mask)
0346: return false;
0347:
0348: k++;
0349: }
0350: }
0351:
0352: return true;
0353: }
0354:
0355: /**
0356: * Returns <code>true</code> if a given <code>Rectangle2D</code> is
0357: * entirely included within the ROI.
0358: *
0359: * @param rect A <code>Rectangle2D</code> specifying the region to be
0360: * tested for inclusion.
0361: * @throws IllegalArgumentException if rect is null.
0362: * @return <code>true</code> if the rectangle is entirely contained
0363: * within the ROI.
0364: */
0365: public boolean contains(Rectangle2D rect) {
0366: if (rect == null) {
0367: throw new IllegalArgumentException(JaiI18N
0368: .getString("Generic0"));
0369: }
0370: Rectangle r = new Rectangle((int) rect.getX(), (int) rect
0371: .getY(), (int) rect.getWidth(), (int) rect.getHeight());
0372: return contains(r);
0373: }
0374:
0375: /**
0376: * Returns <code>true</code> if a given rectangle (x, y, w, h) is entirely
0377: * included within the ROI.
0378: *
0379: * @param x The int X coordinate of the upper left corner of the region.
0380: * @param y The int Y coordinate of the upper left corner of the region.
0381: * @param w The int width of the region.
0382: * @param h The int height of the region.
0383: * @return <code>true</code> if the rectangle is entirely contained
0384: * within the ROI.
0385: */
0386: public boolean contains(int x, int y, int w, int h) {
0387: Rectangle r = new Rectangle(x, y, w, h);
0388: return contains(r);
0389: }
0390:
0391: /**
0392: * Returns <code>true</code> if a given rectangle (x, y, w, h) is entirely
0393: * included within the ROI.
0394: *
0395: * @param x The double X coordinate of the upper left corner of the region.
0396: * @param y The double Y coordinate of the upper left corner of the region.
0397: * @param w The double width of the region.
0398: * @param h The double height of the region.
0399: *
0400: * @return <code>true</code> if the rectangle is entirely
0401: * contained within the ROI.
0402: */
0403: public boolean contains(double x, double y, double w, double h) {
0404: Rectangle rect = new Rectangle((int) x, (int) y, (int) w,
0405: (int) h);
0406: return contains(rect);
0407: }
0408:
0409: /**
0410: * Returns <code>true</code> if a given <code>Rectangle</code>
0411: * intersects the ROI.
0412: *
0413: * @param rect A <code>Rectangle</code> specifying the region to be tested
0414: * for inclusion.
0415: * @throws IllegalArgumentException if rect is null.
0416: * @return <code>true</code> if the rectangle intersects the ROI.
0417: */
0418: public boolean intersects(Rectangle rect) {
0419: if (rect == null) {
0420: throw new IllegalArgumentException(JaiI18N
0421: .getString("Generic0"));
0422: }
0423:
0424: Rectangle r = rect.intersection(getBounds());
0425:
0426: if (r.isEmpty()) {
0427: return false;
0428: }
0429:
0430: byte[] packedData = ImageUtil.getPackedBinaryData(theImage
0431: .getData(), r);
0432:
0433: // ImageUtil.getPackedBinaryData does not zero out the extra
0434: // bits used to pad to the nearest byte - therefore ignore
0435: // these bits.
0436: int leftover = r.width % 8;
0437:
0438: if (leftover == 0) {
0439:
0440: for (int i = 0; i < packedData.length; i++)
0441: if ((packedData[i] & 0xff) != 0)
0442: return true;
0443:
0444: } else {
0445:
0446: int mask = ((1 << leftover) - 1) << (8 - leftover);
0447:
0448: for (int y = 0, k = 0; y < r.height; y++) {
0449: for (int x = 0; x < r.width - leftover; x += 8, k++) {
0450: if ((packedData[k] & 0xff) != 0)
0451: return true;
0452: }
0453: if ((packedData[k] & mask) != 0)
0454: return true;
0455: k++;
0456: }
0457: }
0458:
0459: return false;
0460: }
0461:
0462: /**
0463: * Returns <code>true</code> if a given <code>Rectangle2D</code>
0464: * intersects the ROI.
0465: *
0466: * @param r A <code>Rectangle2D</code> specifying the region to be tested
0467: * for inclusion.
0468: * @throws IllegalArgumentException if r is null.
0469: * @return <code>true</code> if the rectangle intersects the ROI.
0470: */
0471: public boolean intersects(Rectangle2D r) {
0472: if (r == null) {
0473: throw new IllegalArgumentException(JaiI18N
0474: .getString("Generic0"));
0475: }
0476:
0477: Rectangle rect = new Rectangle((int) r.getX(), (int) r.getY(),
0478: (int) r.getWidth(), (int) r.getHeight());
0479: return intersects(rect);
0480: }
0481:
0482: /**
0483: * Returns <code>true</code> if a given rectangular region
0484: * intersects the ROI.
0485: *
0486: * @param x The int X coordinate of the upper left corner of the region.
0487: * @param y The int Y coordinate of the upper left corner of the region.
0488: * @param w The int width of the region.
0489: * @param h The int height of the region.
0490: * @return <code>true</code> if the rectangle intersects the ROI.
0491: */
0492: public boolean intersects(int x, int y, int w, int h) {
0493: Rectangle rect = new Rectangle(x, y, w, h);
0494: return intersects(rect);
0495: }
0496:
0497: /**
0498: * Returns <code>true</code> if a given rectangular region
0499: * intersects the ROI.
0500: *
0501: * @param x The double X coordinate of the upper left corner of the region.
0502: * @param y The double Y coordinate of the upper left corner of the region.
0503: * @param w The double width of the region.
0504: * @param h The double height of the region.
0505: * @return <code>true</code> if the rectangle intersects the ROI.
0506: */
0507: public boolean intersects(double x, double y, double w, double h) {
0508: Rectangle rect = new Rectangle((int) x, (int) y, (int) w,
0509: (int) h);
0510: return intersects(rect);
0511: }
0512:
0513: /**
0514: * Create a binary PlanarImage of the size/bounds specified by
0515: * the rectangle.
0516: */
0517: private static PlanarImage createBinaryImage(Rectangle r) {
0518:
0519: if ((r.x == 0) && (r.y == 0)) {
0520:
0521: BufferedImage bi = new BufferedImage(r.width, r.height,
0522: BufferedImage.TYPE_BYTE_BINARY);
0523:
0524: return PlanarImage.wrapRenderedImage(bi);
0525:
0526: } else {
0527:
0528: SampleModel sm = new MultiPixelPackedSampleModel(
0529: DataBuffer.TYPE_BYTE, r.width, r.height, 1);
0530:
0531: // Create a TiledImage into which to write.
0532: return new TiledImage(r.x, r.y, r.width, r.height, r.x,
0533: r.y, sm, PlanarImage.createColorModel(sm));
0534: }
0535: }
0536:
0537: /**
0538: * Creates a merged ROI by performing the specified image operation
0539: * on <code>this</code> image and the image of the specified ROI.
0540: *
0541: * @param ROI the ROI to merge with <code>this</code>
0542: * @param op the JAI operator to use for merge.
0543: *
0544: * @return the merged ROI
0545: */
0546: private ROI createOpROI(ROI roi, String op) {
0547:
0548: if (roi == null) {
0549: throw new IllegalArgumentException(JaiI18N
0550: .getString("Generic0"));
0551: }
0552:
0553: PlanarImage imThis = this .getAsImage();
0554: PlanarImage imROI = roi.getAsImage();
0555: PlanarImage imDest;
0556:
0557: Rectangle boundsThis = imThis.getBounds();
0558: Rectangle boundsROI = imROI.getBounds();
0559:
0560: // If the bounds of the two images do not match, then
0561: // expand as necessary to the union of the two bounds
0562: // using the "overlay" operator and then perform the JAI
0563: // operation.
0564: if (op.equals("and") || boundsThis.equals(boundsROI)) {
0565: imDest = JAI.create(op, imThis, imROI);
0566:
0567: } else if (op.equals("subtract")
0568: || boundsThis.contains(boundsROI)) {
0569:
0570: PlanarImage imBounds = createBinaryImage(boundsThis);
0571:
0572: imBounds = JAI.create("overlay", imBounds, imROI);
0573: imDest = JAI.create(op, imThis, imBounds);
0574:
0575: } else if (boundsROI.contains(boundsThis)) {
0576:
0577: PlanarImage imBounds = createBinaryImage(boundsROI);
0578:
0579: imBounds = JAI.create("overlay", imBounds, imThis);
0580: imDest = JAI.create(op, imBounds, imROI);
0581:
0582: } else {
0583:
0584: Rectangle merged = boundsThis.union(boundsROI);
0585:
0586: PlanarImage imBoundsThis = createBinaryImage(merged);
0587: PlanarImage imBoundsROI = createBinaryImage(merged);
0588:
0589: imBoundsThis = JAI.create("overlay", imBoundsThis, imThis);
0590: imBoundsROI = JAI.create("overlay", imBoundsROI, imROI);
0591: imDest = JAI.create(op, imBoundsThis, imBoundsROI);
0592: }
0593:
0594: return new ROI(imDest, threshold);
0595: }
0596:
0597: /**
0598: * Adds another <code>ROI</code> to this one and returns the result
0599: * as a new <code>ROI</code>. The supplied <code>ROI</code> will
0600: * be converted to a rendered form if necessary. The bounds of the
0601: * resultant <code>ROI</code> will be the union of the bounds of the
0602: * two <code>ROI</code>s being merged.
0603: *
0604: * @param roi An ROI.
0605: * @throws IllegalArgumentException if roi is null.
0606: * @return A new ROI containing the new ROI data.
0607: */
0608: public ROI add(ROI roi) {
0609: return createOpROI(roi, "add");
0610: }
0611:
0612: /**
0613: * Subtracts another <code>ROI</code> from this one and returns the
0614: * result as a new <code>ROI</code>. The supplied <code>ROI</code>
0615: * will be converted to a rendered form if necessary. The
0616: * bounds of the resultant <code>ROI</code> will be the same as
0617: * <code>this</code> <code>ROI</code>.
0618: *
0619: * @param roi An ROI.
0620: * @throws IllegalArgumentException if roi is null.
0621: * @return A new ROI containing the new ROI data.
0622: */
0623: public ROI subtract(ROI roi) {
0624: return createOpROI(roi, "subtract");
0625: }
0626:
0627: /**
0628: * Intersects the <code>ROI</code> with another <code>ROI</code> and returns the result as
0629: * a new <code>ROI</code>. The supplied <code>ROI</code> will be converted to a rendered
0630: * form if necessary. The bounds of the resultant <code>ROI</code> will be the
0631: * intersection of the bounds of the two <code>ROI</code>s being merged.
0632: *
0633: * @param roi An ROI.
0634: * @throws IllegalArgumentException if roi is null.
0635: * @return A new ROI containing the new ROI data.
0636: */
0637: public ROI intersect(ROI roi) {
0638: return createOpROI(roi, "and");
0639: }
0640:
0641: /**
0642: * Exclusive-ors the <code>ROI</code> with another <code>ROI</code>
0643: * and returns the result as a new <code>ROI</code>. The supplied
0644: * <code>ROI</code> will be converted to a rendered form if
0645: * necessary. The bounds of the resultant <code>ROI</code> will
0646: * be the union of the bounds of the two <code>ROI</code>s being
0647: * merged.
0648: *
0649: * @param roi An ROI.
0650: * @throws IllegalArgumentException if roi is null.
0651: * @return A new ROI containing the new ROI data.
0652: */
0653: public ROI exclusiveOr(ROI roi) {
0654: return createOpROI(roi, "xor");
0655: }
0656:
0657: /**
0658: * Performs an affine transformation and returns the result as a new
0659: * ROI. The transformation is performed by an "Affine" RIF using the
0660: * indicated interpolation method.
0661: *
0662: * @param at an AffineTransform specifying the transformation.
0663: * @param interp the Interpolation to be used.
0664: * @throws IllegalArgumentException if at is null.
0665: * @throws IllegalArgumentException if interp is null.
0666: * @return a new ROI containing the transformed ROI data.
0667: */
0668: public ROI transform(AffineTransform at, Interpolation interp) {
0669:
0670: if (at == null) {
0671: throw new IllegalArgumentException(JaiI18N
0672: .getString("ROI5"));
0673: }
0674:
0675: if (interp == null) {
0676: throw new IllegalArgumentException(JaiI18N
0677: .getString("ROI6"));
0678: }
0679:
0680: ParameterBlock paramBlock = new ParameterBlock();
0681: paramBlock.add(at);
0682: paramBlock.add(interp);
0683: return performImageOp("Affine", paramBlock, 0, null);
0684: }
0685:
0686: /**
0687: * Performs an affine transformation and returns the result as a new
0688: * ROI. The transformation is performed by an "Affine" RIF using
0689: * nearest neighbor interpolation.
0690: *
0691: * @param at an AffineTransform specifying the transformation.
0692: * @throws IllegalArgumentException if at is null.
0693: * @return a new ROI containing the transformed ROI data.
0694: */
0695: public ROI transform(AffineTransform at) {
0696: if (at == null) {
0697: throw new IllegalArgumentException(JaiI18N
0698: .getString("Generic0"));
0699: }
0700:
0701: return transform(at, Interpolation
0702: .getInstance(Interpolation.INTERP_NEAREST));
0703: }
0704:
0705: /**
0706: * Transforms an ROI using an imaging operation. The operation is
0707: * specified by a <code>RenderedImageFactory</code>. The
0708: * operation's <code>ParameterBlock</code>, minus the image source
0709: * itself is supplied, along with an index indicating where to
0710: * insert the ROI image. The <code>renderHints</code> argument
0711: * allows rendering hints to be passed in.
0712: *
0713: * @param RIF A <code>RenderedImageFactory</code> that will be used
0714: * to create the op.
0715: * @param paramBlock A <code>ParameterBlock</code> containing all
0716: * sources and parameters for the op except for the ROI itself.
0717: * @param sourceIndex The index of the <code>ParameterBlock</code>'s
0718: * sources where the ROI is to be inserted.
0719: * @param renderHints A <code>RenderingHints</code> object containing
0720: * rendering hints, or null.
0721: * @throws IllegalArgumentException if RIF is null.
0722: * @throws IllegalArgumentException if paramBlock is null.
0723: */
0724: public ROI performImageOp(RenderedImageFactory RIF,
0725: ParameterBlock paramBlock, int sourceIndex,
0726: RenderingHints renderHints) {
0727:
0728: if (RIF == null || paramBlock == null) {
0729: throw new IllegalArgumentException(JaiI18N
0730: .getString("Generic0"));
0731: }
0732:
0733: // Clone the ParameterBlock and insert a source
0734: ParameterBlock pb = (ParameterBlock) paramBlock.clone();
0735: Vector sources = pb.getSources();
0736: sources.insertElementAt(this .getAsImage(), sourceIndex);
0737:
0738: // Create a new RenderedImage based on the RIF
0739: // and ParameterBlock.
0740: RenderedImage im = RIF.create(pb, renderHints);
0741: return new ROI(im, threshold);
0742: }
0743:
0744: /**
0745: * Transforms an ROI using an imaging operation. The
0746: * operation is specified by name; the default JAI registry is
0747: * used to resolve this into a RIF. The operation's
0748: * <code>ParameterBlock</code>, minus the image source itself is supplied,
0749: * along with an index indicating where to insert the ROI image.
0750: * The <code>renderHints</code> argument allows rendering hints to
0751: * be passed in.
0752: *
0753: * @param name The name of the operation to perform.
0754: * @param paramBlock A <code>ParameterBlock</code> containing all
0755: * sources and parameters for the op except for the ROI itself.
0756: * @param sourceIndex The index of the <code>ParameterBlock</code>'s
0757: * sources where the ROI is to be inserted.
0758: * @param renderHints A <code>RenderingHints</code> object containing
0759: * rendering hints, or null.
0760: * @throws IllegalArgumentException if name is null.
0761: * @throws IllegalArgumentException if paramBlock is null.
0762: */
0763: public ROI performImageOp(String name, ParameterBlock paramBlock,
0764: int sourceIndex, RenderingHints renderHints) {
0765:
0766: if (name == null || paramBlock == null) {
0767: throw new IllegalArgumentException(JaiI18N
0768: .getString("Generic0"));
0769: }
0770:
0771: // Clone the ParameterBlock and insert a source
0772: ParameterBlock pb = (ParameterBlock) paramBlock.clone();
0773: Vector sources = pb.getSources();
0774: sources.insertElementAt(this .getAsImage(), sourceIndex);
0775:
0776: // Create a new RenderedImage based on the operation name
0777: // and ParameterBlock using the default registry.
0778: RenderedImage im = JAI.create(name, pb, renderHints);
0779: return new ROI(im, threshold);
0780: }
0781:
0782: /**
0783: * Returns a <code>Shape</code> representation of the
0784: * <code>ROI</code>, if possible. If none is available, null is
0785: * returned. A proper instance of <code>ROI</code> (one that is not
0786: * an instance of any subclass of <code>ROI</code>) will always
0787: * return null.
0788: *
0789: * @return The <code>ROI</code> as a <code>Shape</code>.
0790: */
0791: public Shape getAsShape() {
0792: return null;
0793: }
0794:
0795: /**
0796: * Returns a <code>PlanarImage</code> representation of the
0797: * <code>ROI</code>. This method will always succeed. This method
0798: * returns a (bilevel) image whose <code>SampleModel</code> is an
0799: * instance of <code>MultiPixelPackedSampleModel</code>.
0800: *
0801: * @return The <code>ROI</code> as a <code>PlanarImage</code>.
0802: */
0803: public PlanarImage getAsImage() {
0804: return theImage;
0805: }
0806:
0807: /**
0808: * Returns a bitmask for a given rectangular region of the ROI
0809: * indicating whether the pixel is included in the region of
0810: * interest. The results are packed into 32-bit integers, with
0811: * the MSB considered to lie on the left. The last entry in each
0812: * row of the result may have bits that lie outside of the
0813: * requested rectangle. These bits are guaranteed to be zeroed.
0814: *
0815: * <p> The <code>mask</code> array, if supplied, must be of length
0816: * equal to or greater than <code>height</code> and each of its
0817: * subarrays must have length equal to or greater than (width +
0818: * 31)/32. If <code>null</code> is passed in, a suitable array
0819: * will be constructed. If the mask is non-null but has
0820: * insufficient size, an exception will be thrown.
0821: *
0822: * @param x The X coordinate of the upper left corner of the rectangle.
0823: * @param y The Y coordinate of the upper left corner of the rectangle.
0824: * @param width The width of the rectangle.
0825: * @param height The height of the rectangle.
0826: * @param mask A two-dimensional array of ints at least
0827: * (width + 31)/32 entries wide and (height) entries tall,
0828: * or null.
0829: * @return A reference to the <code>mask</code> parameter, or
0830: * to a newly constructed array if <code>mask</code> is
0831: * <code>null</code>. If the specified rectangle does
0832: * intersect with the image bounds then a <code>null</code>
0833: * is returned.
0834: */
0835: public int[][] getAsBitmask(int x, int y, int width, int height,
0836: int[][] mask) {
0837:
0838: Rectangle rect = getBounds().intersection(
0839: new Rectangle(x, y, width, height));
0840:
0841: // Verify that the requested area actually intersects the ROI image.
0842: if (rect.isEmpty()) {
0843: return null;
0844: }
0845:
0846: // Determine the minimum required width of the bitmask in integers.
0847: int bitmaskIntWidth = (width + 31) / 32;
0848:
0849: // Construct bitmask array if argument is null.
0850: if (mask == null) {
0851: mask = new int[height][bitmaskIntWidth];
0852: } else if (mask.length < height
0853: || mask[0].length < bitmaskIntWidth) {
0854: throw new RuntimeException(JaiI18N.getString("ROI3"));
0855: }
0856:
0857: byte[] data = ImageUtil.getPackedBinaryData(theImage.getData(),
0858: rect);
0859:
0860: // ImageUtil.getPackedBinaryData does not zero out the extra
0861: // bits used to pad to the nearest byte - so zero these
0862: // bits out.
0863: int leftover = rect.width % 8;
0864:
0865: if (leftover != 0) {
0866: int datamask = ((1 << leftover) - 1) << (8 - leftover);
0867: int linestride = (width + 7) / 8;
0868:
0869: for (int i = linestride - 1; i < data.length; i += linestride) {
0870: data[i] = (byte) (data[i] & datamask);
0871: }
0872: }
0873:
0874: int lineStride = (rect.width + 7) / 8;
0875: int leftOver = lineStride % 4;
0876:
0877: int row, col, k;
0878: int ncols = (lineStride - leftOver) / 4;
0879:
0880: for (row = 0, k = 0; row < rect.height; row++) {
0881: int[] maskRow = mask[row];
0882:
0883: for (col = 0; col < ncols; col++) {
0884: maskRow[col] = ((data[k] & 0xff) << 24)
0885: | ((data[k + 1] & 0xff) << 16)
0886: | ((data[k + 2] & 0xff) << 8)
0887: | ((data[k + 3] & 0xff) << 0);
0888: k += 4;
0889: }
0890:
0891: switch (leftOver) {
0892: case 0:
0893: break;
0894: case 1:
0895: maskRow[col++] = ((data[k] & 0xff) << 24);
0896: break;
0897: case 2:
0898: maskRow[col++] = ((data[k] & 0xff) << 24)
0899: | ((data[k + 1] & 0xff) << 16);
0900: break;
0901: case 3:
0902: maskRow[col++] = ((data[k] & 0xff) << 24)
0903: | ((data[k + 1] & 0xff) << 16)
0904: | ((data[k + 2] & 0xff) << 8);
0905: break;
0906: }
0907:
0908: k += leftOver;
0909:
0910: Arrays.fill(maskRow, col, bitmaskIntWidth, 0);
0911: }
0912:
0913: // Clear any trailing rows.
0914: for (row = rect.height; row < height; row++) {
0915: Arrays.fill(mask[row], 0);
0916: }
0917:
0918: return mask;
0919: }
0920:
0921: /**
0922: * Returns a <code>LinkedList</code> of <code>Rectangle</code>s
0923: * for a given rectangular region of the ROI. The
0924: * <code>Rectangle</code>s in the list are merged into a minimal
0925: * set.
0926: *
0927: * @param x The X coordinate of the upper left corner of the rectangle.
0928: * @param y The Y coordinate of the upper left corner of the rectangle.
0929: * @param width The width of the rectangle.
0930: * @param height The height of the rectangle.
0931: * @return A <code>LinkedList</code> of <code>Rectangle</code>s.
0932: * If the specified rectangle does intersect with the image
0933: * bounds then a <code>null</code> is returned.
0934: */
0935: public LinkedList getAsRectangleList(int x, int y, int width,
0936: int height) {
0937: return getAsRectangleList(x, y, width, height, true);
0938: }
0939:
0940: /**
0941: * Returns a <code>LinkedList</code> of <code>Rectangle</code>s for
0942: * a given rectangular region of the ROI.
0943: *
0944: * @param x The X coordinate of the upper left corner of the rectangle.
0945: * @param y The Y coordinate of the upper left corner of the rectangle.
0946: * @param width The width of the rectangle.
0947: * @param height The height of the rectangle.
0948: * @param mergeRectangles <code>true</code> if the <code>Rectangle</code>s
0949: * are to be merged into a minimal set.
0950: * @return A <code>LinkedList</code> of <code>Rectangle</code>s.
0951: * If the specified rectangle does intersect with the image
0952: * bounds then a <code>null</code> is returned.
0953: */
0954: protected LinkedList getAsRectangleList(int x, int y, int width,
0955: int height, boolean mergeRectangles) {
0956:
0957: // Verify that the requested area actually intersects the ROI image.
0958: Rectangle bounds = getBounds();
0959: Rectangle rect = new Rectangle(x, y, width, height);
0960: if (!bounds.intersects(rect)) {
0961: return null;
0962: }
0963:
0964: // Clip the requested area to the ROI image if necessary.
0965: if (!bounds.contains(rect)) {
0966: rect = bounds.intersection(rect);
0967: x = rect.x;
0968: y = rect.y;
0969: width = rect.width;
0970: height = rect.height;
0971: }
0972:
0973: byte[] data = ImageUtil.getPackedBinaryData(theImage.getData(),
0974: rect);
0975:
0976: // ImageUtil.getPackedBinaryData does not zero out the extra
0977: // bits used to pad to the nearest byte - therefore ignore
0978: // these bits.
0979: int lineStride = (width + 7) / 8;
0980: int leftover = width % 8;
0981: int mask = (leftover == 0) ? 0xff
0982: : ((1 << leftover) - 1) << (8 - leftover);
0983:
0984: LinkedList rectList = new LinkedList();
0985:
0986: // Calculate the initial list of rectangles as a list of run
0987: // lengths which are in fact rectangles of unit height.
0988:
0989: int row, col, k, start, val, cnt;
0990:
0991: for (row = 0, k = 0; row < height; row++) {
0992:
0993: start = -1;
0994:
0995: for (col = 0, cnt = 0; col < lineStride; col++, k++) {
0996:
0997: val = data[k] & ((col == lineStride - 1) ? mask : 0xff);
0998:
0999: if (val == 0) {
1000: if (start >= 0) {
1001: rectList.addLast(new Rectangle(x + start, y
1002: + row, col * 8 - start, 1));
1003: start = -1;
1004: }
1005:
1006: } else if (val == 0xff) {
1007: if (start < 0) {
1008: start = col * 8;
1009: }
1010:
1011: } else {
1012: for (int bit = 7; bit >= 0; bit--) {
1013: if ((val & (1 << bit)) == 0x00) {
1014: if (start >= 0) {
1015: rectList.addLast(new Rectangle(x
1016: + start, y + row, col * 8
1017: + (7 - bit) - start, 1));
1018: start = -1;
1019: }
1020: } else {
1021: if (start < 0) {
1022: start = col * 8 + (7 - bit);
1023: }
1024: }
1025: }
1026: }
1027: }
1028:
1029: if (start >= 0) {
1030: rectList.addLast(new Rectangle(x + start, y + row, col
1031: * 8 - start, 1));
1032: }
1033: }
1034:
1035: // Return the list of Rectangles possibly merged into a minimal set.
1036: return mergeRectangles ? mergeRunLengthList(rectList)
1037: : rectList;
1038: }
1039:
1040: /**
1041: * Serialize the <code>ROI</code>.
1042: *
1043: * @param out The <code>ObjectOutputStream</code>.
1044: */
1045: private void writeObject(ObjectOutputStream out) throws IOException {
1046: out.defaultWriteObject();
1047: if (theImage != null) {
1048: out.writeBoolean(true);
1049: RenderingHints hints = new RenderingHints(null);
1050: hints.put(JAI.KEY_SERIALIZE_DEEP_COPY, new Boolean(true));
1051: out
1052: .writeObject(SerializerFactory.getState(theImage,
1053: hints));
1054: } else {
1055: out.writeBoolean(false);
1056: }
1057: }
1058:
1059: /**
1060: * Deserialize the <code>ROI</code>.
1061: *
1062: * @param in The <code>ObjectInputStream</code>.
1063: */
1064: private void readObject(ObjectInputStream in) throws IOException,
1065: ClassNotFoundException {
1066: in.defaultReadObject();
1067: if ((boolean) in.readBoolean()) {
1068: SerializableState ss = (SerializableState) in.readObject();
1069: RenderedImage ri = (RenderedImage) (ss.getObject());
1070: theImage = PlanarImage.wrapRenderedImage(ri);
1071: } else {
1072: theImage = null;
1073: }
1074: iter = null;
1075: }
1076: }
|