Skip to content

Enhance image frame #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package hageldave.imagingkit.core.interaction;

import hageldave.imagingkit.core.util.ImagePanel;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.Arrays;

/**
* This class implements panning of an {@link ImagePanel} by dragging the mouse.
* To activate the panning, the 'e' key has to be pressed.
* The panning is done by translating the panningAffineTransform {@link AffineTransform} of the image panel.
* <br>
* The ImagePanning class first has to be registered on the image panel by calling register() and
* passing the image panel object to the ImagePanning constructor.
* It can be deregistered if it isn't used anymore by calling deRegister().
* <br>
* Example use of the ImagePanning class:
* <pre>ImagePanning ip = new ImagePanning(imagePanel).register();</pre>
* <pre>ip.deRegister();</pre>
*/
public class ImagePanning extends PanelInteraction {
final protected ImagePanel imagePanel;
protected Point2D dragStart = null;

/**
* Constructs a new ImagePanning interaction for the given image panel.
* @param imagePanel image panel to pan
*/
public ImagePanning(ImagePanel imagePanel) {
this.imagePanel = imagePanel;
}

@Override
public void mouseDragged(MouseEvent e) {
super.mouseDragged(e);
if (this.dragStart != null) {
double mouseTx = e.getX()-this.dragStart.getX();
double mouseTy = e.getY()-this.dragStart.getY();
double scaleX = imagePanel.getZoomAffineTransform().getScaleX();
double scaleY = imagePanel.getZoomAffineTransform().getScaleY();
mouseTx /= scaleX;
mouseTy /= scaleY;

AffineTransform transformedTrans = imagePanel.getPanningAffineTransform();
transformedTrans.translate(mouseTx, mouseTy);
imagePanel.setPanningAffineTransform(transformedTrans);
this.dragStart.setLocation(e.getX(), e.getY());
}
}

@Override
public void mousePressed(MouseEvent e) {
if (pressedKeycode == KeyEvent.VK_E) {
this.dragStart = e.getPoint();
}
}

@Override
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
this.dragStart = null;
}

