Resize image while keeping aspect ratio in Java - java

im trying to resize bufferdImage in memory in java but to keep the aspect ratio of the image
im have something like this but this is not good
int w = picture.getWidth();
int h = picture.getWidth();
int neww=w;
int newh=h;
int wfactor = w;
int hfactor = h;
if(w > DEFULT_PICTURE_WIDTH || h > DEFULT_PICTURE_HIGHT)
{
while(neww > DEFULT_PICTURE_WIDTH)
{
neww = wfactor /2;
newh = hfactor /2;
wfactor = neww;
hfactor = newh;
}
}
picture = Utils.resizePicture(picture,neww,newh);

Adding to Erik's point about getScaledInstance, if you moved away from it to using the recommended scaling mechanisms in Java2D, you might have noticed that your images look noticeably worse.
The reason for that is when the Java2D discouraged use of getScaledInstance and AreaAveragingScaleFilter, they didn't replace it with anything as easy to use in the API, instead we were left to our own devices using Java2D APIs directly. Fortunately, Chris Campbell (from the J2D team) followed up with the recommendation of using an incremental scaling technique that gives similar looking results to AreaAveragingScaleFilter and runs faster; unfortunately the code is of a decent size and doesn't address your original question of honoring proportions.
About 6 months ago I saw all these questions on SO again and again about "scaling images in Java" and eventually collected all the advice, did all the digging and research I could, and compiled all of into a single "best practices" image scaling library.
The API is dead simple as it is only 1 class and a bunch of static methods. Basic use looks like this:
BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, 320);
This is the simplest call where the library will make a best-guess at the quality, honor your image proportions, and fit the result within a 320x320 bounding box. NOTE, the bounding box is just the maximum W/H used, since your image proportions are honored, the resulting image would still honor that, say 320x200.
If you want to override the automatic mode and force it to give you the best-looking result and even apply a very mild anti-alias filter to the result so it looks even better (especially good for thumbnails), that call would look like:
BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, Method.QUALITY,
150, 100, Scalr.OP_ANTIALIAS);
These are all just examples, the API is broad and covers everything from super-simple use cases to very specialized. You can even pass in your own BufferedImageOps to be applied to the image (and the library automatically fixes the 6-year BufferedImageOp JDK bug for you!)
There is a lot more to scaling images in Java successfully that the library does for you, for example always keeping the image in one of the best supported RGB or ARGB image types while operating on it. Under the covers the Java2D image processing pipeline falls back to an inferior software pipeline if the image type used for any image operations is poorly supported.
If all that sounded like a lot of headache, it sort of is... that's why I wrote the library and open sourced it, so folks could just resize their images and move on with their lives without needing to worry about it.

If width, height of source and target are known, use following function to determine scale of the image.
private double determineImageScale(int sourceWidth, int sourceHeight, int targetWidth, int targetHeight) {
double scalex = (double) targetWidth / sourceWidth;
double scaley = (double) targetHeight / sourceHeight;
return Math.min(scalex, scaley);
}
Then use this scale to scale up/down the image using following code
Image scaledImage = sourceBufferedImage.getScaledInstance((int) (width * scale), (int) (height * scale), Image.SCALE_SMOOTH);

For starters - take a look at line 2. Shouldnt that be getHeight()?
You dont want a while loop for the resizing, you want to find out the resizing ratio, which is a simple bit of math.
(width / height) = (new_width / new_height)
If you know one of the 'new' sizes, the other can be found via multiplication
new_height * (width / height) = new_width
You can also use the lazy method provided by BufferedImage's superclass Image, getScaledInstance() - using -1 for either width or height will maintain aspect ratio
ex:
scaledPic = picture.getScaledInstance(new_width, -1, Image.SCALE_FAST);

You may have a look at perils-of-image-getscaledinstance.html that explains why getScaledInstance(), used in some of the answers, should be avoided.
The article also provides alternative code.

I use these two methods to scale images, where max is the bigger dimension of your destination image. For 100x100 image it will be 100, for 200x300 image it will be 300.
public static BufferedImage scale(InputStream is, int max) {
Image image = null;
try {
image = ImageIO.read(is);
} catch (IOException e) {
e.printStackTrace();
}
int width = image.getWidth(null);
int height = image.getHeight(null);
double dWidth = 0;
double dHeight = 0;
if (width == height) {
dWidth = max;
dHeight = max;
}
else if (width > height) {
dWidth = max;
dHeight = ((double) height / (double) width) * max;
}
else {
dHeight = max;
dWidth = ((double) width / (double) height) * max;
}
image = image.getScaledInstance((int) dWidth, (int) dHeight, Image.SCALE_SMOOTH);
BufferedImage bImage = toBufferedImage(image);
return bImage;
}
public static BufferedImage toBufferedImage(Image img)
{
if (img instanceof BufferedImage)
{
return (BufferedImage) img;
}
BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics2D bGr = bimage.createGraphics();
bGr.drawImage(img, 0, 0, null);
bGr.dispose();
return bimage;
}

