boofcv.gui.image.ImagePanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of boofcv-swing Show documentation
Show all versions of boofcv-swing Show documentation
BoofCV is an open source Java library for real-time computer vision and robotics applications.
/*
* Copyright (c) 2021, Peter Abeles. All Rights Reserved.
*
* This file is part of BoofCV (http://boofcv.org).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package boofcv.gui.image;
import boofcv.gui.BoofSwingUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.Objects;
/**
* Simple JPanel for displaying buffered images.
*
* @author Peter Abeles
*/
@SuppressWarnings({"NullAway.Init"})
public class ImagePanel extends JPanel {
// the image being displayed
protected @Nullable BufferedImage img;
// should it re-size the image based on the panel's size
protected ScaleOptions scaling = ScaleOptions.DOWN;
public double scale = 1;
public double offsetX = 0;
public double offsetY = 0;
// this variable must only be touched inside the GUI thread
private ScaleOffset adjustmentGUI = new ScaleOffset();
protected SaveImageOnClick mouseListener;
protected AffineTransform transform = new AffineTransform();
private boolean center = false;
public ImagePanel( BufferedImage img ) {
this(img, ScaleOptions.NONE);
}
public ImagePanel( final BufferedImage img, ScaleOptions scaling ) {
this(true);
this.img = img;
this.scaling = scaling;
autoSetPreferredSize();
}
public ImagePanel( int width, int height ) {
this(true);
setPreferredSize(new Dimension(width, height));
}
/**
* Adds the ability to save an image using the middle mouse button. A dialog is shown to the user
* so that they know what has happened. They can hide it in the future if they wish.
*/
public ImagePanel( boolean addMouseListener ) {
if (addMouseListener) {
addClickToSaveListener();
}
}
public ImagePanel() {
this(true);
}
/**
* Adds the ability to save an image using the middle mouse button. A dialog is shown to the user
* so that they know what has happened. They can hide it in the future if they wish.
*/
public ImagePanel addClickToSaveListener() {
mouseListener = new SaveImageOnClick(this);
addMouseListener(mouseListener);
return this;
}
@Override
public void paintComponent( Graphics g ) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
AffineTransform original = g2.getTransform();
configureDrawImageGraphics(g2);
//draw the image
BufferedImage img = this.img;
if (img != null) {
computeOffsetAndScale(img, adjustmentGUI);
scale = adjustmentGUI.scale;
offsetX = adjustmentGUI.offsetX;
offsetY = adjustmentGUI.offsetY;
if (scale == 1) {
g2.drawImage(img, (int)offsetX, (int)offsetY, this);
} else {
transform.setTransform(scale, 0, 0, scale, offsetX, offsetY);
g2.drawImage(img, transform, null);
}
}
g2.setTransform(original);
}
protected void configureDrawImageGraphics( Graphics2D g2 ) {}
@SuppressWarnings({"SelfAssignment"})
private void computeOffsetAndScale( BufferedImage img, ScaleOffset so ) {
if (scaling != ScaleOptions.NONE) {
if (scaling == ScaleOptions.MANUAL) {
so.scale = scale;
} else {
double ratioW = (double)getWidth()/(double)img.getWidth();
double ratioH = (double)getHeight()/(double)img.getHeight();
so.scale = Math.min(ratioW, ratioH);
if (scaling == ScaleOptions.DOWN && so.scale >= 1)
so.scale = 1;
}
if (center) {
so.offsetX = (getWidth() - img.getWidth()*so.scale)/2;
so.offsetY = (getHeight() - img.getHeight()*so.scale)/2;
} else {
so.offsetX = 0;
so.offsetY = 0;
}
if (scale == 1) {
so.offsetX = (int)so.offsetX;
so.offsetY = (int)so.offsetY;
}
} else {
if (center) {
so.offsetX = (getWidth() - img.getWidth())/2;
so.offsetY = (getHeight() - img.getHeight())/2;
} else {
so.offsetX = 0;
so.offsetY = 0;
}
so.scale = 1;
}
}
/**
* Change the image being displayed. If panel is active then don't call unless inside the GUI thread. Repaint()
* is not automatically called.
*
* @param image The new image which will be displayed.
*/
public ImagePanel setImage( BufferedImage image ) {
this.img = image;
return this;
}
/**
* Changes the buffered image and calls repaint. Does not need to be called in the UI thread.
*/
public ImagePanel setImageRepaint( @Nullable BufferedImage image ) {
// if image is larger before than the new image then you need to make sure you repaint
// the entire image otherwise a ghost will be left
ScaleOffset workspace;
if (SwingUtilities.isEventDispatchThread()) {
workspace = adjustmentGUI;
} else {
workspace = new ScaleOffset();
}
repaintJustImage(img, workspace);
this.img = image;
if (image != null) {
repaintJustImage(img, workspace);
} else {
repaint();
}
return this;
}
/**
* Changes the image and will be invoked inside the UI thread at a later time. repaint() is automatically
* called.
*
* @param image The new image which will be displayed.
*/
public void setImageUI( final @Nullable BufferedImage image ) {
BoofSwingUtil.invokeNowOrLater(() -> {
repaintJustImage(ImagePanel.this.img, adjustmentGUI);
ImagePanel.this.img = image;
repaintJustImage(ImagePanel.this.img, adjustmentGUI);
});
}
public boolean isCentered() {
return center;
}
public void setCentering( boolean center ) {
this.center = center;
}
/**
* Repaints just the region around the image.
*/
public void repaintJustImage() {
repaintJustImage(img, new ScaleOffset());
}
protected void repaintJustImage( @Nullable BufferedImage img, ScaleOffset workspace ) {
if (img == null) {
repaint();
return;
}
computeOffsetAndScale(img, workspace);
repaint((int)Math.round(workspace.offsetX) - 1, (int)Math.round(workspace.offsetY) - 1,
(int)(img.getWidth()*workspace.scale + 0.5) + 2, (int)(img.getHeight()*workspace.scale + 0.5) + 2);
}
public @Nullable BufferedImage getImage() {
return img;
}
public Dimension getImageDimension() {
Objects.requireNonNull(img);
return new Dimension(img.getWidth(), img.getHeight());
}
public ImagePanel setScaling( ScaleOptions scaling ) {
this.scaling = scaling;
return this;
}
public void autoSetPreferredSize() {
Objects.requireNonNull(img);
setPreferredSize(new Dimension(img.getWidth(), img.getHeight()));
}
public MouseListener getMouseClickToSaveListener() {
return mouseListener;
}
public ImagePanel setScale( double scale ) {
this.scale = scale;
return this;
}
private static class ScaleOffset {
double scale, offsetX, offsetY;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy