Image resize - maintain aspect ratio - java

How could I resize an image and still keep it's aspect ratio?
This is the method that I use :
private static BufferedImage resizeImage(BufferedImage originalImage,
int type) {
BufferedImage resizedImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT,
type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, IMG_WIDTH, IMG_HEIGHT, null);
g.dispose();
return resizedImage;
}
The type variable :
BufferedImage original = ImageIO.read(new File(imagePath));
int type = original.getType() == 0 ? BufferedImage.TYPE_INT_ARGB
: original.getType();
The problem is that some images are correctly resized but others lose their aspect ratio because of the IMG_WIDTH and IMG_HEIGHT.
Is there a way to get the original image dimensions and then apply some kind of proportion resize to maintain the aspect ratio of the resized image?

Why don't you use originalImage.getWidth() and originalImage.getHeight()? Then you can easily calculate aspect ratio. Don't forget that int/int = int, so you need to do
double ratio = 1.0 * originalImage.getWidth() / originalImage.getHeight();
or
double ratio = (double) originalImage.getWidth() / originalImage.getHeight();
Regarding the additional math, you can calculate
int height = (int) IMG_WIDTH/ratio;
int width = (int) IMG_HEIGHT*ratio;
Then see which one fits your needs better and resize to (IMG_WIDTH, height) or (width, IMG_HEIGHT)

To get the image size, see getWidth()/getHeight(). The rest is just some relatively simple math.
Presuming the IMG_WIDTH & IMG_HEIGHT represent the largest size desired:
Find which is going to hit the limit first.
Calculate the ratio between the natural size and that maximum size.
Multiply the other image dimension by the same ratio.

Related

Scaling an image with Java

I create an image that is an extract of a PDF and i make an OCR with tesseract on it. Everything works good until a decide to change the dpi of my image. I was excpecting to have an error by doing this and i tried to rescale my image in order to make my OCR work well again.
I have no idea about how I can rescale my image. I know there is some methods with the BufferedImage class but i can't find a way to dynamicly rescale it.
I don't know if I'm clear but imagine a 300 dpi image. If I want to change it to 600 I have to rescale my image to make my OCR work again, my question here is how can I rescale it dynamicly ? Is there a sort of a ratio between the original dpi and the new one that i can use to get a new width and height? Or something else?
To help you understand me here is my code:
public double ratioDPI() {
int ratio = 0;
int minimal_dpi = 300;
int dpi = ERXProperties.intForKey("dpi.image");
return ratio = (dpi/minimal_dpi);
}
public BufferedImage rescale(BufferedImage img) {
int width_img = img.getWidth();
int height_img = img.getHeight();
double factor_width = ERXProperties.doubleForKey("factor.size.width.image.republique.francaise");
double factor_height = ERXProperties.doubleForKey("factor.size.height.image.republique.francaise");
return (BufferedImage) img.getScaledInstance((int)(width_img*ratio), (int)(height_img*ratio), BufferedImage.SCALE_SMOOTH);
}
If you change the DPI of an image, you change the size when outputting it to a printer, for example. If you increase the DPI from 300 to 600, the image in the output only takes up half the width and half the height. If you resize the picture now it only takes up more memory, the quality of the picture would not be better.
For scaling it is best to use AffineTransform, so you can filter the image bilinear so that the pixelation is not so noticeable:
A scaling function:
public static BufferedImage scale(BufferedImage source, double scale, boolean bilinearFiltering){
try{
BufferedImage destination = new BufferedImage((int)(source.getWidth() * scale), (int)(source.getHeight() * scale), source.getType());
AffineTransform at = new AffineTransform();
at.scale(scale, scale);
AffineTransformOp scaleOp = new AffineTransformOp(at, getInterpolationType(bilinearFiltering));
return scaleOp.filter(source, destination);
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}
private static int getInterpolationType(boolean bilinearFiltering){
return bilinearFiltering ? AffineTransformOp.TYPE_BILINEAR : AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
}
Maybe that's a solution for you.

Resize Bitmap to 512x512 Without changing original aspect ratio

I have a created bitmaps. Sizes are not specific. Sometimes 120x60 , 129x800 , 851x784. Its not have a specific value... I want to make these bitmaps resizing to 512x512 always but without changing original images aspect ratio. And without cropping. New image must have canvas 512x512 and original image must be center without any cropping.
I was resizing my bitmaps with this function but it makes images really bad because image fitting X and Y . I don't want image to fit x and y on same time fits one of it and keeps its aspect ratio.
public Bitmap getResizedBitmap(Bitmap bm, int newWidth, int newHeight) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / 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(
bm, 0, 0, width, height, matrix, false);
bm.recycle();
return resizedBitmap;
}
What I have;
What I want;
Ok, so you're really close. I can't test this right now, but basically what needs to be changed is
1) You need to apply the same scale to both X and Y, so you need to pick the smaller one (try the bigger one if that doesn't work).
matrix.postScale(Math.min(scaleWidth, scaleHeight), Math.min(scaleWidth, scaleHeight));
2) The result will be a bitmap where at least one side is 512px large, the other one will be smaller. So you need to add the padding to fit that side to 512px (equally left and right/top and bottom for centering). In order to do so, you need to create an new bitmap of the desired size:
Bitmap outputimage = Bitmap.createBitmap(512,512, Bitmap.Config.ARGB_8888);
3) and lastly depending on what side of the resizedBitmap is 512px you need to draw resizedBitmap to the correct position in outputImage
Canvas can = new Canvas(outputimage);
can.drawBitmap(resizedBitmap, (512 - resizedBitmap.getWidth()) / 2, (512 - resizedBitmap.getHeight()) / 2, null);
Note here, that 512 - resizedBitmap.getWidth() results in 0 and therefor no padding at the side with correct size.
4) Now return outputImage
Here's a simplification in Kotlin that does both the scale and the translation with the matrix, skipping the intermediate bitmap.
Note that it also sets the background color to white for new pixels, which I needed for my image pipeline. Feel free to remove that if you don't need it.
fun resizedBitmapWithPadding(bitmap: Bitmap, newWidth: Int, newHeight: Int) : Bitmap {
val scale = min(newWidth.toFloat() / bitmap.width, newHeight.toFloat() / bitmap.height)
val scaledWidth = scale * bitmap.width
val scaledHeight = scale * bitmap.height
val matrix = Matrix()
matrix.postScale(scale, scale)
matrix.postTranslate(
(newWidth - scaledWidth) / 2f,
(newHeight - scaledHeight) / 2f
)
val outputBitmap = Bitmap.createBitmap(newWidth, newHeight, bitmap.config)
outputBitmap.eraseColor(Color.WHITE)
Canvas(outputBitmap).drawBitmap(
bitmap,
matrix,
null
)
return outputBitmap
}