If you want to resize a picture of w0 x h0 to w1 x h1 by keeping the aspect ratio, then calculate the vertical and horizontal scale and select the smaller one.
double scalex = 1;
double scaley = 1;
if (scalingMode == ScalingMode.WINDOW_SIZE) {
scalex = (double)getWidth() / frontbuffer.getWidth();
scaley = (double)getHeight() / frontbuffer.getHeight();
} else
if (scalingMode == ScalingMode.KEEP_ASPECT) {
double sx = (double)getWidth() / frontbuffer.getWidth();
double sy = (double)getHeight() / frontbuffer.getHeight();
scalex = Math.min(sx, sy);
scaley = scalex;
// center the image
g2.translate((getWidth() - (frontbuffer.getWidth() * scalex)) / 2,
(getHeight() - (frontbuffer.getHeight() * scaley)) / 2);
}
g2.scale(scalex, scaley);
if (interpolation != ImageInterpolation.NONE) {
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolation.hint);
}
g2.drawImage(frontbuffer, 0, 0, null);

private static BufferedImage resize(BufferedImage img, int width, int height) {
double scalex = (double) width / img.getWidth();
double scaley = (double) height / img.getHeight();
double scale = Math.min(scalex, scaley);
int w = (int) (img.getWidth() * scale);
int h = (int) (img.getHeight() * scale);
Image tmp = img.getScaledInstance(w, h, Image.SCALE_SMOOTH);
BufferedImage resized = new BufferedImage(w, h, img.getType());
Graphics2D g2d = resized.createGraphics();
g2d.drawImage(tmp, 0, 0, null);
g2d.dispose();
return resized;
}

Related

Java - How to pad and resize image without cropping?

I need to resize alot of images from the ratio aspect (2:3) to (3:4).
The images are 800px x 1200px currently. I need them to be 600px x 800px eventually without any cropping.
May I know what libraries are available for me to do padding and resizing without cropping in Java?
From your current Image (assuming a java.awt.Image) you can use :
Image.getScaledInstance(w,h,h) as method
Image.SCALE_SMOOTH as algorithm for resize
And these steps:
compute the ratios in width and in height
depending on their values (padding width or padding height)
compute the width and height to obtain the scaled image
compute the padding required
write the image at the good position
static BufferedImage pad(BufferedImage image, double width, double height, Color pad) {
double ratioW = image.getWidth() / width;
double ratioH = image.getHeight() / height;
double newWidth = width, newHeight = height;
int fitW = 0, fitH = 0;
BufferedImage resultImage;
Image resize;
//padding width
if (ratioW < ratioH) {
newWidth = image.getWidth() / ratioH;
newHeight = image.getHeight() / ratioH;
fitW = (int) ((width - newWidth) / 2.0);
}//padding height
else if (ratioH < ratioW) {
newWidth = image.getWidth() / ratioW;
newHeight = image.getHeight() / ratioW;
fitH = (int) ((height - newHeight) / 2.0);
}
resize = image.getScaledInstance((int) newWidth, (int) newHeight, Image.SCALE_SMOOTH);
resultImage = new BufferedImage((int) width, (int) height, image.getType());
Graphics g = resultImage.getGraphics();
g.setColor(pad);
g.fillRect(0, 0, (int) width, (int) height);
g.drawImage(resize, fitW, fitH, null);
g.dispose();
return resultImage;
}
To use as
BufferedImage image = ...;
BufferedImage result = pad(image, 600, 800, Color.white);
Managed to do it using below code:
'w' is the amount of padding you need on each side.
BufferedImage newImage = new BufferedImage(image.getWidth()+2*w, image.getHeight(),
image.getType());
Graphics g = newImage.getGraphics();
g.setColor(Color.white);
g.fillRect(0,0,image.getWidth()+2*w,image.getHeight());
g.drawImage(image, w, 0, null);
g.dispose();
I think ffmpeg can help you to do anything with image.
e.g. Use ffmpeg to resize image
You can keep ffmpeg binaries in some conf folder.
Create sh script for ffmpeg command.
Use CommandLine from (Apache Commons exec library) to run the script.

