java swing - dynamically fit jlabel to jframe [duplicate] - java

I have a JPanel with a painted background image and a layout manager holding other smaller images, all of this inside a JFrame. The background image is pretty big and I want to be able to have it maintain its aspect ratio whether its on a big or small monitor.
Eventually, I want to be able to have my LayoutManager and the smaller images in its cells "glued" to the background picture.
I looked around for resources and it seems that many examples use a BufferedImage but I am not; will this pose a problem? I'll post my code below for painting the image, If I lack any information please let me know.
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);
}
}
EDIT: I should mention that I know the aspect ratio formula:
original height / original width x new width = new height
However, I do not know how to use that correctly to my advantage.

Well, the quickest and easiest solution is to use Image.getScaledInstance
g.drawImage(img.getScaledInstance(newWidth, -1, Image. SCALE_SMOOTH), x, y, this);
If your wondering about the negative number, the java docs say:
If either width or height is a negative number then a value is
substituted to maintain the aspect ratio of the original image
dimensions. If both width and height are negative, then the original
image dimensions are used.
UPDATE
Just as a side note (my Google was playing up).
getScaledInstance is neither the fastest or highest quality approach, but it is the easiest.
Take a read through The Perils of Image.getScaledInstance for some more ideas
UPDATE
Scaling an image to fit an area is slightly more complicated then simply scaling the aspect ratio. You have to make a choice over if you want the image to "fit" within the area (possibly leaving blank areas around it) or over "fill" the area (so that it's smallest dimension fits the largest dimension of the area).
Fit & Fill
Basically, I work with scale factors
This returns the scaling factor for a particular size. I use this to make decisions about which factor I want to use based which algorithm I need
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;
}
It's used by these two methods. They simply take two Dimensions. The original and the target.
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;
}
It's relatively simple to pass an image into (either directly or via a support method). So for example, you could call this from within your paint method
double factor getScaledFactorToFit(new Dimension(image.getWidth(), image.getHeight()), getSize());
int scaledWidth = image.getWidth() * scale;
int scaledHeight *= image.getWidth() * scale;
This will automatically take care of the aspect ratio for you ;)
UPDATED with expanded example
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);
}

Try something like this:
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);
}
}
}
which will ultimately scale the image to the JPanel's size by using getScaledInstance(int width, int height, ImageObserver io)

For anyone interested ammending the PaintComponent method by MadProgrammer as follows allows much quicker display update
#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);
}

I came up with this solution:
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);
}
}
}

Related

Unable to get and set the circular Icon

I am new to programming and I've been trying to get and set the icon to circle but it's not being updated. I want to set every icon that was set to JLabel will become circular.
class CircleLabel extends JLabel {
private Icon icon;
private int borderSize;
#Override
protected void paintComponent(Graphics g) {
if (getIcon() != null) {
icon = getIcon();
int width = getWidth();
int height = getHeight();
int diameter = Math.min(width, height);
int x = width / 2 - diameter / 2;
int y = height / 2 - diameter / 2;
int border = borderSize * 2;
diameter -= border;
Dimension size = getAutoSize(icon, diameter);
BufferedImage img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2_img = img.createGraphics();
g2_img.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2_img.fillOval(0, 0, diameter, diameter);
Composite composite = g2_img.getComposite();
g2_img.setComposite(AlphaComposite.SrcIn);
g2_img.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2_img.drawImage(toImage(icon), 0, 0, size.width, size.height, null);
g2_img.setComposite(composite);
g2_img.dispose();
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(new Color(255, 255, 255));
diameter += border;
g2.fillOval(borderSize, borderSize, diameter, diameter);
g2.drawImage(img, x - borderSize, y + borderSize, null);
} else {
System.out.println("No Icon!");
}
super.paintComponent(g);
}
private Dimension getAutoSize(Icon image, int size) {
int w = size;
int h = size;
int iw = image.getIconWidth();
int ih = image.getIconHeight();
double xScale = (double) w / iw;
double yScale = (double) h / iw;
System.out.println(xScale);
System.out.println(yScale);
double scale = Math.max(xScale, yScale);
int width = (int) (scale * iw);
int height = (int) (scale * ih);
if (width < 1) {
width = 1;
}
if (height < 1) {
height = 1;
}
return new Dimension(width, height);
}
private Image toImage(Icon icon) {
return ((ImageIcon) icon).getImage();
}
}
If I set the icon manually and removed the icon from the label, it will work but when I add the icon to the label through properties, it will not work.
icon = new ImageIcon(getClass().getResource("/Attachments/user.png"));
I found this on YouTube and wanted to apply it to the application I'm trying to build.

