# Art Lupeneffekt



## windl (6. Feb 2012)

Hi NG,

ich habe folgendes Problem!

Ich male etwas in ein Graphics z.B. drei - vier Rechtecke nebeneinander.
Nun möchte ich einen Rahmen langsam von links nach Rechts wandern lassen.
Alles was sich im Rahmen befindet soll gezoomt dargestellt werden!
Eine Art Lupenfunktion. Habe es schon versucht mit BufferImage und dann getSubImage.
Hat leider nicht gut funktioniert.

Wäre schön, wenn jemand eine Idee oder einen Link zu einem Javaprogramm hätte 

Danke
Uwe


----------



## Marco13 (6. Feb 2012)

Hm... sowas wollte ich schon ewig mal implementieren. Ich hatte zwar mal sowas in Swogl gemacht (screenshot) aber das ist sicher nicht das, was du suchst, nicht mehr ganz up to date, und außerdem ein Overkill. 

Das für eine Swing-Component in seiner allgemeinsten Form zu machen ist alles andere als einfach - genaugenommen sogar SEHR schwierig: Damit so eine Lupe überhaupt Sinn macht, müßte alles, was gezeichnet wird, größer gezeichnet werden, als es angezeigt wird, und nur dort wo die Lupe ist, wird es so groß angezeigt, wie es gezeichnet wird - andernfalls bekommt man unter der Lupe nur häßlich vergrößerten Pixelmatsch. 

Aber eine schnelle websuche liefert doch einiges, was zumindest in diese Richtung geht (erste Ergebnisse, oder etwas weiter auch sowas wie JXLayer 4.0 demonstration by Piet Blok ...)


----------



## xehpuk (7. Feb 2012)

Ohne mich jetzt durchgegoogelt zu haben, habe ich einfach selbst ein wenig rumgespielt.

Das ist dabei rausgekommen:

```
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputListener;

public class ZoomPanel extends JPanel implements MouseInputListener {
	private static final int DIMENSION = 100;
	private static final double ZOOM = 2.5;
	
	private Shape s;
	
	public ZoomPanel() {
		addMouseListener(this);
		addMouseMotionListener(this);
		setLayout(new GridLayout());
		try {
			add(new JLabel(new ImageIcon(new URL("http://www.java-forum.org/images/misc/java_forum_org.gif"))));
		} catch (final MalformedURLException e) {
			throw new RuntimeException(e);
		}
	}
	
	@Override
	public void paint(final Graphics g) {
		super.paint(g);
		if (s != null) {
			final Graphics2D g2 = (Graphics2D) g.create();
			g2.setClip(s);
			final Rectangle b = s.getBounds();
			g2.clearRect(b.x, b.y, b.width, b.height);
			final AffineTransform transform = new AffineTransform();
			final double centerX = b.getCenterX();
			final double centerY = b.getCenterY();
			transform.translate(centerX, centerY);
			transform.scale(ZOOM, ZOOM);
			transform.translate(-centerX, -centerY);
			g2.setTransform(transform);
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			super.paint(g2);
			g2.setTransform(new AffineTransform());
			g2.setClip(g.getClip());
			g2.draw(s);
			g2.dispose();
		}
	}
	
	private void moveShape(final MouseEvent e) {
		final Point p = e.getPoint();
		final int hd = DIMENSION / 2;
		s = new Ellipse2D.Double(p.x - hd, p.y - hd, DIMENSION, DIMENSION);
		repaint();
	}
	
	@Override
	public void mousePressed(final MouseEvent e) {
		if (SwingUtilities.isRightMouseButton(e)) {
			moveShape(e);
		}
	}
	
	@Override
	public void mouseReleased(final MouseEvent e) {
		if (SwingUtilities.isRightMouseButton(e)) {
			s = null;
			repaint();
		}
	}
	
	@Override
	public void mouseDragged(final MouseEvent e) {
		if (SwingUtilities.isRightMouseButton(e)) {
			moveShape(e);
		}
	}
	
	@Override
	public void mouseClicked(final MouseEvent e) {}
	
	@Override
	public void mouseEntered(final MouseEvent e) {}
	
	@Override
	public void mouseExited(final MouseEvent e) {}
	
	@Override
	public void mouseMoved(final MouseEvent e) {}
	
	public static void main(final String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				final JFrame frame = new JFrame("ZoomPanel");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				frame.setContentPane(new ZoomPanel());
				frame.pack();
				frame.setLocationRelativeTo(null);
				frame.setVisible(true);
			}
		});
	}
}
```
Rechte Maustaste klicken und über das Panel ziehen.


----------



## windl (9. Feb 2012)

Vielen Dank!
Genau das habe ich gesucht!!


----------



## GUI-Programmer (9. Feb 2012)

@xehpuk: Eine Frage noch zu deinen guten, lauffähigen Programm: Hat es einen bestimmten Sinn, warum du die paint() - Methode überschreibst und nicht wie üblich die paintComponent() ?


