Implementing a Lens
A moving lens over an image is quite a useful feature in many applications where visual inspection is an important factor in analyzing images. In addition to the AffineTransform class, the lens feature, which is implemented by the Lens class, uses many aspects of the Graphics2D class. Listing 7.13 shows the code for Lens.
LISTING 7.13 The Lens class
package com.vistech.imageviewer; import java.io.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Lens implements MouseListener, MouseMotionListener{ protected ImageManipulator imageCanvas; protected Dimension lensSize = new Dimension(60,80); protected Point prevPoint = new Point(0,0); protected boolean lensOn = false; protected int sizeIncrement = 10; protected double magIncrement = 0.5; protected double lensMag = 2.0; protected Zoom zoom; public Lens(ImageManipulator c){ imageCanvas = c; zoom = new Zoom(imageCanvas); } public void init() { setLensOn(true);} public void setLensSize(Dimension d){ lensSize = d; } public Dimension getLensSize(){ return lensSize; } public void setLensMag(double mag){ lensMag = mag; } public double getLensMag(){ return lensMag; } public void setLensMagIncrement(int incr){ magIncrement = incr; } public double getLensMagIncrement(){ return magIncrement; } public void setLensSizeIncrement(int incr){ sizeIncrement = incr; } public int getLensSizeIncrement(){ return sizeIncrement; } public void setLensOn(boolean onOff){ lensOn = onOff; } public boolean getLensOn(){ return lensOn; } public void drawLens(int x, int y){ int wid = lensSize.width; int ht = lensSize.height; Shape lens = new Ellipse2D.Float(prevPoint.x-wid,prevPoint.y-ht, wid,ht); imageCanvas.setClip(lens); zoom.magnify(0,0, 1); Shape ch = new Ellipse2D.Float(x-wid,y-ht, wid,ht); imageCanvas.setClip(ch); zoom.magnify((x-wid/2), (y-ht/2), lensMag); imageCanvas.draw(ch); prevPoint = new Point(x,y); } public void incrementLensSize(){ lensSize.width += sizeIncrement; lensSize.height += sizeIncrement; } public void decrementLensSize(){ lensSize.width -= sizeIncrement; lensSize.height -= sizeIncrement; } public void incrementLensMag(){ lensMag += magIncrement; } public void decrementLensMag(){ lensMag -= magIncrement; } public void reset() { setLensOn(false); } public void mousePressed(MouseEvent e) { if(!lensOn) return; if(SwingUtilities.isLeftMouseButton(e)){ drawLens(e.getX(), e.getY()); } } public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} public void mouseReleased(MouseEvent e) { if(!lensOn) return; imageCanvas.setClip(null); zoom.magnify(0,0,1); } public void mouseDragged(MouseEvent e){ if(SwingUtilities.isLeftMouseButton(e)){ if(lensOn) drawLens(e.getX(), e.getY()); } } public void mouseMoved(MouseEvent e){} }
The Lens class constructor takes ImageManipulator as an input parameter. This means that we can pass the ImageCanvas2D object, which implements the ImageManipulator interface, as this parameter.
The Lens class implements both the mouse and the mouseMotion eventhandling methods. A third-party object registers Lens with ImageManipulator to receive these events. Both of these events are needed for dragging the lens over the image.
When the mouse is pressed, a Lens object is created at the mouse position, and when the mouse is released the object is destroyed. When the mouse is dragged, the lens is moved. Figure 7.11 shows a screen shot of a lens overlaid on an image.
FIGURE 7.11 An interactive lens over an image
The principle of drawing the lens is simple. The shape of the lens is oval, so we use the Ellipse2D class for drawing it. Before the lens is drawn, the graphical context is clipped over a region covering the lens. For this reason, the same shape that is used for creating the lens is also used for clipping the graphical context. The image is then magnified and is drawn over the entire viewport. Because the graphical context is clipped, the image is drawn only over the region where the clipping is in effect.
When you drag the mouse, the lens is drawn at the previous position to erase that image. Recall from Chapter 5 that the XOR paint mode erases the graphical object if it is drawn twice. Drawing the original image at the previous clip area does exactly this. When the mouse is released, the clip region is reset, by the passing of null to the setClip() method.
The Lens class has several properties, including lens size and magnification factor. These properties allow the user to set the desirable lens and magnification attributes. Figure 7.12 shows a screen shot of a simple panel we created to add to our image viewer. When the Lens feature is invoked, this panel is launched. Listing 7.14 shows the code for the LensPanel class.
FIGURE 7.12 The Lens panel
LISTING 7.14 The LensPanel class
public class LensPanel extends JPanel { protected ImageManipulator imageCanvas; protected Lens lens; protected boolean lensOn = false; public LensPanel(ImageManipulator manip) { imageCanvas = manip; if(manip != null) { manip.resetManipulation(); lens = new Lens(manip); createUI(); } } public void setLensOn(boolean onOrOff){ if(lensOn == onOrOff) return; lensOn = onOrOff; if(lensOn) { imageCanvas.addMouseListener(lens); imageCanvas.addMouseMotionListener(lens); } else { imageCanvas.removeMouseListener(lens); imageCanvas.removeMouseMotionListener(lens); } lens.setLensOn(lensOn); } private void createUI() { // Code that creates lens panel } }
In our image viewer we added a new menu called Manipulate for selecting manipulation-related features. To invoke the lens feature, select this menu and click on Lens. The panel shown in Figure 7.12 will appear. Invoking Lens will reset all the manipulation operations but will preserve the display mode and flip mode settings.