Infinite while loop in Java application

In my Java application i am trying to resize an image until it fits a required dimension but still keeping its original scale.
the following method is used for resizing the image.
public BufferedImage ImageScaler(BufferedImage image){
scaledWidth = (int) image.getWidth() / SCALE_FACTOR * 100;
scaledHeight = (int) image.getHeight() / SCALE_FACTOR * 100;
scaledImage = resize(image, scaledHeight, scaledWidth);
while(scaledHeight > 150){
scaledHeight = scaledImage.getHeight();
scaledWidth = scaledImage.getWidth();
scaledImage = resize(image, scaledHeight, scaledWidth);
scaledHeight = scaledImage.getHeight();
scaledWidth = scaledImage.getWidth();
}
return scaledImage;
}
the while loop goes into an infinte loop. please help.
resize method.
private static BufferedImage resize(BufferedImage img, int height, int width) {
java.awt.Image tmp = img.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH);
BufferedImage resized = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = resized.createGraphics();
g2d.drawImage(tmp, 0, 0, null);
g2d.dispose();
return resized;
}
Your code doesn't actually change anything. Suppose that when it goes into the loop, the width and height are both 200; the first two lines merely set scaledHeight and scaledWidth to 200, the third edits the image to conform to said variables, and then the fourth and fifth line set it to.. Exactly the same dimensions again. If after the initial resizing before the loop scaledHeight doesn't reach 150, it never will.

Scaling down and making image square in android

I need a way to scale an image down to 78x78. I have found ways of doing this by cutting part of the image off, like this:
Bitmap image = Bitmap.createBitmap(image, 0, 0, 78, 78);
but I need to maintain as much of the image as possible. I had thought of scaling the image down and then making it square:
Bitmap image = Bitmap.createScaledBitmap(imageTest, 78, 78, true);
but of course this creates a square image that is squashed.
Can anyone suggest how I can create a 78x78 image that doesn't rescale and maintains as much of the original image as possible?
From what I understood, you should scale down and center crop the image. Try this code out.
public Bitmap scaleCenterCrop(Bitmap source, int newHeight, int newWidth) {
int sourceWidth = source.getWidth();
int sourceHeight = source.getHeight();
// Compute the scaling factors to fit the new height and width, respectively.
// To cover the final image, the final scaling will be the bigger
// of these two.
float xScale = (float) newWidth / sourceWidth;
float yScale = (float) newHeight / sourceHeight;
float scale = Math.max(xScale, yScale);
// Now get the size of the source bitmap when scaled
float scaledWidth = scale * sourceWidth;
float scaledHeight = scale * sourceHeight;
// Let's find out the upper left coordinates if the scaled bitmap
// should be centered in the new size give by the parameters
float left = (newWidth - scaledWidth) / 2;
float top = (newHeight - scaledHeight) / 2;
// The target rectangle for the new, scaled version of the source bitmap will now
// be
RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
// Finally, we create a new bitmap of the specified size and draw our new,
// scaled bitmap onto it.
Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
Canvas canvas = new Canvas(dest);
canvas.drawBitmap(source, null, targetRect, null);
return dest;
}
Hope it helps
Try this:
Bitmap image = Bitmap.createScaledBitmap(testImage, (int) 78 * (testImage.getWidth() / testImage.getHeight()), 78, true);
image = Bitmap.createBitmap(image, (int) (image.getWidth() - 78) / 2, 78);
Haven't tested this, as I'm on my way to bed, but it should accomplish what you want, so long as your image has a width greater than or equal to its height.
Regardless, I'd suggest you use BufferedImage instead of Bitmap.
The idea here would be resize your image using the same resize rate for width and height keeping the smaller size in 78. After that you can use a center point based crop to get the middle of your image and making it a squared image.
Image srcImage;
int widthSrc = 150;
int heightSrc = 180;
float resizeRate = 78 / min(widthSrc, heightSrc);
Image resizedImage = resizeImage($srcImage, resizeRate);
int widthDest = 78;
int heightDest = 78;
int cropX = ($widthSrc - $widthDest)/2;
int cropY = ($heightSrc - $heightDest)/2;
Image croppedImage = cropImage(resizedImage,$widthDest, $heightDest, $cropX, $cropY);
If the image is already square you can skip the crop part.

How to re-scale bitmap to different screen sizes but keep it square?