Image background on JFrame [duplicate]

I have a JPanel with a painted background image and a layout manager holding other smaller images, all of this inside a JFrame. The background image is pretty big and I want to be able to have it maintain its aspect ratio whether its on a big or small monitor.
Eventually, I want to be able to have my LayoutManager and the smaller images in its cells "glued" to the background picture.
I looked around for resources and it seems that many examples use a BufferedImage but I am not; will this pose a problem? I'll post my code below for painting the image, If I lack any information please let me know.
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);
}
}
EDIT: I should mention that I know the aspect ratio formula:
original height / original width x new width = new height
However, I do not know how to use that correctly to my advantage.
Well, the quickest and easiest solution is to use Image.getScaledInstance
g.drawImage(img.getScaledInstance(newWidth, -1, Image. SCALE_SMOOTH), x, y, this);
If your wondering about the negative number, the java docs say:
If either width or height is a negative number then a value is
substituted to maintain the aspect ratio of the original image
dimensions. If both width and height are negative, then the original
image dimensions are used.
UPDATE
Just as a side note (my Google was playing up).
getScaledInstance is neither the fastest or highest quality approach, but it is the easiest.
Take a read through The Perils of Image.getScaledInstance for some more ideas
UPDATE
Scaling an image to fit an area is slightly more complicated then simply scaling the aspect ratio. You have to make a choice over if you want the image to "fit" within the area (possibly leaving blank areas around it) or over "fill" the area (so that it's smallest dimension fits the largest dimension of the area).
Fit & Fill
Basically, I work with scale factors
This returns the scaling factor for a particular size. I use this to make decisions about which factor I want to use based which algorithm I need
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;
}
It's used by these two methods. They simply take two Dimensions. The original and the target.
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;
}
It's relatively simple to pass an image into (either directly or via a support method). So for example, you could call this from within your paint method
double factor getScaledFactorToFit(new Dimension(image.getWidth(), image.getHeight()), getSize());
int scaledWidth = image.getWidth() * scale;
int scaledHeight *= image.getWidth() * scale;
This will automatically take care of the aspect ratio for you ;)
UPDATED with expanded example
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);
}
Try something like this:
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);
}
}
}
which will ultimately scale the image to the JPanel's size by using getScaledInstance(int width, int height, ImageObserver io)
For anyone interested ammending the PaintComponent method by MadProgrammer as follows allows much quicker display update
#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);
}
I came up with this solution:
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);
}
}
}

Java Animations

