Java: mantener la relación de aspecto de la imagen de fondo de JPanel
Tengo un archivo JPanel
con una imagen de fondo pintada y un administrador de diseño que contiene otras imágenes más pequeñas, todo esto dentro de un archivo JFrame
. La imagen de fondo es bastante grande y quiero poder mantener su relación de aspecto ya sea en un monitor grande o pequeño.
Con el tiempo, quiero poder tener mis LayoutManager
imágenes y las más pequeñas en sus celdas "pegadas" a la imagen de fondo.
Busqué recursos a mi alrededor y parece que muchos ejemplos usan a BufferedImage
pero yo no; ¿Esto supondrá un problema? Publicaré mi código a continuación para pintar la imagen. Si me falta alguna información, hágamelo saber.
public class MonitorPanel extends JPanel {
Image img;
public MonitorPanel() throws MalformedURLException {
//add components
try {
img = ImageIO.read(new File("src/customer_vlans.jpg"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
protected void paintComponent(Graphics g)
{
//paint background image
super.paintComponent(g);
//g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
g.drawImage(img, 0, 0, this);
}
}
EDITAR: Debo mencionar que conozco la fórmula de relación de aspecto: alto original / ancho original x nuevo ancho = nuevo alto Sin embargo, no sé cómo usarlo correctamente para mi beneficio.
Bueno, la solución más rápida y sencilla es utilizarImage.getScaledInstance
g.drawImage(img.getScaledInstance(newWidth, -1, Image. SCALE_SMOOTH), x, y, this);
Si se pregunta acerca del número negativo, los documentos de Java dicen:
Si el ancho o el alto es un número negativo, se sustituye por un valor para mantener la relación de aspecto de las dimensiones de la imagen original. Si tanto el ancho como el alto son negativos, entonces se utilizan las dimensiones de la imagen original.
ACTUALIZAR
Solo como nota al margen (mi Google estaba funcionando mal).
getScaledInstance
No es el método más rápido ni el de mayor calidad, pero sí el más sencillo.
Lea The Perils of Image.getScaledInstance para obtener más ideas.
ACTUALIZAR
Escalar una imagen para que se ajuste a un área es un poco más complicado que simplemente escalar la relación de aspecto. Debe elegir si desea que la imagen "encaje" dentro del área (posiblemente dejando áreas en blanco a su alrededor) o "llene" demasiado el área (de modo que su dimensión más pequeña se ajuste a la dimensión más grande del área).
Ajustar y llenar
Básicamente trabajo con factores de escala.
Esto devuelve el factor de escala para un tamaño particular. Utilizo esto para tomar decisiones sobre qué factor quiero usar según el algoritmo que necesito.
public static double getScaleFactor(int iMasterSize, int iTargetSize) {
double dScale = 1;
if (iMasterSize > iTargetSize) {
dScale = (double) iTargetSize / (double) iMasterSize;
} else {
dScale = (double) iTargetSize / (double) iMasterSize;
}
return dScale;
}
Se utiliza mediante estos dos métodos. Simplemente toman dos Dimension
s. El original y el objetivo.
public static double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
public static double getScaleFactorToFill(Dimension masterSize, Dimension targetSize) {
double dScaleWidth = getScaleFactor(masterSize.width, targetSize.width);
double dScaleHeight = getScaleFactor(masterSize.height, targetSize.height);
double dScale = Math.max(dScaleHeight, dScaleWidth);
return dScale;
}
Es relativamente sencillo pasar una imagen (ya sea directamente o mediante un método de soporte). Entonces, por ejemplo, podrías llamar a esto desde tu paint
método
double factor getScaledFactorToFit(new Dimension(image.getWidth(), image.getHeight()), getSize());
int scaledWidth = image.getWidth() * scale;
int scaledHeight *= image.getWidth() * scale;
Esto se encargará automáticamente de la relación de aspecto por usted;)
ACTUALIZADO con ejemplo ampliado
public double getScaleFactor(int iMasterSize, int iTargetSize) {
double dScale = 1;
if (iMasterSize > iTargetSize) {
dScale = (double) iTargetSize / (double) iMasterSize;
} else {
dScale = (double) iTargetSize / (double) iMasterSize;
}
return dScale;
}
public double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
double scaleFactor = Math.min(1d, getScaleFactorToFit(new Dimension(image.getWidth(), image.getHeight()), getSize()));
int scaleWidth = (int) Math.round(image.getWidth() * scaleFactor);
int scaleHeight = (int) Math.round(image.getHeight() * scaleFactor);
Image scaled = image.getScaledInstance(scaleWidth, scaleHeight, Image.SCALE_SMOOTH);
int width = getWidth() - 1;
int height = getHeight() - 1;
int x = (width - scaled.getWidth(this)) / 2;
int y = (height - scaled.getHeight(this)) / 2;
g.drawImage(scaled, x, y, this);
}
Pruebe algo como esto:
import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SG2B2 {
JFrame frame;
public static void main(String[] args) {
SG2B2 gui = new SG2B2();
gui.createUI();
}
public void createUI() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyDrawPanel drawPanel = new MyDrawPanel();
frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
frame.setSize(300, 400);
frame.setVisible(true);
}
class MyDrawPanel extends JPanel {
Image image;
private final String pic = "Logo.jpg";
public MyDrawPanel() {
image = new ImageIcon(pic).getImage();
image = scaleImage(image);
}
@Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(image, 0, 0, this);
}
private Image scaleImage(Image rawImage) {
Image scaledImage = null;
System.out.println("Scaling");
try {
int rawImageWidth = rawImage.getWidth(this);
int rawImageHeight = rawImage.getHeight(this);
int paneWidth = (int) getWidth();
int paneHeight = (int) getHeight();
System.out.println("Image W = " + rawImageWidth
+ ", H = " + rawImageHeight
+ "; Pane W = " + paneWidth
+ ", H = " + paneHeight);
// preserve the original ratio
float widthRatio = (float) rawImageWidth / (float) paneWidth;
float heightRatio = (float) rawImageHeight / (float) paneHeight;
int widthFactor = -1;
int heightFactor = -1;
if ((widthRatio > heightRatio) && (widthRatio > 1.0)) {
widthFactor = paneWidth;
} else if ((heightRatio > widthRatio) && (heightRatio > 1.0)) {
heightFactor = paneHeight;
}
System.out.println("widthRatio = "
+ String.format("%.3f", widthRatio)
+ ", heightRatio = "
+ String.format("%.3f", heightRatio));
System.out.println("widthFactor = " + widthFactor
+ ", heightFactor = " + heightFactor);
if ((widthFactor < 0) && (heightFactor < 0)) {
scaledImage = rawImage;
} else {
scaledImage = rawImage.getScaledInstance(widthFactor, heightFactor,
Image.SCALE_SMOOTH);
// load the new image, 'getScaledInstance' loads asynchronously
MediaTracker tracker = new MediaTracker(this);
tracker.addImage(scaledImage, 0);
tracker.waitForID(0);
}
} catch (InterruptedException ie) {
System.err.println("load interrupt: " + ie.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
return (scaledImage);
}
}
}
que finalmente escalará la imagen al JPanel
tamaño de usandogetScaledInstance(int width, int height, ImageObserver io)
Para cualquier persona interesada, modificar el método PaintComponent de MadProgrammer de la siguiente manera permite una actualización de la pantalla mucho más rápida.
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
super.paintComponent(g);
double scaleFactor = Math.min(1d, getScaleFactorToFit(new Dimension(image.getWidth(), image.getHeight()), getSize()));
int scaleWidth = (int) Math.round(image.getWidth() * scaleFactor);
int scaleHeight = (int) Math.round(image.getHeight() * scaleFactor);
//Image scaled = image.getScaledInstance(scaleWidth, scaleHeight, Image.SCALE_SMOOTH);
int width = getWidth() - 1;
int height = getHeight() - 1;
int x = (width - scaleWidth) / 2;
int y = (height - scaleHeight) / 2;
g2d.drawImage(image, x, y, scaleWidth, scaleHeight, this);
}
Se me ocurrió esta solución:
public class ImageLabel extends JPanel {
private Image image = null;
public void setImage(Image img) {
image = img;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (image != null) {
int imgWidth, imgHeight;
double contRatio = (double) getWidth() / (double) getHeight();
double imgRatio = (double) image.getWidth(this) / (double) image.getHeight(this);
//width limited
if(contRatio < imgRatio){
imgWidth = getWidth();
imgHeight = (int) (getWidth() / imgRatio);
//height limited
}else{
imgWidth = (int) (getHeight() * imgRatio);
imgHeight = getHeight();
}
//to center
int x = (int) (((double) getWidth() / 2) - ((double) imgWidth / 2));
int y = (int) (((double) getHeight()/ 2) - ((double) imgHeight / 2));
g.drawImage(image, x, y, imgWidth, imgHeight, this);
}
}
}