0001: /*
0002:
0003: Licensed to the Apache Software Foundation (ASF) under one or more
0004: contributor license agreements. See the NOTICE file distributed with
0005: this work for additional information regarding copyright ownership.
0006: The ASF licenses this file to You under the Apache License, Version 2.0
0007: (the "License"); you may not use this file except in compliance with
0008: the License. You may obtain a copy of the License at
0009:
0010: http://www.apache.org/licenses/LICENSE-2.0
0011:
0012: Unless required by applicable law or agreed to in writing, software
0013: distributed under the License is distributed on an "AS IS" BASIS,
0014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0015: See the License for the specific language governing permissions and
0016: limitations under the License.
0017:
0018: */
0019: package org.apache.batik.gvt;
0020:
0021: import java.awt.AlphaComposite;
0022: import java.awt.Composite;
0023: import java.awt.Graphics2D;
0024: import java.awt.RenderingHints;
0025: import java.awt.Shape;
0026: import java.awt.geom.AffineTransform;
0027: import java.awt.geom.NoninvertibleTransformException;
0028: import java.awt.geom.Point2D;
0029: import java.awt.geom.Rectangle2D;
0030: import java.lang.ref.WeakReference;
0031: import java.util.Iterator;
0032: import java.util.List;
0033: import java.util.Map;
0034:
0035: import javax.swing.event.EventListenerList;
0036:
0037: import org.apache.batik.ext.awt.RenderingHintsKeyExt;
0038: import org.apache.batik.ext.awt.image.renderable.ClipRable;
0039: import org.apache.batik.ext.awt.image.renderable.Filter;
0040: import org.apache.batik.gvt.event.GraphicsNodeChangeEvent;
0041: import org.apache.batik.gvt.event.GraphicsNodeChangeListener;
0042: import org.apache.batik.gvt.filter.GraphicsNodeRable;
0043: import org.apache.batik.gvt.filter.GraphicsNodeRable8Bit;
0044: import org.apache.batik.gvt.filter.Mask;
0045: import org.apache.batik.util.HaltingThread;
0046:
0047: /**
0048: * A partial implementation of the <tt>GraphicsNode</tt> interface.
0049: *
0050: * @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a>
0051: * @author <a href="mailto:etissandier@ilog.fr">Emmanuel Tissandier</a>
0052: * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
0053: * @version $Id: AbstractGraphicsNode.java 504084 2007-02-06 11:24:46Z dvholten $
0054: */
0055: public abstract class AbstractGraphicsNode implements GraphicsNode {
0056:
0057: /**
0058: * The listeners list.
0059: */
0060: protected EventListenerList listeners;
0061:
0062: /**
0063: * The transform of this graphics node.
0064: */
0065: protected AffineTransform transform;
0066:
0067: /**
0068: * The inverse transform for this node, i.e., from parent node
0069: * to this node.
0070: */
0071: protected AffineTransform inverseTransform;
0072:
0073: /**
0074: * The compositing operation to be used when a graphics node is
0075: * painted on top of another one.
0076: */
0077: protected Composite composite;
0078:
0079: /**
0080: * This flag bit indicates whether or not this graphics node is visible.
0081: */
0082: protected boolean isVisible = true;
0083:
0084: /**
0085: * The clipping filter for this graphics node.
0086: */
0087: protected ClipRable clip;
0088:
0089: /**
0090: * The rendering hints that control the quality to use when rendering
0091: * this graphics node.
0092: */
0093: protected RenderingHints hints;
0094:
0095: /**
0096: * The parent of this graphics node.
0097: */
0098: protected CompositeGraphicsNode parent;
0099:
0100: /**
0101: * The root of the GVT tree.
0102: */
0103: protected RootGraphicsNode root;
0104:
0105: /**
0106: * The mask of this graphics node.
0107: */
0108: protected Mask mask;
0109:
0110: /**
0111: * The filter of this graphics node.
0112: */
0113: protected Filter filter;
0114:
0115: /**
0116: * Indicates how this graphics node reacts to events.
0117: */
0118: protected int pointerEventType = VISIBLE_PAINTED;
0119:
0120: /**
0121: * The GraphicsNodeRable for this node.
0122: */
0123: protected WeakReference graphicsNodeRable;
0124:
0125: /**
0126: * The GraphicsNodeRable for this node with all filtering applied
0127: */
0128: protected WeakReference enableBackgroundGraphicsNodeRable;
0129:
0130: /**
0131: * A Weak Reference to this.
0132: */
0133: protected WeakReference weakRef;
0134:
0135: /**
0136: * Internal Cache: node bounds
0137: */
0138: private Rectangle2D bounds;
0139:
0140: protected GraphicsNodeChangeEvent changeStartedEvent = null;
0141: protected GraphicsNodeChangeEvent changeCompletedEvent = null;
0142:
0143: /**
0144: * Constructs a new graphics node.
0145: */
0146: protected AbstractGraphicsNode() {
0147: }
0148:
0149: /**
0150: * Returns a canonical WeakReference to this GraphicsNode.
0151: * This is suitable for use as a key value in a hash map
0152: */
0153: public WeakReference getWeakReference() {
0154: if (weakRef == null)
0155: weakRef = new WeakReference(this );
0156: return weakRef;
0157: }
0158:
0159: //
0160: // Properties methods
0161: //
0162:
0163: /**
0164: * Returns the type that describes how this graphics node reacts to events.
0165: *
0166: * @return VISIBLE_PAINTED | VISIBLE_FILL | VISIBLE_STROKE | VISIBLE |
0167: * PAINTED | FILL | STROKE | ALL | NONE
0168: */
0169: public int getPointerEventType() {
0170: return pointerEventType;
0171: }
0172:
0173: /**
0174: * Sets the type that describes how this graphics node reacts to events.
0175: *
0176: * @param pointerEventType VISIBLE_PAINTED | VISIBLE_FILL | VISIBLE_STROKE |
0177: * VISIBLE | PAINTED | FILL | STROKE | ALL | NONE
0178: */
0179: public void setPointerEventType(int pointerEventType) {
0180: this .pointerEventType = pointerEventType;
0181: }
0182:
0183: /**
0184: * Sets the transform of this node.
0185: *
0186: * @param newTransform the new transform of this node
0187: */
0188: public void setTransform(AffineTransform newTransform) {
0189: fireGraphicsNodeChangeStarted();
0190: this .transform = newTransform;
0191: if (transform.getDeterminant() != 0) {
0192: try {
0193: inverseTransform = transform.createInverse();
0194: } catch (NoninvertibleTransformException e) {
0195: // Should never happen.
0196: throw new Error(e.getMessage());
0197: }
0198: } else {
0199: // The transform is not invertible. Use the same
0200: // transform.
0201: inverseTransform = transform;
0202: }
0203: if (parent != null)
0204: parent.invalidateGeometryCache();
0205: fireGraphicsNodeChangeCompleted();
0206: }
0207:
0208: /**
0209: * Returns the transform of this node or null if any.
0210: */
0211: public AffineTransform getTransform() {
0212: return transform;
0213: }
0214:
0215: /**
0216: * Returns the inverse transform for this node.
0217: */
0218: public AffineTransform getInverseTransform() {
0219: return inverseTransform;
0220: }
0221:
0222: /**
0223: * Returns the concatenated transform of this node. That is, this
0224: * node's transform preconcatenated with it's parent's transforms.
0225: */
0226: public AffineTransform getGlobalTransform() {
0227: AffineTransform ctm = new AffineTransform();
0228: GraphicsNode node = this ;
0229: while (node != null) {
0230: if (node.getTransform() != null) {
0231: ctm.preConcatenate(node.getTransform());
0232: }
0233: node = node.getParent();
0234: }
0235: return ctm;
0236: }
0237:
0238: /**
0239: * Sets the composite of this node.
0240: *
0241: * @param newComposite the composite of this node
0242: */
0243: public void setComposite(Composite newComposite) {
0244: fireGraphicsNodeChangeStarted();
0245: invalidateGeometryCache();
0246: this .composite = newComposite;
0247: fireGraphicsNodeChangeCompleted();
0248: }
0249:
0250: /**
0251: * Returns the composite of this node or null if any.
0252: */
0253: public Composite getComposite() {
0254: return composite;
0255: }
0256:
0257: /**
0258: * Sets if this node is visible or not depending on the specified value.
0259: *
0260: * @param isVisible If true this node is visible
0261: */
0262: public void setVisible(boolean isVisible) {
0263: fireGraphicsNodeChangeStarted();
0264: this .isVisible = isVisible;
0265: invalidateGeometryCache();
0266: fireGraphicsNodeChangeCompleted();
0267: }
0268:
0269: /**
0270: * Returns true if this node is visible, false otherwise.
0271: */
0272: public boolean isVisible() {
0273: return isVisible;
0274: }
0275:
0276: public void setClip(ClipRable newClipper) {
0277: if ((newClipper == null) && (this .clip == null))
0278: return; // No change still no clip.
0279:
0280: fireGraphicsNodeChangeStarted();
0281: invalidateGeometryCache();
0282: this .clip = newClipper;
0283: fireGraphicsNodeChangeCompleted();
0284: }
0285:
0286: /**
0287: * Returns the clipping filter of this node or null if any.
0288: */
0289: public ClipRable getClip() {
0290: return clip;
0291: }
0292:
0293: /**
0294: * Maps the specified key to the specified value in the rendering hints of
0295: * this node.
0296: *
0297: * @param key the key of the hint to be set
0298: * @param value the value indicating preferences for the specified
0299: * hint category.
0300: */
0301: public void setRenderingHint(RenderingHints.Key key, Object value) {
0302: fireGraphicsNodeChangeStarted();
0303: if (this .hints == null) {
0304: this .hints = new RenderingHints(key, value);
0305: } else {
0306: hints.put(key, value);
0307: }
0308: fireGraphicsNodeChangeCompleted();
0309: }
0310:
0311: /**
0312: * Copies all of the mappings from the specified Map to the
0313: * rendering hints of this node.
0314: *
0315: * @param hints the rendering hints to be set
0316: */
0317: public void setRenderingHints(Map hints) {
0318: fireGraphicsNodeChangeStarted();
0319: if (this .hints == null) {
0320: this .hints = new RenderingHints(hints);
0321: } else {
0322: this .hints.putAll(hints);
0323: }
0324: fireGraphicsNodeChangeCompleted();
0325: }
0326:
0327: /**
0328: * Sets the rendering hints of this node.
0329: *
0330: * @param newHints the new rendering hints of this node
0331: */
0332: public void setRenderingHints(RenderingHints newHints) {
0333: fireGraphicsNodeChangeStarted();
0334: hints = newHints;
0335: fireGraphicsNodeChangeCompleted();
0336: }
0337:
0338: /**
0339: * Returns the rendering hints of this node or null if any.
0340: */
0341: public RenderingHints getRenderingHints() {
0342: return hints;
0343: }
0344:
0345: /**
0346: * Sets the mask of this node.
0347: *
0348: * @param newMask the new mask of this node
0349: */
0350: public void setMask(Mask newMask) {
0351: if ((newMask == null) && (mask == null))
0352: return; // No change still no mask.
0353:
0354: fireGraphicsNodeChangeStarted();
0355: invalidateGeometryCache();
0356: mask = newMask;
0357: fireGraphicsNodeChangeCompleted();
0358: }
0359:
0360: /**
0361: * Returns the mask of this node or null if any.
0362: */
0363: public Mask getMask() {
0364: return mask;
0365: }
0366:
0367: /**
0368: * Sets the filter of this node.
0369: *
0370: * @param newFilter the new filter of this node
0371: */
0372: public void setFilter(Filter newFilter) {
0373: if ((newFilter == null) && (filter == null))
0374: return; // No change still no filter.
0375:
0376: fireGraphicsNodeChangeStarted();
0377: invalidateGeometryCache();
0378: filter = newFilter;
0379: fireGraphicsNodeChangeCompleted();
0380: }
0381:
0382: /**
0383: * Returns the filter of this node or null if any.
0384: */
0385: public Filter getFilter() {
0386: return filter;
0387: }
0388:
0389: /**
0390: * Returns the GraphicsNodeRable for this node. This
0391: * GraphicsNodeRable is the Renderable (Filter) before any of the
0392: * filter operations have been applied.
0393: */
0394: public Filter getGraphicsNodeRable(boolean createIfNeeded) {
0395: GraphicsNodeRable ret = null;
0396: if (graphicsNodeRable != null) {
0397: ret = (GraphicsNodeRable) graphicsNodeRable.get();
0398: if (ret != null)
0399: return ret;
0400: }
0401: if (createIfNeeded) {
0402: ret = new GraphicsNodeRable8Bit(this );
0403: graphicsNodeRable = new WeakReference(ret);
0404: }
0405: return ret;
0406: }
0407:
0408: /**
0409: * Returns the GraphicsNodeRable for this node. This
0410: * GraphicsNodeRable is the Renderable (Filter) after all of the
0411: * filter operations have been applied.
0412: */
0413: public Filter getEnableBackgroundGraphicsNodeRable(
0414: boolean createIfNeeded) {
0415: GraphicsNodeRable ret = null;
0416: if (enableBackgroundGraphicsNodeRable != null) {
0417: ret = (GraphicsNodeRable) enableBackgroundGraphicsNodeRable
0418: .get();
0419: if (ret != null)
0420: return ret;
0421: }
0422: if (createIfNeeded) {
0423: ret = new GraphicsNodeRable8Bit(this );
0424: ret.setUsePrimitivePaint(false);
0425: enableBackgroundGraphicsNodeRable = new WeakReference(ret);
0426: }
0427: return ret;
0428: }
0429:
0430: //
0431: // Drawing methods
0432: //
0433:
0434: /**
0435: * Paints this node.
0436: *
0437: * @param g2d the Graphics2D to use
0438: */
0439: public void paint(Graphics2D g2d) {
0440: if ((composite != null)
0441: && (composite instanceof AlphaComposite)) {
0442: AlphaComposite ac = (AlphaComposite) composite;
0443: if (ac.getAlpha() < 0.001)
0444: return; // No point in drawing
0445: }
0446: Rectangle2D bounds = getBounds();
0447: if (bounds == null)
0448: return;
0449:
0450: // Set up graphic context. It is important to setup the
0451: // transform first, because the clip is defined in this node's
0452: // user space.
0453: Composite defaultComposite = null;
0454: AffineTransform defaultTransform = null;
0455: RenderingHints defaultHints = null;
0456: Graphics2D baseG2d = null;
0457:
0458: if (clip != null) {
0459: baseG2d = g2d;
0460: g2d = (Graphics2D) g2d.create();
0461: if (hints != null)
0462: g2d.addRenderingHints(hints);
0463: if (transform != null)
0464: g2d.transform(transform);
0465: if (composite != null)
0466: g2d.setComposite(composite);
0467: g2d.clip(clip.getClipPath());
0468: } else {
0469: if (hints != null) {
0470: defaultHints = g2d.getRenderingHints();
0471: g2d.addRenderingHints(hints);
0472: }
0473: if (transform != null) {
0474: defaultTransform = g2d.getTransform();
0475: g2d.transform(transform);
0476: }
0477: if (composite != null) {
0478: defaultComposite = g2d.getComposite();
0479: g2d.setComposite(composite);
0480: }
0481: }
0482:
0483: Shape curClip = g2d.getClip();
0484: g2d.setRenderingHint(RenderingHintsKeyExt.KEY_AREA_OF_INTEREST,
0485: curClip);
0486:
0487: // Check if any painting is needed at all. Get the clip (in user space)
0488: // and see if it intersects with this node's bounds (in user space).
0489: boolean paintNeeded = true;
0490: Shape g2dClip = curClip; //g2d.getClip();
0491: if (g2dClip != null) {
0492: Rectangle2D cb = g2dClip.getBounds2D();
0493: if (!bounds.intersects(cb.getX(), cb.getY(), cb.getWidth(),
0494: cb.getHeight()))
0495: paintNeeded = false;
0496: }
0497:
0498: // Only paint if needed.
0499: if (paintNeeded) {
0500: boolean antialiasedClip = false;
0501: if ((clip != null) && clip.getUseAntialiasedClip()) {
0502: antialiasedClip = isAntialiasedClip(g2d.getTransform(),
0503: g2d.getRenderingHints(), clip.getClipPath());
0504: }
0505:
0506: boolean useOffscreen = isOffscreenBufferNeeded();
0507:
0508: useOffscreen |= antialiasedClip;
0509:
0510: if (!useOffscreen) {
0511: // Render on this canvas.
0512: primitivePaint(g2d);
0513: } else {
0514: Filter filteredImage = null;
0515:
0516: if (filter == null) {
0517: filteredImage = getGraphicsNodeRable(true);
0518: } else {
0519: // traceFilter(filter, "=====>> ");
0520: filteredImage = filter;
0521: }
0522:
0523: if (mask != null) {
0524: if (mask.getSource() != filteredImage) {
0525: mask.setSource(filteredImage);
0526: }
0527: filteredImage = mask;
0528: }
0529:
0530: if (clip != null && antialiasedClip) {
0531: if (clip.getSource() != filteredImage) {
0532: clip.setSource(filteredImage);
0533: }
0534: filteredImage = clip;
0535: }
0536:
0537: baseG2d = g2d;
0538: // Only muck with the clip on a 'child'
0539: // graphics 2D otherwise when we restore the
0540: // clip it might 'wander' by a pixel.
0541: g2d = (Graphics2D) g2d.create();
0542:
0543: if (antialiasedClip) {
0544: // Remove hard edged clip
0545: g2d.setClip(null);
0546: }
0547:
0548: Rectangle2D filterBounds = filteredImage.getBounds2D();
0549: g2d.clip(filterBounds);
0550:
0551: org.apache.batik.ext.awt.image.GraphicsUtil.drawImage(
0552: g2d, filteredImage);
0553:
0554: g2d.dispose();
0555: g2d = baseG2d;
0556: baseG2d = null;// Don't leave null we need g2d restored...
0557: }
0558: }
0559:
0560: // Restore default rendering attributes
0561: if (baseG2d != null) {
0562: g2d.dispose();
0563: } else {
0564: if (defaultHints != null)
0565: g2d.setRenderingHints(defaultHints);
0566: if (defaultTransform != null)
0567: g2d.setTransform(defaultTransform);
0568: if (defaultComposite != null) {
0569: g2d.setComposite(defaultComposite);
0570: }
0571: }
0572: }
0573:
0574: /**
0575: * DEBUG: Trace filter chain
0576: */
0577: private void traceFilter(Filter filter, String prefix) {
0578: System.out.println(prefix + filter.getClass().getName());
0579: System.out.println(prefix + filter.getBounds2D());
0580: List sources = filter.getSources();
0581: int nSources = sources != null ? sources.size() : 0;
0582: prefix += "\t";
0583: for (int i = 0; i < nSources; i++) {
0584: Filter source = (Filter) sources.get(i);
0585: traceFilter(source, prefix);
0586: }
0587:
0588: System.out.flush();
0589: }
0590:
0591: /**
0592: * Returns true of an offscreen buffer is needed to render this node, false
0593: * otherwise.
0594: */
0595: protected boolean isOffscreenBufferNeeded() {
0596: return ((filter != null) || (mask != null) || (composite != null && !AlphaComposite.SrcOver
0597: .equals(composite)));
0598: }
0599:
0600: /**
0601: * Returns true if there is a clip and it should be antialiased
0602: */
0603: protected boolean isAntialiasedClip(AffineTransform usr2dev,
0604: RenderingHints hints, Shape clip) {
0605: // Antialias clip if:
0606: // + The KEY_CLIP_ANTIALIASING is true.
0607: // *and*
0608: // + clip is not null
0609: // *and*
0610: // + clip is not a rectangle in device space.
0611: //
0612: // This leaves out the case where the node clip is a
0613: // rectangle and the current clip (i.e., the intersection
0614: // of the current Graphics2D's clip and this node's clip)
0615: // is not a rectangle.
0616: //
0617: if (clip == null)
0618: return false;
0619:
0620: Object val = hints.get(RenderingHintsKeyExt.KEY_TRANSCODING);
0621: if ((val == RenderingHintsKeyExt.VALUE_TRANSCODING_PRINTING)
0622: || (val == RenderingHintsKeyExt.VALUE_TRANSCODING_VECTOR))
0623: return false;
0624:
0625: if (!(clip instanceof Rectangle2D && usr2dev.getShearX() == 0 && usr2dev
0626: .getShearY() == 0))
0627: return true;
0628:
0629: return false;
0630: }
0631:
0632: //
0633: // Event support methods
0634: //
0635: public void fireGraphicsNodeChangeStarted(GraphicsNode changeSrc) {
0636: if (changeStartedEvent == null)
0637: changeStartedEvent = new GraphicsNodeChangeEvent(this ,
0638: GraphicsNodeChangeEvent.CHANGE_STARTED);
0639: changeStartedEvent.setChangeSrc(changeSrc);
0640: fireGraphicsNodeChangeStarted(changeStartedEvent);
0641: changeStartedEvent.setChangeSrc(null);
0642: }
0643:
0644: //
0645: // Event support methods
0646: //
0647: public void fireGraphicsNodeChangeStarted() {
0648: if (changeStartedEvent == null)
0649: changeStartedEvent = new GraphicsNodeChangeEvent(this ,
0650: GraphicsNodeChangeEvent.CHANGE_STARTED);
0651: else {
0652: changeStartedEvent.setChangeSrc(null);
0653: }
0654: fireGraphicsNodeChangeStarted(changeStartedEvent);
0655: }
0656:
0657: public void fireGraphicsNodeChangeStarted(
0658: GraphicsNodeChangeEvent changeStartedEvent) {
0659: // If we had per node listeners we would fire them here...
0660:
0661: RootGraphicsNode rootGN = getRoot();
0662: if (rootGN == null)
0663: return;
0664:
0665: List l = rootGN.getTreeGraphicsNodeChangeListeners();
0666: if (l == null)
0667: return;
0668:
0669: Iterator i = l.iterator();
0670: GraphicsNodeChangeListener gncl;
0671: while (i.hasNext()) {
0672: gncl = (GraphicsNodeChangeListener) i.next();
0673: gncl.changeStarted(changeStartedEvent);
0674: }
0675: }
0676:
0677: public void fireGraphicsNodeChangeCompleted() {
0678: if (changeCompletedEvent == null) {
0679: changeCompletedEvent = new GraphicsNodeChangeEvent(this ,
0680: GraphicsNodeChangeEvent.CHANGE_COMPLETED);
0681: }
0682:
0683: // If we had per node listeners we would fire them here...
0684:
0685: RootGraphicsNode rootGN = getRoot();
0686: if (rootGN == null)
0687: return;
0688:
0689: List l = rootGN.getTreeGraphicsNodeChangeListeners();
0690: if (l == null)
0691: return;
0692:
0693: Iterator i = l.iterator();
0694: GraphicsNodeChangeListener gncl;
0695: while (i.hasNext()) {
0696: gncl = (GraphicsNodeChangeListener) i.next();
0697: gncl.changeCompleted(changeCompletedEvent);
0698: }
0699: }
0700:
0701: //
0702: // Structural methods
0703: //
0704:
0705: /**
0706: * Returns the parent of this node or null if any.
0707: */
0708: public CompositeGraphicsNode getParent() {
0709: return parent;
0710: }
0711:
0712: /**
0713: * Returns the root of the GVT tree or null if the node is not part of a GVT
0714: * tree.
0715: */
0716: public RootGraphicsNode getRoot() {
0717: return root;
0718: }
0719:
0720: /**
0721: * Sets the root node of this graphics node.
0722: *
0723: * @param newRoot the new root node of this node
0724: */
0725: protected void setRoot(RootGraphicsNode newRoot) {
0726: this .root = newRoot;
0727: }
0728:
0729: /**
0730: * Sets the parent node of this graphics node.
0731: *
0732: * @param newParent the new parent node of this node
0733: */
0734: protected void setParent(CompositeGraphicsNode newParent) {
0735: this .parent = newParent;
0736: }
0737:
0738: //
0739: // Geometric methods
0740: //
0741:
0742: /**
0743: * Invalidates the cached geometric bounds. This method is called
0744: * each time an attribute that affects the bounds of this node
0745: * changed.
0746: */
0747: protected void invalidateGeometryCache() {
0748: // If our bounds are invalid then our parents bounds
0749: // must be invalid also. So just return.
0750: //if (bounds == null) return;
0751:
0752: if (parent != null) {
0753: parent.invalidateGeometryCache();
0754: }
0755: bounds = null;
0756: }
0757:
0758: /**
0759: * Returns the bounds of this node in user space. This includes primitive
0760: * paint, filtering, clipping and masking.
0761: */
0762: public Rectangle2D getBounds() {
0763: // Get the primitive bounds
0764: // Rectangle2D bounds = null;
0765: if (bounds == null) {
0766: // The painted region, before cliping, masking and compositing is
0767: // either the area painted by the primitive paint or the area
0768: // painted by the filter.
0769: if (filter == null) {
0770: bounds = getPrimitiveBounds();
0771: } else {
0772: bounds = filter.getBounds2D();
0773: }
0774: // Factor in the clipping area, if any
0775: if (bounds != null) {
0776: if (clip != null) {
0777: Rectangle2D clipR = clip.getClipPath()
0778: .getBounds2D();
0779: if (clipR.intersects(bounds))
0780: Rectangle2D.intersect(bounds, clipR, bounds);
0781: }
0782: // Factor in the mask, if any
0783: if (mask != null) {
0784: Rectangle2D maskR = mask.getBounds2D();
0785: if (maskR.intersects(bounds))
0786: Rectangle2D.intersect(bounds, maskR, bounds);
0787: }
0788: }
0789:
0790: bounds = normalizeRectangle(bounds);
0791:
0792: // Check If we should halt early.
0793: if (HaltingThread.hasBeenHalted()) {
0794: // The Thread has been 'halted'.
0795: // Invalidate any cached values and proceed.
0796: invalidateGeometryCache();
0797: }
0798: }
0799:
0800: return bounds;
0801: }
0802:
0803: /**
0804: * Returns the bounds of this node after applying the input transform
0805: * (if any), concatenated with this node's transform (if any).
0806: *
0807: * @param txf the affine transform with which this node's transform should
0808: * be concatenated. Should not be null.
0809: */
0810: public Rectangle2D getTransformedBounds(AffineTransform txf) {
0811: AffineTransform t = txf;
0812: if (transform != null) {
0813: t = new AffineTransform(txf);
0814: t.concatenate(transform);
0815: }
0816:
0817: // The painted region, before cliping, masking and compositing
0818: // is either the area painted by the primitive paint or the
0819: // area painted by the filter.
0820: Rectangle2D tBounds = null;
0821: if (filter == null) {
0822: // Use txf, not t
0823: tBounds = getTransformedPrimitiveBounds(txf);
0824: } else {
0825: tBounds = t.createTransformedShape(filter.getBounds2D())
0826: .getBounds2D();
0827: }
0828: // Factor in the clipping area, if any
0829: if (tBounds != null) {
0830: if (clip != null) {
0831: Rectangle2D.intersect(tBounds, t
0832: .createTransformedShape(clip.getClipPath())
0833: .getBounds2D(), tBounds);
0834: }
0835:
0836: // Factor in the mask, if any
0837: if (mask != null) {
0838: Rectangle2D.intersect(tBounds, t
0839: .createTransformedShape(mask.getBounds2D())
0840: .getBounds2D(), tBounds);
0841: }
0842: }
0843:
0844: return tBounds;
0845: }
0846:
0847: /**
0848: * Returns the bounds of this node's primitivePaint after applying
0849: * the input transform (if any), concatenated with this node's
0850: * transform (if any).
0851: *
0852: * @param txf the affine transform with which this node's transform should
0853: * be concatenated. Should not be null. */
0854: public Rectangle2D getTransformedPrimitiveBounds(AffineTransform txf) {
0855: Rectangle2D tpBounds = getPrimitiveBounds();
0856: if (tpBounds == null) {
0857: return null;
0858: }
0859: AffineTransform t = txf;
0860: if (transform != null) {
0861: t = new AffineTransform(txf);
0862: t.concatenate(transform);
0863: }
0864:
0865: return t.createTransformedShape(tpBounds).getBounds2D();
0866: }
0867:
0868: /**
0869: * Returns the bounds of the area covered by this node, without
0870: * taking any of its rendering attribute into accoun. That is,
0871: * exclusive of any clipping, masking, filtering or stroking, for
0872: * example. The returned value is transformed by the concatenation
0873: * of the input transform and this node's transform.
0874: *
0875: * @param txf the affine transform with which this node's transform should
0876: * be concatenated. Should not be null.
0877: */
0878: public Rectangle2D getTransformedGeometryBounds(AffineTransform txf) {
0879: Rectangle2D tpBounds = getGeometryBounds();
0880: if (tpBounds == null) {
0881: return null;
0882: }
0883: AffineTransform t = txf;
0884: if (transform != null) {
0885: t = new AffineTransform(txf);
0886: t.concatenate(transform);
0887: }
0888:
0889: return t.createTransformedShape(tpBounds).getBounds2D();
0890: }
0891:
0892: /**
0893: * Returns the bounds of the sensitive area covered by this node,
0894: * This includes the stroked area but does not include the effects
0895: * of clipping, masking or filtering. The returned value is
0896: * transformed by the concatenation of the input transform and
0897: * this node's transform.
0898: *
0899: * @param txf the affine transform with which this node's
0900: * transform should be concatenated. Should not be null.
0901: */
0902: public Rectangle2D getTransformedSensitiveBounds(AffineTransform txf) {
0903: Rectangle2D sBounds = getSensitiveBounds();
0904: if (sBounds == null) {
0905: return null;
0906: }
0907: AffineTransform t = txf;
0908: if (transform != null) {
0909: t = new AffineTransform(txf);
0910: t.concatenate(transform);
0911: }
0912:
0913: return t.createTransformedShape(sBounds).getBounds2D();
0914: }
0915:
0916: /**
0917: * Returns true if the specified Point2D is inside the boundary of this
0918: * node, false otherwise.
0919: *
0920: * @param p the specified Point2D in the user space
0921: */
0922: public boolean contains(Point2D p) {
0923: Rectangle2D b = getSensitiveBounds();
0924: if (b == null || !b.contains(p)) {
0925: return false;
0926: }
0927: switch (pointerEventType) {
0928: case VISIBLE_PAINTED:
0929: case VISIBLE_FILL:
0930: case VISIBLE_STROKE:
0931: case VISIBLE:
0932: return isVisible;
0933: case PAINTED:
0934: case FILL:
0935: case STROKE:
0936: case ALL:
0937: return true;
0938: case NONE:
0939: default:
0940: return false;
0941: }
0942: }
0943:
0944: /**
0945: * Returns true if the interior of this node intersects the interior of a
0946: * specified Rectangle2D, false otherwise.
0947: *
0948: * @param r the specified Rectangle2D in the user node space
0949: */
0950: public boolean intersects(Rectangle2D r) {
0951: Rectangle2D b = getBounds();
0952: if (b == null)
0953: return false;
0954:
0955: return b.intersects(r);
0956: }
0957:
0958: /**
0959: * Returns the GraphicsNode containing point p if this node or one of its
0960: * children is sensitive to mouse events at p.
0961: *
0962: * @param p the specified Point2D in the user space
0963: */
0964: public GraphicsNode nodeHitAt(Point2D p) {
0965: return (contains(p) ? this : null);
0966: }
0967:
0968: static double EPSILON = 1e-6;
0969:
0970: /**
0971: * This method makes sure that neither the width nor height of the
0972: * rectangle is zero. But it tries to make them very small
0973: * relatively speaking.
0974: */
0975: protected Rectangle2D normalizeRectangle(Rectangle2D bounds) {
0976: if (bounds == null)
0977: return null;
0978:
0979: if ((bounds.getWidth() < EPSILON)) {
0980: if (bounds.getHeight() < EPSILON) {
0981: AffineTransform gt = getGlobalTransform();
0982: double det = Math.sqrt(gt.getDeterminant());
0983: return new Rectangle2D.Double(bounds.getX(), bounds
0984: .getY(), EPSILON / det, EPSILON / det);
0985: } else {
0986: double tmpW = bounds.getHeight() * EPSILON;
0987: if (tmpW < bounds.getWidth())
0988: tmpW = bounds.getWidth();
0989: return new Rectangle2D.Double(bounds.getX(), bounds
0990: .getY(), tmpW, bounds.getHeight());
0991: }
0992: } else if (bounds.getHeight() < EPSILON) {
0993: double tmpH = bounds.getWidth() * EPSILON;
0994: if (tmpH < bounds.getHeight())
0995: tmpH = bounds.getHeight();
0996: return new Rectangle2D.Double(bounds.getX(), bounds.getY(),
0997: bounds.getWidth(), tmpH);
0998: }
0999: return bounds;
1000: }
1001:
1002: }
|