----------



## xehpuk (9. Feb 2012)

Ja.
Wenn nur 
	
	
	
	





```
paintComponent()
```
 überschrieben wird, bleiben natürlich 
	
	
	
	





```
paintBorder()
```
 und 
	
	
	
	





```
paintChildren()
```
 unberührt. Dadurch fehlen in der "Lupe" diese beiden Bestandteile. Und selbst wenn es damit funktionierte, wüsste ich jetzt nicht, wie man das am geschicktesten neben anderem Malcode in der 
	
	
	
	





```
paintComponent()
```
 unterbringen könnte.

Wie dem auch sei, ich habe da ein wenig verrückt abstrahiert, um die Komponente "wiederverwendbarer" zu machen:

```
import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputListener;

public abstract class ZoomPanel extends JPanel {
	{
		final ZoomListener listener = new ZoomListener();
		addMouseListener(listener);
		addMouseMotionListener(listener);
		addMouseWheelListener(listener);
	}
	
	public ZoomPanel() {
		super();
	}
	
	public ZoomPanel(final boolean isDoubleBuffered) {
		super(isDoubleBuffered);
	}
	
	public ZoomPanel(final LayoutManager layout, final boolean isDoubleBuffered) {
		super(layout, isDoubleBuffered);
	}
	
	public ZoomPanel(final LayoutManager layout) {
		super(layout);
	}
	
	public abstract AbstractMagnifier getMagnifier();
	
	@Override
	public void paint(final Graphics g) {
		super.paint(g);
		final AbstractMagnifier magnifier = getMagnifier();
		final Shape shape = magnifier.getShape();
		if (shape != null) {
			final Graphics2D g2 = (Graphics2D) g.create();
			g2.setClip(shape);
			final Rectangle b = shape.getBounds();
			g2.clearRect(b.x, b.y, b.width, b.height);
			final AffineTransform transform = new AffineTransform();
			final double centerX = b.getCenterX();
			final double centerY = b.getCenterY();
			transform.translate(centerX, centerY);
			final double scale = Math.pow(magnifier.step, magnifier.zoom);
			transform.scale(scale, scale);
			transform.translate(-centerX, -centerY);
			g2.setTransform(transform);
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			super.paint(g2);
			g2.setTransform(new AffineTransform());
			g2.setClip(g.getClip());
			g2.setStroke(magnifier.getStroke());
			g2.draw(shape);
			g2.dispose();
		}
	}
	
	protected abstract class AbstractMagnifier {
		private double step = 1.1;
		private int zoom = 0;
		private Point point;
		private Stroke stroke = new BasicStroke(1f);
		
		public double getStep() {
			return this.step;
		}
		
		public void setStep(final double step) {
			this.step = step;
		}
		
		public int getZoom() {
			return this.zoom;
		}
		
		public void setZoom(final int zoom) {
			this.zoom = zoom;
		}
		
		public Stroke getStroke() {
			return this.stroke;
		}
		
		public void setStroke(final Stroke stroke) {
			this.stroke = stroke;
		}
		
		public Point getPoint() {
			return this.point;
		}
		
		public void setPoint(final Point point) {
			this.point = point;
		}
		
		public abstract Shape getShape();
	}
	
	private class ZoomListener implements MouseInputListener, MouseWheelListener {
		private void movePoint(final MouseEvent e) {
			getMagnifier().setPoint(e.getPoint());
			repaint();
		}
		
		@Override
		public void mousePressed(final MouseEvent e) {
			if (SwingUtilities.isRightMouseButton(e)) {
				movePoint(e);
			}
		}
		
		@Override
		public void mouseReleased(final MouseEvent e) {
			if (SwingUtilities.isRightMouseButton(e)) {
				getMagnifier().setPoint(null);
				repaint();
			}
		}
		
		@Override
		public void mouseDragged(final MouseEvent e) {
			if (SwingUtilities.isRightMouseButton(e)) {
				movePoint(e);
			}
		}
		
		@Override
		public void mouseWheelMoved(final MouseWheelEvent e) {
			if (getMagnifier().getPoint() != null) {
				final int rotation = e.getWheelRotation();
				getMagnifier().setZoom(getMagnifier().getZoom() - rotation);
				repaint();
			}
		}
		
		@Override
		public void mouseClicked(final MouseEvent e) {
		}
		
		@Override
		public void mouseEntered(final MouseEvent e) {
		}
		
		@Override
		public void mouseExited(final MouseEvent e) {
		}
		
		@Override
		public void mouseMoved(final MouseEvent e) {
		}
	}
}
```
Das 
	
	
	
	





```
ZoomPanel
```
 ist nun abstrakt und beinhaltet die Lupenfunktionalität gekapselt in einer inneren abstrakten Klasse 
	
	
	
	





```
AbstractMagnifier
```
, welche eine abstrakte Methode 
	
	
	
	