@Override
public void keyPressed(KeyEvent e) {
super.keyPressed(e);
if (e.getKeyCode() == KeyEvent.VK_E) {
imagePanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
}

@Override
public void keyReleased(KeyEvent e) {
super.keyReleased(e);
imagePanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}

@Override
public ImagePanning register(){
if( ! Arrays.asList(imagePanel.getMouseListeners()).contains(this))
imagePanel.addMouseListener(this);
if( ! Arrays.asList(imagePanel.getMouseMotionListeners()).contains(this))
imagePanel.addMouseMotionListener(this);
if ( ! Arrays.asList(imagePanel.getKeyListeners()).contains(this))
imagePanel.addKeyListener(this);
return this;
}

@Override
public ImagePanning deRegister(){
imagePanel.removeMouseListener(this);
imagePanel.removeMouseMotionListener(this);
imagePanel.removeKeyListener(this);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package hageldave.imagingkit.core.interaction;

import hageldave.imagingkit.core.util.ImagePanel;

import java.awt.event.KeyEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.util.Arrays;

/**
* This class implements zooming of an {@link ImagePanel} by scrolling the mouse wheel.
* The zooming of this class is different from that of {@link MouseFocusedZooming},
* as it always zooms into the center of the current viewport, not on the current mouse position.
* <br>
* To zoom on the image panel, the 'shift' key has to be pressed.
* The zooming is done by scaling the zoomAffineTransform {@link AffineTransform} of the image panel.
* <br>
* The ImageZooming class first has to be registered on the image panel by calling register().
* It can be deregistered if it isn't used anymore by calling deRegister().
* <br>
* Example use of the ImageZooming class:
* <pre>ImageZooming iz = new ImageZooming(imagePanel).register();</pre>
* <pre>iz.deRegister();</pre>
*/
public class ImageZooming extends PanelInteraction {
final protected ImagePanel imagePanel;

/**
* Constructs a new ImageZooming interaction for the given image panel.
* @param imagePanel image panel to zoom
*/
public ImageZooming(ImagePanel imagePanel) {
this.imagePanel = imagePanel;
}

@Override
public void mouseWheelMoved(MouseWheelEvent e) {
super.mouseWheelMoved(e);
if (pressedKeycode == KeyEvent.VK_SHIFT) {
AffineTransform zoomAT = this.calcZoomAffineTransform(Math.pow(1.7, e.getWheelRotation() * 0.1));
AffineTransform panelAT = this.imagePanel.getZoomAffineTransform();
panelAT.concatenate(zoomAT);
this.imagePanel.setZoomAffineTransform(panelAT);
}
}

protected AffineTransform calcZoomAffineTransform(double zoom) {
double imageWidth = imagePanel.getBounds().getWidth();
double imageHeight = imagePanel.getBounds().getHeight();

AffineTransform zoomAT = new AffineTransform();
zoomAT.translate(imageWidth / 2.0, imageHeight / 2.0);
zoomAT.scale(zoom, zoom);
zoomAT.translate(-(imageWidth / 2.0), -(imageHeight / 2.0));
return zoomAT;
}

@Override
public ImageZooming register(){
if( ! Arrays.asList(imagePanel.getMouseWheelListeners()).contains(this))
imagePanel.addMouseWheelListener(this);
if ( ! Arrays.asList(imagePanel.getKeyListeners()).contains(this))
imagePanel.addKeyListener(this);
return this;
}

@Override
public ImageZooming deRegister(){
imagePanel.removeMouseWheelListener(this);
imagePanel.removeKeyListener(this);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package hageldave.imagingkit.core.interaction;

import hageldave.imagingkit.core.util.ImagePanel;

import java.awt.event.KeyEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.util.Arrays;

/**
* This class implements zooming of an {@link ImagePanel} by scrolling the mouse wheel.
* The zooming of this class is different from that of {@link ImageZooming},
* as it always zooms onto the current mouse position, not into the center of the current viewport.
* <br>
* To zoom on the image panel, the 'shift' key has to be pressed.
* The zooming is done by scaling the zoomAffineTransform {@link AffineTransform} of the image panel.
* <br>
* The MouseFocusedZooming class first has to be registered on the image panel by calling register().
* It can be deregistered if it isn't used anymore by calling deRegister().
* <br>
* Example use of the MouseFocusedZooming class:
* <pre>MouseFocusedZooming mfz = new MouseFocusedZooming(imagePanel).register();</pre>
* <pre>mfz.deRegister();</pre>
*/
public class MouseFocusedZooming extends PanelInteraction {
final protected ImagePanel imagePanel;

/**
* Constructs a new MouseFocusedZooming object for the given image panel.
* @param imagePanel image panel to zoom
*/
public MouseFocusedZooming(ImagePanel imagePanel) {
this.imagePanel = imagePanel;
}

@Override
public void mouseWheelMoved(MouseWheelEvent e) {
super.mouseWheelMoved(e);
if (pressedKeycode == KeyEvent.VK_SHIFT) {
try {
AffineTransform zoomAT = this.calcZoomAffineTransform(e, Math.pow(1.7, e.getWheelRotation() * 0.1));
AffineTransform panelAT = this.imagePanel.getZoomAffineTransform();
panelAT.concatenate(zoomAT);
this.imagePanel.setZoomAffineTransform(panelAT);
} catch (NoninvertibleTransformException ex) {
throw new RuntimeException(ex);
}
}
}

protected AffineTransform calcZoomAffineTransform(MouseWheelEvent e, double zoom) throws NoninvertibleTransformException {
Point2D.Double mouseCoordinates = new Point2D.Double(e.getX(), e.getY());
mouseCoordinates = (Point2D.Double) imagePanel.getZoomAffineTransform().inverseTransform(mouseCoordinates, mouseCoordinates);

AffineTransform zoomAT = new AffineTransform();
zoomAT.translate(mouseCoordinates.getX(), mouseCoordinates.getY());
zoomAT.scale(zoom, zoom);
zoomAT.translate(-mouseCoordinates.getX(), -mouseCoordinates.getY());
return zoomAT;
}

@Override
public MouseFocusedZooming register(){
if( ! Arrays.asList(imagePanel.getMouseWheelListeners()).contains(this))
imagePanel.addMouseWheelListener(this);
if ( ! Arrays.asList(imagePanel.getKeyListeners()).contains(this))
imagePanel.addKeyListener(this);
return this;
}

@Override
public MouseFocusedZooming deRegister(){
imagePanel.removeMouseWheelListener(this);
imagePanel.removeKeyListener(this);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package hageldave.imagingkit.core.interaction;

import hageldave.imagingkit.core.util.ImagePanel;

import java.awt.event.KeyEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.util.Arrays;

/**
* This class implements panning of an {@link ImagePanel} by scrolling the mouse wheel.
* To avoid conflicts with other interactions, the 'shift' key can't be pressed during the panning.
* The panning is either done horizontally or vertically, depending on whether the 'alt' key is pressed.
* <br>
* The MouseWheelPanning class first has to be registered on the image panel by calling register() and
* passing the image panel object to the MouseWheelPanning constructor.
* It can be deregistered if it isn't used anymore by calling deRegister().
* <br>
* Example use of the ImagePanning class:
* <pre>MouseWheelPanning mwp = new MouseWheelPanning(imagePanel).register();</pre>
* <pre>mwp.deRegister();</pre>
*/
public class MouseWheelPanning extends PanelInteraction {

final protected ImagePanel imagePanel;

/**
* Constructs a new MouseWheelPanning interaction for the given image panel.
* @param imagePanel image panel to pan
*/
public MouseWheelPanning(ImagePanel imagePanel) {
this.imagePanel = imagePanel;
}

@Override
public void mouseWheelMoved(MouseWheelEvent e) {
super.mouseWheelMoved(e);
if (pressedKeycode != KeyEvent.VK_SHIFT) {
double scroll = e.getPreciseWheelRotation() * 1.5;
AffineTransform panningAT = imagePanel.getPanningAffineTransform();
double scaleX = imagePanel.getZoomAffineTransform().getScaleX();
double scaleY = imagePanel.getZoomAffineTransform().getScaleY();
if (pressedKeycode == KeyEvent.VK_ALT) {
panningAT.translate(scroll / scaleX, 0);
} else {
panningAT.translate(0, scroll / scaleY);
}
imagePanel.setPanningAffineTransform(panningAT);
}
}

@Override
public MouseWheelPanning register(){
if( ! Arrays.asList(imagePanel.getMouseWheelListeners()).contains(this))
imagePanel.addMouseWheelListener(this);
if ( ! Arrays.asList(imagePanel.getKeyListeners()).contains(this))
imagePanel.addKeyListener(this);
return this;
}

@Override
public MouseWheelPanning deRegister(){
imagePanel.removeMouseWheelListener(this);
imagePanel.removeKeyListener(this);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package hageldave.imagingkit.core.interaction;

import hageldave.imagingkit.core.util.ImagePanel;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;

/**
* The PanelInteraction class is an abstract class that can be used to implement interactions with an {@link ImagePanel}.
* It implements the {@link KeyListener} interface and provides a {@link #pressedKeycode} field that can be used to
* check which key is currently pressed.
* <br>
* Implementations of the abstract class can be found in the {@link hageldave.imagingkit.core.interaction} package.
*/
public abstract class PanelInteraction extends MouseAdapter implements KeyListener {
int pressedKeycode = -1;
@Override
public void keyPressed(KeyEvent e) {
this.pressedKeycode = e.getKeyCode();
}

@Override
public void keyReleased(KeyEvent e) {
this.pressedKeycode = -1;
}

@Override
public void keyTyped(KeyEvent e) {

}

/**
* Registers the interaction class to the image panel (e.g., calling addMouseListener() on the image panel).
*
* @return {@link PanelInteraction} this for chaining
*/
public abstract PanelInteraction register();

/**
* Unregisters the interaction class from the image panel (e.g., calling removeMouseListener() on the image panel).
*
* @return {@link PanelInteraction} this for chaining
*/
public abstract PanelInteraction deRegister();
}
Original file line number Diff line number Diff line change
@@ -22,14 +22,11 @@

package hageldave.imagingkit.core.util;

import java.awt.Dimension;
import java.awt.Image;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import hageldave.imagingkit.core.Img;

import javax.swing.*;
import java.awt.*;

/**
* {@link JFrame} for displaying an image utilizing an {@link ImagePanel}.
* This class is merely meant for debugging purposes when you want
Original file line number Diff line number Diff line change
@@ -23,23 +23,19 @@
package hageldave.imagingkit.core.util;


import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Stroke;
import hageldave.imagingkit.core.Img;
import hageldave.imagingkit.core.io.ImageSaver;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.ImageObserver;
import java.util.function.Function;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import hageldave.imagingkit.core.Img;

/**
* Panel for displaying Images.
* The panel behaves as follows:
@@ -57,7 +53,7 @@
* @since 1.4
*/
@SuppressWarnings("serial")
public class ImagePanel extends JPanel{
public class ImagePanel extends JPanel {
/** First color of checkerboard (0x999999)
* @since 1.4 */
public static final Color CHECKERBOARD_COLOR_1 = new Color(0x999999);
@@ -66,8 +62,6 @@ public class ImagePanel extends JPanel{
public static final Color CHECKERBOARD_COLOR_2 = new Color(0x666666);




/** The image to be displayed, null if not set
* @since 1.4 */
protected Image img = null;
@@ -82,7 +76,10 @@ public class ImagePanel extends JPanel{
/** 8 by default */
private int checkerSize;
private Stroke checkerStroke;


protected AffineTransform zoomAffineTransform = new AffineTransform();
protected AffineTransform panningAffineTransform = new AffineTransform();
protected int pressedKeycode = -1;

/**
* Constructs a new ImagePanel.
@@ -91,12 +88,50 @@ public class ImagePanel extends JPanel{
* @since 1.4
*/
public ImagePanel() {
this.setFocusable(true);
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem saveItem = new JMenuItem("Save image");
popupMenu.add(saveItem);

saveItem.addActionListener(e -> {
FileDialog saveDialog = new FileDialog(new Frame(), "Choose where to save the file.", FileDialog.SAVE);
saveDialog.setVisible(true);
String fileName = saveDialog.getFile();
String directory = saveDialog.getDirectory();
if (fileName != null) {
for (String fileFormat: ImageSaver.getSaveableImageFileFormats()) {
if (fileName.endsWith(fileFormat)) {
ImageSaver.saveImage(img, directory + fileName);
System.out.println("Image has been exported to " + directory + fileName + ".");
}
}
}
popupMenu.setVisible(false);
});

JMenuItem originalResolution = new JMenuItem("Zoom to original resolution");
popupMenu.add(originalResolution);
originalResolution.addActionListener(e -> this.setZoomAffineTransform(new AffineTransform()));

JMenuItem fitFrame = new JMenuItem("Fit to frame");
popupMenu.add(fitFrame);
fitFrame.addActionListener(e -> {
this.setZoomAffineTransform(new AffineTransform());
this.setPanningAffineTransform(new AffineTransform());
});

this.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if(SwingUtilities.isLeftMouseButton(e)){
ImagePanel.this.clickPoint = e.getPoint();
ImagePanel.this.repaint();
if (pressedKeycode != KeyEvent.VK_E) {
ImagePanel.this.clickPoint = e.getPoint();
ImagePanel.this.repaint();
popupMenu.setVisible(false);
}
} else if (SwingUtilities.isRightMouseButton(e)) {
popupMenu.setLocation(e.getXOnScreen(), e.getYOnScreen());
popupMenu.setVisible(true);
}
}
@Override
@@ -107,6 +142,7 @@ public void mouseReleased(MouseEvent e) {
}
}
});

this.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
@@ -116,6 +152,21 @@ public void mouseDragged(MouseEvent e) {
}
}
});

this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
super.keyPressed(e);
ImagePanel.this.pressedKeycode = e.getKeyCode();
}

@Override
public void keyReleased(KeyEvent e) {
super.keyReleased(e);
ImagePanel.this.pressedKeycode = -1;
}
});

this.setBackground(CHECKERBOARD_COLOR_2);
this.setForeground(CHECKERBOARD_COLOR_1);
this.checkerSize = 8;
@@ -220,9 +271,7 @@ public void paint(Graphics g) {
}
drawImage(g2d);
}




/**
* Draws the image to the specified graphics context
* @param g graphics context to draw on
@@ -240,9 +289,13 @@ protected void drawImage(Graphics2D g) {
Image img = this.img;
if(img != null){
Point clickPoint = this.clickPoint;
if(clickPoint == null){
if (clickPoint == null) {
g.transform(zoomAffineTransform);
g.transform(panningAffineTransform);

double imgRatio = img.getWidth(obs_w)*1.0/img.getHeight(obs_h);
double panelRatio = this.getWidth()*1.0/this.getHeight();

if(imgRatio > panelRatio) {
// image wider than panel
int height = (int) (this.getWidth()/imgRatio);
@@ -330,5 +383,22 @@ protected void drawCheckerBoard(Graphics2D g2d) {
protected static final Stroke checkerStrokeForSize(int size) {
return new BasicStroke(size, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1, new float[]{0,size*2}, 0);
}


public AffineTransform getZoomAffineTransform() {
return zoomAffineTransform;
}

public void setZoomAffineTransform(AffineTransform zoomAffineTransform) {
this.zoomAffineTransform = zoomAffineTransform;
this.repaint();
}

public AffineTransform getPanningAffineTransform() {
return panningAffineTransform;
}

public void setPanningAffineTransform(AffineTransform panningAffineTransform) {
this.panningAffineTransform = panningAffineTransform;
this.repaint();
}
}