Hello great people of stackoverflow.com, I have a stumbled upon a great difficulty in my code; that being not being able to resize my images in-line with my screen size.
I have an image that is SAY 65 pixels by 65 pixels but I want this image to be a certain percentage of the screen say 6% while keeping square.
My brain cannot process the mathematics for this (because it is slow :-( )
(_width & _height is the screen width and height)
What I've got so far is:
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int avg_screen_dimension = (_width + _height) / 2;
float scaleWidth =
((float) width + (avg_screen_dimension - _width)) / width;
float scaleHeight =
((float) height + (avg_screen_dimension - _height)) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// RECREATE THE NEW BITMAP
Bitmap resizedBitmap =
Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
return resizedBitmap;
Any help would be greatly appreciated!
It's little more than simple algebra - no complex math needed.
You'll have to choose between fraction of the width or height of your screen, unless they're equal.
Let h = screen height, w = screen width, f = fraction of screen dimension, x = image size.
Then x = h*f. If you want to make it a fraction of the width, substitute w for h.
(Just as an aside - in your pasted code, you've mixed up the variable names : you declare them as height and width but pass them as _width and _height

How to scale a BufferedImage

Following the javadocs, I have tried to scale a BufferedImage without success here is my code:
BufferedImage image = MatrixToImageWriter.getBufferedImage(encoded);
Graphics2D grph = image.createGraphics();
grph.scale(2.0, 2.0);
grph.dispose();
I can't understand why it is not working, any help?
AffineTransformOp offers the additional flexibility of choosing the interpolation type.
BufferedImage before = getBufferedImage(encoded);
int w = before.getWidth();
int h = before.getHeight();
BufferedImage after = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
AffineTransform at = new AffineTransform();
at.scale(2.0, 2.0);
AffineTransformOp scaleOp =
new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
after = scaleOp.filter(before, after);
The fragment shown illustrates resampling, not cropping; this related answer addresses the issue; some related examples are examined here.
Unfortunately the performance of getScaledInstance() is very poor if not problematic.
The alternative approach is to create a new BufferedImage and and draw a scaled version of the original on the new one.
BufferedImage resized = new BufferedImage(newWidth, newHeight, original.getType());
Graphics2D g = resized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(original, 0, 0, newWidth, newHeight, 0, 0, original.getWidth(),
original.getHeight(), null);
g.dispose();
newWidth,newHeight indicate the new BufferedImage size and have to be properly calculated.
In case of factor scaling:
int newWidth = new Double(original.getWidth() * widthFactor).intValue();
int newHeight = new Double(original.getHeight() * heightFactor).intValue();
EDIT: Found the article illustrating the performance issue: The Perils of Image.getScaledInstance()
Using imgscalr – Java Image Scaling Library:
BufferedImage image =
Scalr.resize(originalImage, Scalr.Method.BALANCED, newWidth, newHeight);
https://github.com/rkalla/imgscalr
To scale an image, you need to create a new image and draw into it. One way is to use the filter() method of an AffineTransferOp, as suggested here. This allows you to choose the interpolation technique.
private static BufferedImage scale1(BufferedImage before, double scale) {
int w = before.getWidth();
int h = before.getHeight();
// Create a new image of the proper size
int w2 = (int) (w * scale);
int h2 = (int) (h * scale);
BufferedImage after = new BufferedImage(w2, h2, BufferedImage.TYPE_INT_ARGB);
AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
AffineTransformOp scaleOp
= new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);
scaleOp.filter(before, after);
return after;
}
Another way is to simply draw the original image into the new image, using a scaling operation to do the scaling. This method is very similar, but it also illustrates how you can draw anything you want in the final image. (I put in a blank line where the two methods start to differ.)
private static BufferedImage scale2(BufferedImage before, double scale) {
int w = before.getWidth();
int h = before.getHeight();
// Create a new image of the proper size
int w2 = (int) (w * scale);
int h2 = (int) (h * scale);
BufferedImage after = new BufferedImage(w2, h2, BufferedImage.TYPE_INT_ARGB);
AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
AffineTransformOp scaleOp
= new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);
Graphics2D g2 = (Graphics2D) after.getGraphics();
// Here, you may draw anything you want into the new image, but we're
// drawing a scaled version of the original image.
g2.drawImage(before, scaleOp, 0, 0);
g2.dispose();
return after;
}
Addendum: Results
To illustrate the differences, I compared the results of the five methods below. Here is what the results look like, scaled both up and down, along with performance data. (Performance varies from one run to the next, so take these numbers only as rough guidelines.) The top image is the original. I scale it double-size and half-size.
As you can see, AffineTransformOp.filter(), used in scaleBilinear(), is faster than the standard drawing method of Graphics2D.drawImage() in scale2(). Also BiCubic interpolation is the slowest, but gives the best results when expanding the image. (For performance, it should only be compared with scaleBilinear() and scaleNearest().) Bilinear seems to be better for shrinking the image, although it's a tough call. And NearestNeighbor is the fastest, with the worst results. Bilinear seems to be the best compromise between speed and quality. The Image.getScaledInstance(), called in the questionable() method, performed very poorly, and returned the same low quality as NearestNeighbor. (Performance numbers are only given for expanding the image.)
public static BufferedImage scaleBilinear(BufferedImage before, double scale) {
final int interpolation = AffineTransformOp.TYPE_BILINEAR;
return scale(before, scale, interpolation);
}
public static BufferedImage scaleBicubic(BufferedImage before, double scale) {
final int interpolation = AffineTransformOp.TYPE_BICUBIC;
return scale(before, scale, interpolation);
}
public static BufferedImage scaleNearest(BufferedImage before, double scale) {
final int interpolation = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
return scale(before, scale, interpolation);
}
#NotNull
private static
BufferedImage scale(final BufferedImage before, final double scale, final int type) {
int w = before.getWidth();
int h = before.getHeight();
int w2 = (int) (w * scale);
int h2 = (int) (h * scale);
BufferedImage after = new BufferedImage(w2, h2, before.getType());
AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
AffineTransformOp scaleOp = new AffineTransformOp(scaleInstance, type);
scaleOp.filter(before, after);
return after;
}
/**
* This is a more generic solution. It produces the same result, but it shows how you
* can draw anything you want into the newly created image. It's slower
* than scaleBilinear().
* #param before The original image
* #param scale The scale factor
* #return A scaled version of the original image
*/
private static BufferedImage scale2(BufferedImage before, double scale) {
int w = before.getWidth();
int h = before.getHeight();
// Create a new image of the proper size
int w2 = (int) (w * scale);
int h2 = (int) (h * scale);
BufferedImage after = new BufferedImage(w2, h2, before.getType());
AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
AffineTransformOp scaleOp
= new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);
Graphics2D g2 = (Graphics2D) after.getGraphics();
// Here, you may draw anything you want into the new image, but we're just drawing
// a scaled version of the original image. This is slower than
// calling scaleOp.filter().
g2.drawImage(before, scaleOp, 0, 0);
g2.dispose();
return after;
}
/**
* I call this one "questionable" because it uses the questionable getScaledImage()
* method. This method is no longer favored because it's slow, as my tests confirm.
* #param before The original image
* #param scale The scale factor
* #return The scaled image.
*/
private static Image questionable(final BufferedImage before, double scale) {
int w2 = (int) (before.getWidth() * scale);
int h2 = (int) (before.getHeight() * scale);
return before.getScaledInstance(w2, h2, Image.SCALE_FAST);
}
As #Bozho says, you probably want to use getScaledInstance.
To understand how grph.scale(2.0, 2.0) works however, you could have a look at this code:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
class Main {
public static void main(String[] args) throws IOException {
final int SCALE = 2;
Image img = new ImageIcon("duke.png").getImage();
BufferedImage bi = new BufferedImage(SCALE * img.getWidth(null),
SCALE * img.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics2D grph = (Graphics2D) bi.getGraphics();
grph.scale(SCALE, SCALE);
// everything drawn with grph from now on will get scaled.
grph.drawImage(img, 0, 0, null);
grph.dispose();
ImageIO.write(bi, "png", new File("duke_double_size.png"));
}
}
Given duke.png:
it produces duke_double_size.png:
If you do not mind using an external library, Thumbnailator can perform scaling of BufferedImages.
Thumbnailator will take care of handling the Java 2D processing (such as using Graphics2D and setting appropriate rendering hints) so that a simple fluent API call can be used to resize images:
BufferedImage image = Thumbnails.of(originalImage).scale(2.0).asBufferedImage();
Although Thumbnailator, as its name implies, is geared toward shrinking images, it will do a decent job enlarging images as well, using bilinear interpolation in its default resizer implementation.
Disclaimer: I am the maintainer of the Thumbnailator library.
scale(..) works a bit differently. You can use bufferedImage.getScaledInstance(..)

Categories