001: package com.xoetrope.svg;
002:
003: import com.kitfox.svg.SVGDiagram;
004: import com.kitfox.svg.SVGException;
005: import com.kitfox.svg.SVGRoot;
006: import com.kitfox.svg.animation.AnimationElement;
007: import com.xoetrope.carousel.build.BuildProperties;
008: import java.awt.AWTException;
009: import java.awt.AlphaComposite;
010: import java.awt.BasicStroke;
011: import java.awt.Color;
012: import java.awt.Composite;
013: import java.awt.Graphics;
014: import java.awt.Graphics2D;
015: import java.awt.GraphicsConfiguration;
016: import java.awt.GraphicsDevice;
017: import java.awt.Image;
018: import java.awt.Point;
019: import java.awt.Rectangle;
020: import java.awt.RenderingHints;
021: import java.awt.Robot;
022: import java.awt.Shape;
023: import java.awt.Stroke;
024: import java.awt.geom.AffineTransform;
025: import java.awt.geom.Arc2D;
026: import java.awt.geom.Ellipse2D;
027: import java.awt.geom.GeneralPath;
028: import java.awt.image.BufferedImage;
029: import java.io.File;
030: import java.lang.ref.WeakReference;
031: import java.util.HashMap;
032: import java.util.Hashtable;
033: import java.util.WeakHashMap;
034: import javax.imageio.ImageIO;
035: import javax.swing.ImageIcon;
036: import javax.swing.JComponent;
037: import javax.swing.SwingUtilities;
038: import net.xoetrope.swing.XImage;
039:
040: /**
041: *
042: *
043: * <p> Copyright (c) Xoetrope Ltd., 2001-2006, This software is licensed under
044: * the GNU Public License (GPL), please see license.txt for more details. If
045: * you make commercial use of this software you must purchase a commercial
046: * license from Xoetrope.</p>
047: * <p> $Revision: 1.2 $</p>
048: */
049: public class XSvgMagnifyingGlass extends JComponent implements Runnable {
050: private Graphics2D g2d_1, g2d_2, buffer;
051: private SVGDiagram diagram;
052: private Point zoomLocation, mouseLocation, centrePoint, bigLens;
053: private float scaleFactor, scaling;
054: private Image smallLensImage, bigLensImage;
055: private boolean isOpaque, isRotational, quality;
056: private SVGRoot root;
057: private int[] attr, saveAttr;
058: private Integer[] keys;
059: private double tileSize, width, height, previousX, previousY,
060: previousWidth, previousHeight;
061: private int fadeDegree, centre;
062: private XSvgImageMap imageMap;
063: private BasicStroke stroke;
064: private Rectangle componentRect;
065: private float alpha, animationSpeed;
066: private boolean renderMode;
067: private int animationMode, left, right;
068: public static final int FADE_IN = 0;
069: public static final int FADE_OUT = 1;
070: private BufferedImage leftImage, centreImage, rightImage;
071: private boolean startThread, first, stopRenderer, stopAnimator;
072: private WeakHashMap referenceMap;
073: private GeneralPath arcPath;
074: private XRenderingSemaphore semaphore;
075:
076: /** Creates a new instance of XSvgMagnifyingGlass */
077: public XSvgMagnifyingGlass() {
078: try {
079: // Read in images used to render magnifying glass
080: smallLensImage = new ImageIcon(getClass().getResource(
081: "images/small_lens.png")).getImage();
082: bigLensImage = new ImageIcon(getClass().getResource(
083: "images/big_lens.png")).getImage();
084: } catch (Exception ex) {
085: if (BuildProperties.DEBUG)
086: ex.printStackTrace();
087: }
088:
089: scaleFactor = 2.0F;
090: isOpaque = true;
091: stroke = new BasicStroke(8.0F);
092: renderMode = true;
093: setDoubleBuffered(true);
094: setOpaque(true);
095: first = true;
096: startThread = true;
097: animationMode = -1;
098: centre = 0;
099: tileSize = 400.0;
100:
101: Arc2D.Double lowerArc = new Arc2D.Double(-12, 12, 10, 5, 0, 90,
102: Arc2D.OPEN);
103: Arc2D.Double upperArc = new Arc2D.Double(12, -12, 5, 10, 180,
104: 90, Arc2D.OPEN);
105: arcPath = new GeneralPath();
106: arcPath.append(lowerArc, false);
107: arcPath.append(upperArc, false);
108:
109: alpha = 1.0F;
110: }
111:
112: /**
113: * Set the SVGDiagram instance that the magnifier will magnify.
114: * @param diagram the SVGDiagram instance to be used.
115: */
116: public void setDiagram(SVGDiagram diagram) {
117: this .diagram = diagram;
118:
119: if (referenceMap != null) {
120: referenceMap.clear();
121: first = true;
122: }
123: }
124:
125: /**
126: * Set the XSvgImageMap instance that the magnifier will be displayed upon.
127: * @param diagram the XSvgImageMap instance to be used.
128: */
129: public void setImageMap(XSvgImageMap imageMap) {
130: this .imageMap = imageMap;
131: semaphore = imageMap.getSemaphore();
132: }
133:
134: /**
135: * Used to set the scale factor, which defines by how much the selected area of the SVG image is zoomed.
136: * @param scaleFactor the float specifying the scale factor.
137: */
138: public void setScaleFactor(float scaleFactor) {
139: this .scaleFactor = scaleFactor;
140: }
141:
142: /**
143: * Returns the current scale factor.
144: * @return <CODE>float</CODE> specifying the current scale factor.
145: */
146: public float getScaleFactor() {
147: return scaleFactor;
148: }
149:
150: /**
151: * Used to set which part of the SVG image will be zoomed.
152: * @param p the Point object specifying the area to be zoomed.
153: * @param mouseLocation the location of the mousePointer
154: */
155: public void setZoomPoint(Point p, Point mouseLocation) {
156: if (p != null) {
157: zoomLocation = p;
158: this .mouseLocation = mouseLocation;
159: repaint();
160: }
161: }
162:
163: /**
164: * Sets whether the lens is rotated towards the centre.
165: * @param isRotational a boolean specifying if the lens is to be rotated or not.
166: */
167: public void setRotational(boolean isRotational) {
168: this .isRotational = isRotational;
169: }
170:
171: /**
172: * Sets quality level of the lens image.
173: * @param isRotational a boolean specifying if the rendering quality is high or low.
174: */
175: public void setQuality(boolean quality) {
176: this .quality = quality;
177: }
178:
179: /**
180: * Sets whether the magnifying glass is opaque or not.
181: * @param isOpaque a boolean specifying whether the magnifying glass is opaque or not
182: */
183: public void setOpaque(boolean isOpaque) {
184: this .isOpaque = isOpaque;
185: }
186:
187: public void stopThreads() {
188: stopRenderer = true;
189: stopAnimator = true;
190: startThread = true;
191: first = true;
192: }
193:
194: private void createKeys(int size) {
195: keys = new Integer[size];
196:
197: for (int i = 0; i < keys.length; i++) {
198: keys[i] = new Integer(i);
199: }
200: }
201:
202: private void renderTiles() {
203: if (startThread == true) {
204: startThread = false;
205: stopRenderer = false;
206: Thread t = new Thread(new Runnable() {
207: public void run() {
208: referenceMap = new WeakHashMap();
209:
210: while (true) {
211: if (stopRenderer) {
212: return;
213: }
214:
215: semaphore.acquire();
216:
217: SVGRoot root = diagram.getRoot();
218: double[] attr = root.getPresAbsolute("viewBox")
219: .getDoubleList();
220:
221: if ((zoomLocation != null)
222: && (((previousX != attr[0]) || (previousY != attr[1])) || (centre != ((int) (zoomLocation
223: .getX()
224: / (tileSize / scaleFactor) + 1))))) {
225:
226: int previousCentre = centre;
227: boolean doLeft = false, doCentre = false, doRight = false;
228:
229: if ((previousWidth != attr[2])
230: || (previousHeight != attr[3])) {
231: referenceMap.clear();
232: previousWidth = attr[2];
233: previousHeight = attr[3];
234: first = true;
235: }
236:
237: if ((previousX != attr[0])
238: || (previousY != attr[1])) {
239: referenceMap.clear();
240: previousX = attr[0];
241: previousY = attr[1];
242: first = true;
243: }
244:
245: diagram.setIgnoringClipHeuristic(true);
246:
247: centre = (int) ((zoomLocation.getX() / (tileSize / scaleFactor)) + 1);
248: left = centre - 1;
249: right = centre + 1;
250:
251: try {
252: if (first == true) {
253: first = false;
254:
255: doLeft = true;
256: doCentre = true;
257: doRight = true;
258:
259: int keyAmt = (int) ((imageMap
260: .getWidth() / (tileSize / scaleFactor)) + 3.0);
261: createKeys(keyAmt);
262: } else if (centre == (previousCentre - 1)) {
263: if (!referenceMap
264: .containsKey(keys[previousCentre + 1])) {
265: WeakReference sr = new WeakReference(
266: rightImage);
267: referenceMap
268: .put(
269: keys[previousCentre + 1],
270: sr);
271: }
272: rightImage = centreImage;
273: centreImage = leftImage;
274:
275: if (referenceMap
276: .containsKey(keys[centre - 1])) {
277: WeakReference sr = (WeakReference) referenceMap
278: .get(keys[centre - 1]);
279: leftImage = (BufferedImage) sr
280: .get();
281:
282: if (leftImage == null) {
283: referenceMap
284: .remove(keys[centre - 1]);
285: doLeft = true;
286: }
287: } else {
288: doLeft = true;
289: }
290: } else if (centre == (previousCentre + 1)) {
291: if (!referenceMap
292: .containsKey(keys[previousCentre - 1])) {
293: WeakReference sr = new WeakReference(
294: leftImage);
295: referenceMap
296: .put(
297: keys[previousCentre - 1],
298: sr);
299: }
300: leftImage = centreImage;
301: centreImage = rightImage;
302:
303: if (referenceMap
304: .containsKey(keys[centre + 1])) {
305: WeakReference sr = (WeakReference) referenceMap
306: .get(keys[centre + 1]);
307: rightImage = (BufferedImage) sr
308: .get();
309:
310: if (rightImage == null) {
311: referenceMap
312: .remove(keys[centre + 1]);
313: doRight = true;
314: }
315: } else {
316: doRight = true;
317: }
318: } else {
319: doLeft = true;
320: doCentre = true;
321: doRight = true;
322: }
323:
324: if (doCentre) {
325: centreImage = new BufferedImage(
326: (int) (tileSize),
327: ((int) (imageMap
328: .getHeight() * scaleFactor)),
329: BufferedImage.TYPE_INT_ARGB);
330: Graphics2D centreBuffer = centreImage
331: .createGraphics();
332: centreBuffer
333: .setRenderingHint(
334: RenderingHints.KEY_ANTIALIASING,
335: RenderingHints.VALUE_ANTIALIAS_ON);
336: centreBuffer.scale(scaleFactor,
337: scaleFactor);
338: centreBuffer
339: .translate(
340: -((int) (tileSize / scaleFactor) * (centre - 1)),
341: 0);
342: root.render(centreBuffer);
343: centreBuffer.dispose();
344:
345: SwingUtilities
346: .invokeLater(new Runnable() {
347: public void run() {
348: repaint();
349: }
350: });
351: }
352:
353: if (doLeft) {
354: leftImage = new BufferedImage(
355: (int) (tileSize),
356: ((int) (imageMap
357: .getHeight() * scaleFactor)),
358: BufferedImage.TYPE_INT_ARGB);
359: Graphics2D leftBuffer = leftImage
360: .createGraphics();
361: leftBuffer
362: .setRenderingHint(
363: RenderingHints.KEY_ANTIALIASING,
364: RenderingHints.VALUE_ANTIALIAS_ON);
365: leftBuffer.scale(scaleFactor,
366: scaleFactor);
367: leftBuffer
368: .translate(
369: -((int) (tileSize / scaleFactor) * (left - 1)),
370: 0);
371: root.render(leftBuffer);
372: leftBuffer.dispose();
373: }
374:
375: if (doRight) {
376: rightImage = new BufferedImage(
377: (int) (tileSize),
378: ((int) (imageMap
379: .getHeight() * scaleFactor)),
380: BufferedImage.TYPE_INT_ARGB);
381: Graphics2D rightBuffer = rightImage
382: .createGraphics();
383: rightBuffer
384: .setRenderingHint(
385: RenderingHints.KEY_ANTIALIASING,
386: RenderingHints.VALUE_ANTIALIAS_ON);
387: rightBuffer.scale(scaleFactor,
388: scaleFactor);
389: rightBuffer
390: .translate(
391: -((int) (tileSize / scaleFactor) * (right - 1)),
392: 0);
393: root.render(rightBuffer);
394: rightBuffer.dispose();
395: }
396: } catch (Exception ex) {
397: if (BuildProperties.DEBUG)
398: ex.printStackTrace();
399: }
400:
401: SwingUtilities.invokeLater(new Runnable() {
402: public void run() {
403: repaint();
404: }
405: });
406: } else {
407: try {
408: Thread.currentThread().sleep(200);
409: } catch (Exception ex) {
410: if (BuildProperties.DEBUG)
411: ex.printStackTrace();
412: }
413: }
414:
415: semaphore.release();
416: }
417: }
418: });
419: t.start();
420: }
421: }
422:
423: /*
424: * Calls the JComponent's paint method and scales the area that has been selected using setZoomPoint.
425: * The scaled area is then applied to the magnifying glass.
426: * @param g the delegate Graphics object passed to protect the rest of the paint code.
427: */
428: protected void paintComponent(Graphics g) {
429: if (diagram != null) {
430: renderTiles();
431:
432: if ((zoomLocation != null) && (mouseLocation != null)) {
433: double x = mouseLocation.getX();
434: double y = mouseLocation.getY();
435:
436: g2d_1 = (Graphics2D) g.create();
437: dissolve(g2d_1);
438:
439: if (quality == true)
440: g2d_1.setRenderingHint(
441: RenderingHints.KEY_RENDERING,
442: RenderingHints.VALUE_RENDER_QUALITY);
443:
444: g2d_1.translate(x, y);
445: double angle = 0.0;
446: int panelWidth = imageMap.getWidth();
447: int panelHeight = imageMap.getHeight();
448:
449: if (isRotational) {
450: if ((zoomLocation.getX() == panelWidth / 2)
451: || (zoomLocation.getY() == panelHeight / 2))
452: centrePoint = new Point((panelWidth / 2) + 1,
453: (panelHeight / 2) - 1);
454: else
455: centrePoint = new Point((panelWidth / 2),
456: (panelHeight / 2));
457:
458: bigLens = new Point(zoomLocation.x - 80,
459: zoomLocation.y - 80);
460:
461: angle = angle(bigLens, zoomLocation);
462: double angle2 = angle(centrePoint, zoomLocation);
463:
464: angle = angle2 - angle;
465: }
466:
467: g2d_1.rotate(angle);
468: g2d_1.translate(-(x + (zoomLocation.getX() - x) + 80),
469: -(y + (zoomLocation.getY() - y) + 80));
470:
471: if (isOpaque == true) {
472: g2d_1.setColor(Color.white);
473: Ellipse2D.Double backGround = new Ellipse2D.Double(
474: (zoomLocation.getX() - 75.0), (zoomLocation
475: .getY() - 75.0), 150, 150);
476: g2d_1.fill(backGround);
477: }
478:
479: Ellipse2D.Double magnifier = new Ellipse2D.Double(
480: (zoomLocation.getX() - 75.0), (zoomLocation
481: .getY() - 75.0), 150, 150);
482: g2d_1.setClip(magnifier);
483:
484: g2d_1.rotate(-angle, magnifier.getCenterX(), magnifier
485: .getCenterY());
486:
487: // render magnified image using the Buffer------------------------------------------------------------------------------
488: try {
489: double tileAmt = (((double) panelWidth) / (tileSize / scaleFactor));
490: double mouseShift = (((double) panelWidth)
491: / tileAmt * ((double) centre - 1));
492: double xTransform = -(((zoomLocation.getX() - mouseShift) * (scaleFactor - 1.0)) - mouseShift);
493: double yTransform = -(((panelHeight * scaleFactor) - panelHeight) * (zoomLocation
494: .getY() / panelHeight));
495:
496: g2d_1.drawImage(leftImage, null,
497: (int) (xTransform - tileSize),
498: (int) yTransform);
499: g2d_1.drawImage(centreImage, null,
500: (int) xTransform, (int) yTransform);
501: g2d_1.drawImage(rightImage, null,
502: (int) (xTransform + tileSize),
503: (int) yTransform);
504: } catch (Exception ex) {
505: if (BuildProperties.DEBUG)
506: ex.printStackTrace();
507: }
508:
509: g2d_2 = (Graphics2D) g.create();
510: dissolve(g2d_2);
511:
512: g2d_2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
513: RenderingHints.VALUE_ANTIALIAS_ON);
514:
515: if (quality == true)
516: g2d_2.setRenderingHint(
517: RenderingHints.KEY_RENDERING,
518: RenderingHints.VALUE_RENDER_QUALITY);
519:
520: g2d_2.setColor(new Color(0, 0, 255, 120));
521:
522: g2d_2.translate((double) x, (double) y);
523: // draw lines in small lens
524: g2d_2.drawImage(smallLensImage, -50, -50, null);
525: g2d_2.drawLine(-32, 0, 32, 0);
526: g2d_2.drawLine(0, -32, 0, 32);
527:
528: g2d_2.translate(-32, -32);
529: g2d_2.setColor(Color.black);
530: g2d_2.setStroke((Stroke) stroke);
531: Ellipse2D.Double smallLens = new Ellipse2D.Double(0, 0,
532: 64, 64);
533: g2d_2.draw(smallLens);
534:
535: g2d_2.translate(32, 32);
536: g2d_2.setColor(new Color(0, 0, 255, 120));
537: g2d_2.setStroke((Stroke) new BasicStroke(1.0F));
538:
539: drawLens(g2d_2, angle);
540: g2d_2.translate((double) -x, (double) -y);
541:
542: g2d_2.dispose();
543: g2d_1.dispose();
544: }
545: }
546: }
547:
548: private void drawLens(Graphics2D g, double rotation) {
549: g.rotate(rotation);
550: // draw lines in big lens
551: g.translate(-80, -80);
552: g.rotate(-rotation);
553: g.drawImage(bigLensImage, -100, -100, null);
554: g.drawLine(-75, 0, 75, 0);
555: g.drawLine(0, -75, 0, 75);
556: g.rotate(rotation);
557:
558: g.setColor(Color.black);
559: g.setStroke((Stroke) stroke);
560:
561: g.translate(52, 52);
562: g.draw(arcPath);
563:
564: g.translate(-127, -127);
565: Ellipse2D.Double bigLens = new Ellipse2D.Double(0, 0, 150, 150);
566: g.draw(bigLens);
567: }
568:
569: /**
570: * Returns the angle, in radians, of the lens to the center of the panel.
571: * @param a <code>Point</code> object specifying the location of the lens.
572: * @param b <code>Point</code> object specifying the location of the center of the panel.
573: */
574: private double angle(Point a, Point b) {
575: double dx = b.getX() - a.getX();
576: double dy = b.getY() - a.getY();
577: double angle = 0.0;
578:
579: if (dx == 0.0) {
580: if (dy == 0.0)
581: angle = 0.0;
582: else if (dy > 0.0)
583: angle = Math.PI / 2.0;
584: else
585: angle = (Math.PI * 3.0) / 2.0;
586: } else if (dy == 0.0) {
587: if (dx > 0.0)
588: angle = 0.0;
589: else
590: angle = Math.PI;
591: } else {
592: if (dx < 0.0)
593: angle = Math.atan(dy / dx) + Math.PI;
594: else if (dy < 0.0)
595: angle = Math.atan(dy / dx) + (2 * Math.PI);
596: else
597: angle = Math.atan(dy / dx);
598: }
599:
600: return Math.toRadians((angle * 180) / Math.PI);
601: }
602:
603: /**
604: * Applies an AlphaComposite to a Graphics2D instance to achieve a transparency effect.
605: * g2d the <code>Graphics2D</code> instance the AlphaComposite is being applied to.
606: */
607: private void dissolve(Graphics2D g2d) {
608: Composite composite = g2d.getComposite();
609: Composite fade = AlphaComposite.getInstance(
610: AlphaComposite.SRC_OVER, alpha);
611: g2d.setComposite(fade);
612: }
613:
614: /**
615: * Used to run the fading-in and fading-out animations.
616: */
617: public void run() {
618: stopAnimator = false;
619: alpha = 0.0F;
620:
621: while (true) {
622: try {
623: if (stopAnimator) {
624: break;
625: }
626:
627: // fade-in
628: if (animationMode == 0 && alpha != 1.0F) {
629: for (int i = (int) animationSpeed; i >= 0; i--) {
630: // gradually increase the alpha value, applying less transparency
631: alpha = (1.0F - ((float) i) / animationSpeed);
632: SwingUtilities.invokeLater(new Runnable() {
633: public void run() {
634: repaint();
635: }
636: });
637: Thread.currentThread().sleep(20);
638: }
639: animationMode = -1;
640: }
641: // fade-out
642: else if (animationMode == 1 && alpha != 0.0F) {
643: for (int i = 0; i <= (int) animationSpeed; i++) {
644: // gradually decrease the alpha value, applying more transparency
645: alpha = (1.0F - ((float) i) / animationSpeed);
646: SwingUtilities.invokeLater(new Runnable() {
647: public void run() {
648: repaint();
649: }
650: });
651: Thread.currentThread().sleep(20);
652: }
653: animationMode = -1;
654: }
655:
656: Thread.currentThread().sleep(200);
657: } catch (Exception ex) {
658: if (BuildProperties.DEBUG)
659: ex.printStackTrace();
660: }
661: }
662: }
663:
664: /**
665: * Sets whether a fade-in or fade-out animation is to be run.
666: * animationMode an <code>int</code> specifying the animation type.
667: */
668: public void setAnimationMode(int animationMode, float animationSpeed) {
669: this .animationMode = animationMode;
670: this .animationSpeed = animationSpeed;
671: }
672:
673: /**
674: * Set the alpha transparency of the magnifier
675: * a <code>float</code> specifying the new alpha transparency
676: */
677: public void setTransparency(float a) {
678: alpha = a;
679: }
680: }
|