I've started to take interest with making animations(slideshows, backgrounds etc) in Java. I know that JavaFX is much better for doing this, but I'm just to stubborn to bother switching over.
Here is what I got so far.
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BlurredLightCells extends JPanel {
private static final long serialVersionUID = 4610174943257637060L;
private Random random = new Random();
private ArrayList<LightCell> lightcells;
private float[] blurData = new float[500];
public static void main(String[] args) {
JFrame frame = new JFrame("Swing animated bubbles");
frame.setSize(1000, 750);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new BlurredLightCells(60));
frame.setVisible(true);
}
public BlurredLightCells(int amtOfBCells) {
setSize(1000, 750);
/**
* Below we initiate all the cells that are going to be drawn on screen
*/
Arrays.fill(blurData, 1f / 20f);
lightcells = new ArrayList<LightCell>(amtOfBCells);
for (int i = 0; i < amtOfBCells; i++) {
/**
* Below we generate all the values for each cell(SHOULD be random for each one)
*/
int baseSpeed = random(0, 3);
int xSpeed = (int) Math.floor((Math.random() * (baseSpeed - -baseSpeed + baseSpeed)) + -baseSpeed);
int ySpeed = (int) Math.round((Math.random() * baseSpeed) + 0.5);
int radius = random(25, 100);
int x = (int) Math.floor(Math.random() * getWidth());
int y = (int) Math.floor(Math.random() * getHeight());
int blurrAmount = (int) (Math.floor(Math.random() * 10) + 5);
int alpha = (int) ((Math.random() * 15) + 3);
/**
* Now we draw a image, and apply transparency and a slight blur to it
*/
Kernel kernel = new Kernel(blurrAmount, blurrAmount, blurData);
BufferedImageOp op = new ConvolveOp(kernel);
BufferedImage circle = new BufferedImage(150, 150, BufferedImage.TYPE_INT_ARGB);
Graphics2D circlegfx = circle.createGraphics();
circlegfx.setColor(new Color(255, 255, 255, alpha));
circlegfx.fillOval(20, 20, radius, radius);
circle = op.filter(circle, null);
LightCell bubble = new LightCell(x, y, xSpeed, ySpeed, radius, getDirection(random.nextInt(3)), circle);
lightcells.add(bubble);
}
}
public int random(int min, int max) {
final int n = Math.abs(max - min);
return Math.min(min, max) + (n == 0 ? 0 : random.nextInt(n));
}
#Override
public void paint(Graphics g) {
int w = getWidth();
int h = getHeight();
final Graphics2D g2 = (Graphics2D) g;
GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
g2.setPaint(gp);
g2.fillRect(0, 0, w, h);
long start = System.currentTimeMillis();
for (int i = 0; i < lightcells.size(); i++) {
LightCell cell = lightcells.get(i);
cell.process(g2);
}
System.out.println("Took " + (System.currentTimeMillis() - start) + " milliseconds to draw ALL cells.");
repaint();
}
public String getDirection(int i) {
switch (i) {
case 0:
return "right";
case 1:
return "left";
case 2:
return "up";
case 3:
return "down";
}
return "";
}
private class LightCell {
private int x, y, xSpeed, ySpeed, radius;
private String direction;
private BufferedImage image;
public LightCell(int x, int y, int xSpeed, int ySpeed, int radius, String direction, BufferedImage image) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.radius = radius;
this.direction = direction;
this.image = image;
}
public void process(Graphics g) {
switch (direction) {
case "right":
moveRight();
break;
case "left":
moveLeft();
break;
case "up":
moveUp();
break;
case "down":
moveDown();
break;
}
g.drawImage(image, x, y, null);
}
private void moveUp() {
x += xSpeed;
y -= ySpeed;
if (y + (radius / 2) < 0) {
y = getHeight() + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
y = radius + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveDown() {
x += xSpeed;
y += ySpeed;
if (y - (radius / 2) > getHeight()) {
y = 0 - (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
y = getHeight() + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveRight() {
x += ySpeed;
y += xSpeed;
if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
x = 0 - (radius / 2);
y = (int) Math.floor(Math.random() * getHeight());
}
if ((x - radius / 2) > getWidth()) {
x = 0 - (radius / 2);
y = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveLeft() {
x -= ySpeed;
y -= xSpeed;
if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
x = getWidth() + (radius / 2);
y = (int) Math.floor(Math.random() * getHeight());
}
if ((x + radius / 2) < 0) {
x = getWidth() + (radius / 2);
y = (int) Math.floor(Math.random() * getWidth());
}
}
}
}
If you run that, you will see the cells move at a very high speed, and if you look through the code, you see I call repaint() in the paint method in which I override. I know thats not good to do. But my question is, is their any other way in which I could draw each cell outside of the repaint() loop I have right now, because that causes other components to flash/flicker when I use this in a JFrame with other components.
FYI: Aventually I'd like to achieve something similar to this: Click Here
Thanks!
The issue of flicker is to do with the fact that top level containers are not double buffered. Instead of extending from JFrame (or other top level containers), you should consider using something more like JPanel and override it's paintComponent.
nb- Had it in my head that the OP was extending from JFrame...
Two issues could be causing the flickering. The first is overriding paint, the second is not calling super.paint(g) and the time between the updates. A better solution would be to override paintComponent and make sure you are calling super.paintComponent. Also using something like a javax.swing.Timer to schedule updates and regular intervals would also help...
Only call repaint when you want to encourage the RepaintManager to update you component. Don't call repaint from within any paintXxx method, this will cause a never ending loop of paint requests to schedule onto the event queue, eventually consuiming your CPU
I would avoid doing anything in your paintXxx methods that might take time to perform, this will slow down the rendering process. Instead, I would use a javax.swing.Timer for simple updates or for more complicated processing, a Thread which could be used to update the model before it is rendered to the screen.
This is an example of some simple optimisation process I did to take animation of 500 objects to 4500 with only a slight degration in the overall performance.
Updated
I changed you code slight and it works fine...
I changed your paint method to paintComponent and added super.paintComponent(g)
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeight();
final Graphics2D g2 = (Graphics2D) g;
GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
g2.setPaint(gp);
g2.fillRect(0, 0, w, h);
for (int i = 0; i < lightcells.size(); i++) {
LightCell cell = lightcells.get(i);
cell.process(g2);
}
}
And at the end of your constructor I added...
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.start();
To update the UI on a regular bases...

Convert image to thumbnail and maintain aspect ratio [duplicate]

I have a JPanel with a painted background image and a layout manager holding other smaller images, all of this inside a JFrame. The background image is pretty big and I want to be able to have it maintain its aspect ratio whether its on a big or small monitor.
Eventually, I want to be able to have my LayoutManager and the smaller images in its cells "glued" to the background picture.
I looked around for resources and it seems that many examples use a BufferedImage but I am not; will this pose a problem? I'll post my code below for painting the image, If I lack any information please let me know.
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);
}
}
EDIT: I should mention that I know the aspect ratio formula:
original height / original width x new width = new height
However, I do not know how to use that correctly to my advantage.
Well, the quickest and easiest solution is to use Image.getScaledInstance
g.drawImage(img.getScaledInstance(newWidth, -1, Image. SCALE_SMOOTH), x, y, this);
If your wondering about the negative number, the java docs say:
If either width or height is a negative number then a value is
substituted to maintain the aspect ratio of the original image
dimensions. If both width and height are negative, then the original
image dimensions are used.
UPDATE
Just as a side note (my Google was playing up).
getScaledInstance is neither the fastest or highest quality approach, but it is the easiest.
Take a read through The Perils of Image.getScaledInstance for some more ideas
UPDATE
Scaling an image to fit an area is slightly more complicated then simply scaling the aspect ratio. You have to make a choice over if you want the image to "fit" within the area (possibly leaving blank areas around it) or over "fill" the area (so that it's smallest dimension fits the largest dimension of the area).
Fit & Fill
Basically, I work with scale factors
This returns the scaling factor for a particular size. I use this to make decisions about which factor I want to use based which algorithm I need
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;
}
It's used by these two methods. They simply take two Dimensions. The original and the target.
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;
}
It's relatively simple to pass an image into (either directly or via a support method). So for example, you could call this from within your paint method
double factor getScaledFactorToFit(new Dimension(image.getWidth(), image.getHeight()), getSize());
int scaledWidth = image.getWidth() * scale;
int scaledHeight *= image.getWidth() * scale;
This will automatically take care of the aspect ratio for you ;)
UPDATED with expanded example
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);
}
Try something like this:
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);
}
}
}
which will ultimately scale the image to the JPanel's size by using getScaledInstance(int width, int height, ImageObserver io)
For anyone interested ammending the PaintComponent method by MadProgrammer as follows allows much quicker display update
#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);
}
I came up with this solution:
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);
}
}
}