How to make an image cover as much space as possible in screen without loosing proportions in Java?

I have an image that I want to make as big as possible without losing proportions. I also want it to work in different screen sizes. I am using the following code which doesn't preserve proportions:
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double screen_width = screenSize.getWidth();
double screen_height = screenSize.getHeight();
Image img = ImageIO.read(<PATH_TO_IMAGE>);
img = img.getScaledInstance((int)screen_width, (int)screen_height, Image.SCALE_SMOOTH);
Is there a way to resize an image while preserving proportions and still take as much space as possible on the screen.
You have two choices here: keep the whole image in view but possibly leaving a lot of space uncovered, or cover the whole screen but possibly putting some of the image outsize thus not visible.
In both cases to keep the proportions you need to scale with a single factor.
case a)
BufferedImage img=ImageIO.read(new File(....));
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double screen_width = screenSize.getWidth();
double screen_height = screenSize.getHeight();
double scalex=screen_width/img.getWidth(), scaley=screen_height/img.getHeight();
double scale=Math.min(scalex, scaley);
int w=(int)(scale*img.getWidth()), h=(int)(scale*img.getHeight());
BufferedImage img2=new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
img2.getGraphics().drawImage(img, 0, 0, w, h, null);
case b)
BufferedImage img=ImageIO.read(new File(....));
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double screen_width = screenSize.getWidth();
double screen_height = screenSize.getHeight();
double scalex=screen_width/img.getWidth(), scaley=screen_height/img.getHeight();
double scale=Math.max(scalex, scaley);
int w=(int)(scale*img.getWidth()), h=(int)(scale*img.getHeight());
BufferedImage img2=new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
img2.getGraphics().drawImage(img, 0, 0, w, h, null);
Assuming you are using Swing, you can use the Stretch Icon. With the StretchIcon you can configure it to:
fill the entire space available, or
keep the image proportions and scale as much as possible.
So you would :
create the StretchIcon with your image
add the Icon to a JLabel
add the label to the BorderLayout.CENTER of your JFrame
Now as the frame is resize the Icon is automatically resized.

OutOfMemoryError: Jave heap space when jtable saved as Image

