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);
}
Related
Image sample
I have this method written for cropping my image, but HEIGHT dimension doesnt work on the cropping, only weight is done right. I cant find the issue. I want to use my screen width and height as a dynamic width and height.
public Bitmap cropToSquare(Bitmap bitmap){
int width = mScreenWidth ;
int height = mScreenHeight;
int newWidth = width - 2 * 10;
int newHeight = (height- newWidth) / 2;
Bitmap cropImg = Bitmap.createBitmap(bitmap, 0, 0, newWidth, newHeight);
return cropImg; }
}
Here is my solution:
public static Bitmap getResizedClippedBitmap (Bitmap bm, int newWidth, int newHeight) {
Bitmap targetBitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(targetBitmap);
float originalWidth = bm.getWidth();
float originalHeight = bm.getHeight();
float scale, xTranslation = 0.0f, yTranslation = 0.0f;
if (originalWidth > originalHeight) {
scale = newHeight/originalHeight;
xTranslation = (newWidth - originalWidth * scale)/2.0f;
}
else {
scale = newWidth / originalWidth;
yTranslation = (newHeight - originalHeight * scale)/2.0f;
}
Matrix transformation = new Matrix();
transformation.postTranslate(xTranslation, yTranslation);
transformation.preScale(scale, scale);
Paint paint = new Paint();
paint.setFilterBitmap(true);
canvas.drawBitmap(bm, transformation, paint);
return targetBitmap;
}
You can find some other utils here: BitmapUtils.java
Hope it helps.
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);
}
}
}
I have 4608x3456 image that I want to adjust the size of. I want to adjust the resolution of the image to 640x480, yet when I do this the quality of the image drops. Is there a way to decrease image resolution and and still have a better image quality?
Resized Image 640x480 :
Bitmap temobitmpa = loadScaledBitmapFromUri(path,640,480);
return getImageUri(getApplicationContext(), temobitmpa);
public Bitmap loadScaledBitmapFromUri(String filePath, int width, int height) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// calc aspect ratio
int[] retval = calculateAspectRatio(options.outWidth, options.outHeight);
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(options.outWidth,
options.outHeight, width, height);
Log.i("test", "sample size::" + options.inSampleSize);
Bitmap unscaledBitmap = BitmapFactory.decodeFile(filePath, options);
return Bitmap.createScaledBitmap(unscaledBitmap, retval[0], retval[1],
true);
}
private int[] calculateAspectRatio(int origWidth, int origHeight) {
int newWidth = 640;
int newHeight = 480;
// If no new width or height were specified return the original bitmap
if (newWidth <= 0 && newHeight <= 0) {
newWidth = origWidth;
newHeight = origHeight;
}
// Only the width was specified
else if (newWidth > 0 && newHeight <= 0) {
newHeight = (newWidth * origHeight) / origWidth;
}
// only the height was specified
else if (newWidth <= 0 && newHeight > 0) {
newWidth = (newHeight * origWidth) / origHeight;
}
// If the user specified both a positive width and height
// (potentially different aspect ratio) then the width or height is
// scaled so that the image fits while maintaining aspect ratio.
// Alternatively, the specified width and height could have been
// kept and Bitmap.SCALE_TO_FIT specified when scaling, but this
// would result in whitespace in the new image.
else {
double newRatio = newWidth / (double) newHeight;
double origRatio = origWidth / (double) origHeight;
if (origRatio > newRatio) {
newHeight = (newWidth * origHeight) / origWidth;
} else if (origRatio < newRatio) {
newWidth = (newHeight * origWidth) / origHeight;
}
}
int[] retval = new int[2];
retval[0] = newWidth;
retval[1] = newHeight;
return retval;
}
private int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth,
int dstHeight) {
final float srcAspect = (float) srcWidth / (float) srcHeight;
final float dstAspect = (float) dstWidth / (float) dstHeight;
if (srcAspect > dstAspect) {
return srcWidth / dstWidth;
} else {
return srcHeight / dstHeight;
}
}
You can try this image scaling library, it gives a very good image quality.
https://github.com/thebuzzmedia/imgscalr
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);
}
}
}
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);
}
}
}