```
getShape()
```
 hat, welche die Form der Lupe bestimmt. Um an die Lupenimplementation zu kommen, gibt es wiederum im 
	
	
	
	





```
ZoomPanel
```
 eine abstrakte Methode 
	
	
	
	





```
getMagnifier()
```
.
Außerdem kann man nun, wenn die Lupe eingeblendet ist, über das Mausrad den Zoomfaktor verstellen.

Hier eine mögliche Implementation:

```
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class StarZoomPanel extends ZoomPanel {
	private final BufferedImage img = fetchImage();
	
	private BufferedImage fetchImage() {
		try {
			return ImageIO.read(new URL("http://www.java-forum.org/images/misc/java_forum_org.gif"));
		} catch (final IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	{
		setPreferredSize(new Dimension(img.getWidth(), img.getHeight()));
	}
	
	private final AbstractMagnifier magnifier = new AbstractMagnifier() {
		{
			setStroke(new BasicStroke(3f));
		}
		
		@Override
		public Shape getShape() {
			final Point point = getPoint();
			[url]http://www.java-forum.org/blogs/tfa/30-snippet-transparente-rechteckige-fenster.html[/url]
			if (point != null) {
				final GeneralPath gp = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
				gp.moveTo(0, -250);
				gp.lineTo(56, -77);
				gp.lineTo(238, -77);
				gp.lineTo(91, 30);
				gp.lineTo(147, 202);
				gp.lineTo(0, 95);
				gp.lineTo(-147, 202);
				gp.lineTo(-91, 30);
				gp.lineTo(-238, -77);
				gp.lineTo(-56, -77);
				gp.closePath();
				final AffineTransform af = AffineTransform.getTranslateInstance(point.x, point.y);
				af.scale(.5, .5);
				return gp.createTransformedShape(af);
			}
			return null;
		}
	};
	
	@Override
	public AbstractMagnifier getMagnifier() {
		return this.magnifier;
	}
	
	@Override
	protected void paintComponent(final Graphics g) {
		super.paintComponent(g);
		g.drawImage(img, 0, 0, null);
	}
	
	public static void main(final String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				final JFrame frame = new JFrame("ZoomPanel");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				final StarZoomPanel contentPane = new StarZoomPanel();
				contentPane.add(new JLabel("Hallo"));
				contentPane.add(new JTextField("Welt!"));
				contentPane.add(new JButton(":)"));
				frame.setContentPane(contentPane);
				frame.pack();
				frame.setLocationRelativeTo(null);
				frame.setVisible(true);
			}
		});
	}
}
```
Wieder dasselbe Hintergrundbild wie im ersten Beispiel. Um die Flexibilität zu zeigen, habe ich diesmal keinen Kreis, sondern einen Stern als Lupenform genommen. Woher ich die Form geklaut habe, habe ich im Quelltext in gültiger Java-Syntax als URL eingebracht.  (Leider setzt die Forensoftware das url-Tag drum.)

Das alles ist jedoch nicht perfekt.
Das Caret des Textfelds wird sich beim Blinken über die Lupe malen und das Blinken wird an sich nicht in der Lupe sichtbar sein, weil es nicht das Neuzeichnen der gesamten Komponente triggert.
Außerdem ist das "o" vom "Hallo" des Labels ein wenig abgeschnitten, wenn man reinzoomt. Das scheint aber eher ein Problem in Swing zu sein?
Des Weiteren wird die Lupe nicht angezeigt, wenn eine Kindkomponente den Mausklick konsumiert.

Vielleicht lässt sich das mit der GlassPane besser realisieren?

Ab Java 7 soll das angeblich leicht mit JLayer umsetzbar sein, das habe ich mir aber auch noch nicht genauer angeschaut.


----------



## Guybrush Threepwood (10. Feb 2012)

Supercoole Sache! Herzlichen Dank!
Im Vergleich zum ersten Beispiel wird bei mir allerdings nicht mehr gezoomed, sondern lediglich der Stern eingeblendet (Win7 64Bit, Java 7.0.02).


----------



## xehpuk (10. Feb 2012)

Den Initialzoom habe ich im 
	
	
	
	





```
AbstractMagnifier
```
 auf 0 gesetzt, siehe hier:
[JAVA=76]private int zoom = 0;[/code]
Wenn du diesen Wert also änderst oder das Mausrad (innerhalb des Panels) drehst, während die Lupe eingeblendet ist, sollte herein- oder herausgezoomt werden.

Ich habe noch ein wenig herumprobiert, das mit einer GlassPane hinzubekommen. Scheint aber recht schwierig zu sein … zumindest für mich. 

Ich probier das nachher mal mit 
	
	
	
	





```
JLayer
```
, wenn ich Java 7 und ein wenig Zeit zur Verfügung habe.


----------



## Guybrush Threepwood (10. Feb 2012)

Feine Sache! Wirklich sehr schick!


----------