Currently I am saving a jtable as jpeg using the below method, when the dimension of the jtable became 2590, 126181, java.lang.OutOfMemoryError: Java heap space exception occurs at "BufferedImage constructor", when the size of the table is small the image gets saved successfully.
public BufferedImage saveComponentAsJPEG(JTable table, String filename) {
Dimension size = table.getSize();
BufferedImage myImage =
new BufferedImage(size.width, size.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = myImage.createGraphics();
table.paint(g2);
return myImage;
}
How to save a jtable with bigger size in pdf or jpeg image?
Updated Info:
You asked how to "split the JTable into different small images":
As you go through my code below please read my comments, they help explain what is happening and will help you grasp a better understanding of how a JTable/JComponent can be painted to lots of small images. At the heart my code is similar to yours, but there are two key points:
1) Rather than create a single large BufferedImage, I create a single small image that is then used multiple times, therefore leaving a very small memory footprint.
2) With the single image, I use Graphics.translate() to paint a small part of the JTable each time.
The following code was tested with a large JComponents (2590 x 126181) and a tile size of 200x200, and the whole process did not exceed 60mb of memory:
//width = width of tile in pixels, for minimal memory usage try 200
//height = height of tile in pixels, for minimal memory usage try 200
//saveFileLocation = folder to save image tiles
//component = The JComponent to save as tiles
public static boolean saveComponentTiles(int width, int height, String saveFileLocation, JComponent component)
{
try
{
//Calculate tile sizes
int componentWidth = component.getWidth();
int componentHeight = component.getHeight();
int horizontalTiles = (int) Math.ceil((double)componentWidth / width); //use (double) so Math.ceil works correctly.
int verticalTiles = (int) Math.ceil((double)componentHeight / height); //use (double) so Math.ceil works correctly.
System.out.println("Tiles Required (H, W): "+horizontalTiles+", verticalTiles: "+verticalTiles);
//preset arguments
BufferedImage image;
//Loop through vertical and horizontal tiles
//Draw part of the component to the image
//Save image to file
for (int h = 0; h < verticalTiles; h++)
{
for (int w = 0; w < horizontalTiles; w++)
{
//check tile size, if area to paint is smaller than image then shrink image
int imageHeight = height;
int imageWidth = width;
if (h + 1 == verticalTiles)
{
imageHeight = componentHeight - (h * height);
}
if (w + 1 == horizontalTiles)
{
imageWidth = componentWidth - (w * width);
}
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
//translate image graphics so that only the correct part of the component is panted to the image
g.translate(-(w * width), -(h * height));
component.paint(g);
//In my example I am saving the image to file, however you could throw your PDF processing code here
//Files are named as "Image.[h].[w]"
//Example: Image 8 down and 2 accross would save as "Image.8.2.png"
ImageIO.write(image, "png", new File(saveFileLocation + "Image." + h +"."+ w + ".png"));
//tidy up
g.dispose();
}
}
return true;
}
catch (IOException ex)
{
return false;
}
}
Just call it like so:
boolean result = saveComponentTiles(200, 200, saveFileLocation, jTable1);
Also if you haven't done it already, you should only call the method from a different thread because it will hang your application when dealing with large components.
If you have not picked a PDF library yet, then I highly recommend looking at iText.
Original Post:
The process you are looking for is quite simple, however it may take some work.
You were on the right track thinking about parts, but as David
mentioned you shouldn't mess with the jTable, instead you will need a
to make use of the TiledImage class, or do something yourself with
RenderedImage and Rasters.
This sort of method basically uses HDD space instead of memory and
lets you create a large image in lots of smaller parts/tiles, then
when its done you can save it all to a single image file.
This answer may also help: https://stackoverflow.com/a/14069551/1270000

scale an image keeping the aspect ratio without falling below the targets

I wonder if anybody can help me with the math/pseudo code/java code to scale an image to a target dimension. the requirement is to keep the aspect ratio, but not falling below the target dimension on both x and y scales. the final calculated dimension can be greater than the requested target but it needs to be the the closest one to the target.
example:
I have an image that is 200x100. it needs to be scaled down to a target dimension 30x10.
i need to find the minimal dimension that keeps the aspect ratio of the origin where both x and y scales are at least what is specified in the target.
in our example, 20x10 is not good because the x scale fell below the target (which is 30).
the closest one would be 30x15
Thank you.
targetRatio = targetWidth / targetHeight;
sourceRatio = sourceWidth / sourceHeight;
if(sourceRatio >= targetRatio){ // source is wider than target in proportion
requiredWidth = targetWidth;
requiredHeight = requiredWidth / sourceRatio;
}else{ // source is higher than target in proportion
requiredHeight = targetHeight;
requiredWidth = requiredHeight * sourceRatio;
}
This way your final image :
always fits inside the target whereas not being cropped.
keeps its original aspect ratio.
and always has either the width or height (or both) exactly matching the target's.
Well in your example you kind off already used the algorithm you're looking for.
I will use the example you have given.
Original Target
200 x 100 -> 30 x 10
1. You take the bigger value of the target dimensions (in our case 30)
2. Check if its smaller than the corresponding original width or height
2.1 If its smaller define this as the new width (So 30 is the new width)
2.2 If its not smaller check the other part
3. Now we have to calculate the height which is simply the (30/200)*100
So as result you get like you wrote: 30 x 15
Hope this was clear :)
In the coding part you could use the BufferedImage and simply create a new BufferedImage with the correct scale value like that.
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); // <-- Here you should use the calculated scale factors
AffineTransformOp scaleOp =
new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
after = scaleOp.filter(before, after);

Categories