Resize image, maintain aspect ratio

I have an image which I resize:
if((width != null) || (height != null))
{
try{
// Scale image on disk
BufferedImage originalImage = ImageIO.read(file);
int type = originalImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB
: originalImage.getType();
BufferedImage resizedImageJpg = resizeImage(originalImage, type, 200, 200);
ImageIO.write(resizedImageJpg, "jpg", file);
} catch(IOException e) {
System.out.println(e.getMessage());
}
}
This is how I resize the image:
private static BufferedImage resizeImage(BufferedImage originalImage, int type,
Integer imgWidth, Integer imgHeight)
{
var resizedImage = new BufferedImage(imgWidth, imgHeight, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, imgWidth, imgHeight, null);
g.dispose();
return resizedImage;
}
Now the problem is, I also need to maintain aspect ratio. That is, I need the new 200/200 image to contain the new image scaled. Something like this:
I tried some things but they didn't work out as expected.
Any help is appreciated.
Here we go:
Dimension imgSize = new Dimension(500, 100);
Dimension boundary = new Dimension(200, 200);
Function to return the new size depending on the boundary:
public static Dimension getScaledDimension(Dimension imgSize, Dimension boundary) {
int original_width = imgSize.width;
int original_height = imgSize.height;
int bound_width = boundary.width;
int bound_height = boundary.height;
int new_width = original_width;
int new_height = original_height;
// first check if we need to scale width
if (original_width > bound_width) {
//scale width to fit
new_width = bound_width;
//scale height to maintain aspect ratio
new_height = (new_width * original_height) / original_width;
}
// then check if we need to scale even with the new height
if (new_height > bound_height) {
//scale height to fit instead
new_height = bound_height;
//scale width to maintain aspect ratio
new_width = (new_height * original_width) / original_height;
}
return new Dimension(new_width, new_height);
}
In case anyone also needs the image resizing code, here is a decent solution.
If you're unsure about the above solution, there are different ways to achieve the same result.
Translated from here:
Dimension getScaledDimension(Dimension imageSize, Dimension boundary) {
double widthRatio = boundary.getWidth() / imageSize.getWidth();
double heightRatio = boundary.getHeight() / imageSize.getHeight();
double ratio = Math.min(widthRatio, heightRatio);
return new Dimension((int) (imageSize.width * ratio),
(int) (imageSize.height * ratio));
}
You can also use imgscalr to resize images while maintaining aspect ratio:
BufferedImage resizeMe = ImageIO.read(new File("orig.jpg"));
Dimension newMaxSize = new Dimension(255, 255);
BufferedImage resizedImg = Scalr.resize(resizeMe, Method.QUALITY,
newMaxSize.width, newMaxSize.height);
You will want to check out Image.getScaledInstance(), and more in this answer: How to improve the performance of g.drawImage() method for resizing images
Load image:
BufferedImage bufferedImage = ImageIO.read(file);
Resize it:
private BufferedImage resizeAndCrop(BufferedImage bufferedImage, Integer width, Integer height) {
Mode mode = (double) width / (double) height >= (double) bufferedImage.getWidth() / (double) bufferedImage.getHeight() ? Scalr.Mode.FIT_TO_WIDTH
: Scalr.Mode.FIT_TO_HEIGHT;
bufferedImage = Scalr.resize(bufferedImage, Scalr.Method.ULTRA_QUALITY, mode, width, height);
int x = 0;
int y = 0;
if (mode == Scalr.Mode.FIT_TO_WIDTH) {
y = (bufferedImage.getHeight() - height) / 2;
} else if (mode == Scalr.Mode.FIT_TO_HEIGHT) {
x = (bufferedImage.getWidth() - width) / 2;
}
bufferedImage = Scalr.crop(bufferedImage, x, y, width, height);
return bufferedImage;
}
Using Scalr library:
<dependency>
<groupId>org.imgscalr</groupId>
<artifactId>imgscalr-lib</artifactId>
<version>4.2</version>
</dependency>
Here's a small piece of code that I wrote, it resizes the image to fit the container while keeping the image's original aspect ratio. It takes in as parameters the container's width, height and the image. You can modify it to fit your needs.
It's simple and works fine for my applications.
private Image scaleimage(int wid, int hei, BufferedImage img){
Image im = img;
double scale;
double imw = img.getWidth();
double imh = img.getHeight();
if (wid > imw && hei > imh){
im = img;
}else if(wid/imw < hei/imh){
scale = wid/imw;
im = img.getScaledInstance((int) (scale*imw), (int) (scale*imh), Image.SCALE_SMOOTH);
}else if (wid/imw > hei/imh){
scale = hei/imh;
im = img.getScaledInstance((int) (scale*imw), (int) (scale*imh), Image.SCALE_SMOOTH);
}else if (wid/imw == hei/imh){
scale = wid/imw;
im = img.getScaledInstance((int) (scale*imw), (int) (scale*imh), Image.SCALE_SMOOTH);
}
return im;
}
try this
float rateX = (float)jpDisplayImagen.getWidth()/(float)img.getWidth();
float rateY = (float)jpDisplayImagen.getHeight()/(float)img.getHeight();
if (rateX>rateY){
int W=(int)(img.getWidth()*rateY);
int H=(int)(img.getHeight()*rateY);
jpDisplayImagen.getGraphics().drawImage(img, 0, 0,W,H, null);
}
else{
int W=(int)(img.getWidth()*rateX);
int H=(int)(img.getHeight()*rateX);
jpDisplayImagen.getGraphics().drawImage(img, 0, 0,W,H, null);
}
public class ImageTransformation {
public static final String PNG = "png";
public static byte[] resize(FileItem fileItem, int width, int height) {
try {
ResampleOp resampleOp = new ResampleOp(width, height);
BufferedImage scaledImage = resampleOp.filter(ImageIO.read(fileItem.getInputStream()), null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(scaledImage, PNG, baos);
return baos.toByteArray();
} catch (Exception ex) {
throw new MapsException("An error occured during image resizing.", ex);
}
}
public static byte[] resizeAdjustMax(FileItem fileItem, int maxWidth, int maxHeight) {
try {
BufferedInputStream bis = new BufferedInputStream(fileItem.getInputStream());
BufferedImage bufimg = ImageIO.read(bis);
//check size of image
int img_width = bufimg.getWidth();
int img_height = bufimg.getHeight();
if(img_width > maxWidth || img_height > maxHeight) {
float factx = (float) img_width / maxWidth;
float facty = (float) img_height / maxHeight;
float fact = (factx>facty) ? factx : facty;
img_width = (int) ((int) img_width / fact);
img_height = (int) ((int) img_height / fact);
}
return resize(fileItem,img_width, img_height);
} catch (Exception ex) {
throw new MapsException("An error occured during image resizing.", ex);
}
}
}
This is my solution:
/*
Change dimension of Image
*/
public static Image resizeImage(Image image, int scaledWidth, int scaledHeight, boolean preserveRatio) {
if (preserveRatio) {
double imageHeight = image.getHeight();
double imageWidth = image.getWidth();
if (imageHeight/scaledHeight > imageWidth/scaledWidth) {
scaledWidth = (int) (scaledHeight * imageWidth / imageHeight);
} else {
scaledHeight = (int) (scaledWidth * imageHeight / imageWidth);
}
}
BufferedImage inputBufImage = SwingFXUtils.fromFXImage(image, null);
// creates output image
BufferedImage outputBufImage = new BufferedImage(scaledWidth, scaledHeight, inputBufImage.getType());
// scales the input image to the output image
Graphics2D g2d = outputBufImage.createGraphics();
g2d.drawImage(inputBufImage, 0, 0, scaledWidth, scaledHeight, null);
g2d.dispose();
return SwingFXUtils.toFXImage(outputBufImage, null);
}
All other answers show how to calculate the new image height in function of the new image width or vice-versa and how to resize the image using Java Image API. For those people who are looking for a straightforward solution I recommend any java image processing framework that can do this in a single line.
The exemple below uses Marvin Framework:
// 300 is the new width. The height is calculated to maintain aspect.
scale(image.clone(), image, 300);
Necessary import:
import static marvin.MarvinPluginCollection.*
I have found the selected answer to have problems with upscaling, and so I have made (yet) another version (which I have tested):
public static Point scaleFit(Point src, Point bounds) {
int newWidth = src.x;
int newHeight = src.y;
double boundsAspectRatio = bounds.y / (double) bounds.x;
double srcAspectRatio = src.y / (double) src.x;
// first check if we need to scale width
if (boundsAspectRatio < srcAspectRatio) {
// scale width to fit
newWidth = bounds.x;
//scale height to maintain aspect ratio
newHeight = (newWidth * src.y) / src.x;
} else {
//scale height to fit instead
newHeight = bounds.y;
//scale width to maintain aspect ratio
newWidth = (newHeight * src.x) / src.y;
}
return new Point(newWidth, newHeight);
}
Written in Android terminology :-)
as for the tests:
#Test public void scaleFit() throws Exception {
final Point displaySize = new Point(1080, 1920);
assertEquals(displaySize, Util.scaleFit(displaySize, displaySize));
assertEquals(displaySize, Util.scaleFit(new Point(displaySize.x / 2, displaySize.y / 2), displaySize));
assertEquals(displaySize, Util.scaleFit(new Point(displaySize.x * 2, displaySize.y * 2), displaySize));
assertEquals(new Point(displaySize.x, displaySize.y * 2), Util.scaleFit(new Point(displaySize.x / 2, displaySize.y), displaySize));
assertEquals(new Point(displaySize.x * 2, displaySize.y), Util.scaleFit(new Point(displaySize.x, displaySize.y / 2), displaySize));
assertEquals(new Point(displaySize.x, displaySize.y * 3 / 2), Util.scaleFit(new Point(displaySize.x / 3, displaySize.y / 2), displaySize));
}
Just add one more block to Ozzy's code so the thing looks like this:
public static Dimension getScaledDimension(Dimension imgSize,Dimension boundary) {
int original_width = imgSize.width;
int original_height = imgSize.height;
int bound_width = boundary.width;
int bound_height = boundary.height;
int new_width = original_width;
int new_height = original_height;
// first check if we need to scale width
if (original_width > bound_width) {
//scale width to fit
new_width = bound_width;
//scale height to maintain aspect ratio
new_height = (new_width * original_height) / original_width;
}
// then check if we need to scale even with the new height
if (new_height > bound_height) {
//scale height to fit instead
new_height = bound_height;
//scale width to maintain aspect ratio
new_width = (new_height * original_width) / original_height;
}
// upscale if original is smaller
if (original_width < bound_width) {
//scale width to fit
new_width = bound_width;
//scale height to maintain aspect ratio
new_height = (new_width * original_height) / original_width;
}
return new Dimension(new_width, new_height);
}

Categories

Resources