Reputation: 4363
Sometimes my images are too big and I get this error:
Exception in thread "main" java.lang.NegativeArraySizeException at java.awt.image.DataBufferByte.(Unknown Source) at java.awt.image.Raster.createInterleavedRaster(Unknown Source) at java.awt.image.BufferedImage.(Unknown Source)
Whenever I get this I want to adjust my image to the highest possible size while maintaining the ratio.
I ended up with the following formulas:
if ( targetWidth * targetHeight >= Integer.MAX_VALUE || targetWidth * targetHeight < 0 ) {
System.out.println( "Target image too big... Size will be adjusted!" );
if ( targetWidth > targetHeight ) {
targetWidth = (int)Math.sqrt( ( Integer.MAX_VALUE ) * (float)( targetWidth / targetHeight ) );
targetHeight = ( Integer.MAX_VALUE ) / targetWidth;
} else {
targetHeight = (int)Math.sqrt( ( Integer.MAX_VALUE ) * (float)( targetHeight / targetWidth ) );
targetWidth = ( Integer.MAX_VALUE ) / targetHeight;
}
}
I still get the same problem, and my conditions are satisfied. I guess that
WIDTH * HEIGHT < Integer.MAX_VALUE
Is clearly not the condition I am looking for Any help?
Edit: After some discussion I think the real question to this problem is: What is the biggest possible size that I can pass to the BufferedImage constructor in order to not get a NegativeArraySizeException at:
at java.awt.image.DataBufferByte.(Unknown Source)
Upvotes: 0
Views: 670
Reputation: 11463
BufferedImage
limit is the same limit of having a byte[Integer.MAX_VALUE]
due to limitations in the Raster
class (ref). You also have the overhead of the header which is platform and implementation dependent. That's why I recommend a safety buffer of the length of a long
.
(Integer.MAX_VALUE - 8) / 4
should be a good safe limit.
NOTE: You MUST account for the size of each pixel (ref). For example, BufferedImage.TYPE_4BYTE_ABGR
is 4 bytes per pixel. That means your area limit is Integer.MAX_VALUE / 4
in that case. Of course, the number of bytes consumed by each pixel will vary for the type you use. Adjust your test max area by the number of bytes to represent each pixel. You'll have to look at the API to figure that out: https://docs.oracle.com/javase/7/docs/api/java/awt/image/BufferedImage.html.
In order to resize an image while retaining the aspect ratio the math is pretty simple:
double aspectRatio = width / height;
if (aspectRatio < 1) {
// taller than wide
targetHeight = maxDimension;
targetWidth = (int)(targetHeight * aspectRatio);
} else {
// wider than tall
targetWidth = maxDimension;
targetHeight = (int)(targetWidth / aspectRatio);
}
That leaves is the question of calculating maxDimension
based off of the total area of the image. For the sake of argument, let's say our maximum area is Integer.MAX_VALUE - 8
(relatively safe). We can extrapolate this using algebra. We know width * height = area
, and with the formula above, we have two ways we need to solve for our max area.
For aspectRatio < 1
we substitute width with the formula to get it:
height * height * aspectRatio = area
Solving for height:
height^2 * aspectRatio = area
height^2 = area / aspectRatio
height = Math.sqrt(area / aspectRatio
For asptectRatio >= 1
we substitute height with the formulat to get it:
width * width / aspectRatio = area
Solving for width:
Now we can update the basic formula to account for a shape in the max area:
public static final long MAX_AREA = (Integer.MAX_VALUE - 8) / 4;
if (aspectRatio < 1) {
targetHeight = Math.sqrt(MAX_AREA / aspectRatio);
targetWidth = (int)(targetHeight * aspectRatio);
} else {
// wider than tall
targetWidth = Math.sqrt(MAX_AREA * aspectRatio);
targetHeight = (int)(targetWidth / aspectRatio);
}
Of course, this leaves the basic question of testing if you are above your max area threshold. That needs to be done with something other than an int
.
public static final long MAX_AREA = (Integer.MAX_VALUE - 8) / 4;
long area = (long)width * (long)height;
if(area < MAX_AREA) {
// recalculate size
}
Otherwise you will get an overflow issue.
Upvotes: 1