BufferedImage getSubimage throws exception with seemingly good arguments - java

Java 8 here. Trying to stick with the BufferedImage API and not delve into JavaFx land.
I have a JPG image that is 768 pixel wide and 432 pixels in height. I want to crop a centered 400x400 pixel square out of the center of it using BufferedImage#getSubimage(...).
I have the following code:
BufferedImage image = ImageIO.read(imageTempFile);
int dim = 400;
int xCropBuffer, yCropBuffer;
xCropBuffer = (image.getWidth() - dim) / 2;
yCropBuffer = (image.getHeight() - dim) / 2;
log.info("width = " + image.getWidth() + ", height = " + image.getHeight() + ", dim = " + dim + ", xCropBuffer = " + xCropBuffer + ", yCropBuffer = " + yCropBuffer);
image = image.getSubimage(xCropBuffer, yCropBuffer + dim, dim, dim);
At runtime this throws the following exception:
java.awt.image.RasterFormatException: (y + height) is outside of Raster
at sun.awt.image.ByteInterleavedRaster.createWritableChild(ByteInterleavedRaster.java:1248)
at java.awt.image.BufferedImage.getSubimage(BufferedImage.java:1202)
at java_awt_image_BufferedImage$getSubimage.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:149)
at com.myapp.DefaultMediaService.uploadImage(DefaultMediaService.java:56)
at com.myapp.MediaService$uploadImage.call(Unknown Source)
And the logs print the following message right before the exception is thrown:
width = 768, height = 432, dim = 400, xCropBuffer = 184, yCropBuffer = 16
So I'm passing in image.getSubimage(184, 416, 400, 400) arguments...shouldn't this be OK?! The image is 768x432, so (184, 416) should be a valid coordinate for its upper-left corner, and 184 + 400 < 768 and 416 - 400 > 0. So all these parameters should map to a valid 400x400 rectangle inside the image, right?

Drawing an image is not like drawing a text, where the ascend runs from baseline to smaller y coordinates. The image is specified by its upper left corner and extending towards the greater coordinates for both, x and y. Hence, you should not specify + dim for the y coordinate.
BufferedImage oldImage = ImageIO.read(imageFile),
newImage = oldImage.getSubimage(
(oldImage.getWidth()-dim)/2, (oldImage.getHeight()-dim)/2, dim, dim);
ImageIO.write(newImage, "png", imageFile);
For completeness, since there seem to have been some confusion in your previous question, here some alternative for achieves the same image transformation, which can be used as template for other image operations:
via BufferedImageOp
BufferedImage oldImage = ImageIO.read(imageFile),
newImage = new BufferedImage(dim, dim, oldImage.getType());
BufferedImageOp op = new AffineTransformOp(
AffineTransform.getTranslateInstance(
-(oldImage.getWidth()-dim)/2, -(oldImage.getHeight()-dim)/2),
AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
op.filter(oldImage, newImage);
ImageIO.write(newImage, "png", imageFile);
via Graphics
BufferedImage oldImage = ImageIO.read(imageFile),
newImage = new BufferedImage(dim, dim, oldImage.getType());
Graphics2D gfx = newImage.createGraphics();
gfx.drawImage(oldImage,
-(oldImage.getWidth()-dim)/2, -(oldImage.getHeight()-dim)/2, null);
gfx.dispose();
ImageIO.write(newImage, "png", imageFile);
In either case, the cropping to a desired size is implied by making the target image smaller than the original one.
Then, the source image only has to get translated by (-x, -y) to select the desired detail rectangle.

This seems pretty simple. The height is 432, and you are specifying the upper-left corner as (182, 416). Next, you ask it to get 400 pixels down. So you get 432 < 416 + 400 which means that the bottom of the frame you want to select is greater than the height of the image.

Related

Resize Java BufferedImage keeping aspect ratio and fill with background

I'm working with Java to store and modify .jpg images, (not Android or Swing). I want to transform an image with a new dimension keeping the aspect ratio and filling the background with white color if the new dimension is not proportional to the original image.
BufferedImage image = /*Here i read from disk and load the image */;
image = resizeImage(image, newWidth, newHeight);
ImageIO.write(image, extension, new File(filename + "_" + page + "." + extension));
The function I'm trying to implement is resizeImage: in the example resizes the image but it doesn't keep the aspect ratio.
private static BufferedImage resizeImage(BufferedImage originalImage, int width, int height) {
BufferedImage resizedImage = new BufferedImage(width, height, originalImage.getType());
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, width, height, null);
g.dispose();
return resizedImage;
}
I think a picture will be more illustrative of what I'm asking for:
If the original image is 200x200 and is asked to resize to 400x300 the result should be a picture with white margin and the original picture resized inside. In this example should be 300x300.
The problem is not how to resize, it's how to fill the remaining image with white and the original resized image on the center of it.
This code worked for me:
private static BufferedImage resizeImage(BufferedImage originalImage, int newWidth, int newHeight) {
BufferedImage resizedImage = new BufferedImage(newWidth, newHeight, originalImage.getType());
Graphics2D graphics = resizedImage.createGraphics();
graphics.setColor(Color.WHITE);
// fill the entire picture with white
graphics.fillRect(0, 0, newWidth, newHeight);
int maxWidth = 0;
int maxHeight = 0;
// go along the width and height in increments of 1 until one value is equal to the specified resize value
while (maxWidth <= newWidth && maxHeight <= newHeight)
{
++maxWidth;
++maxHeight;
}
// calculate the x value with which the original image is centred
int centerX = (resizedImage.getWidth() - maxWidth) / 2;
// calculate the y value with which the original image is centred
int centerY = (resizedImage.getHeight() - maxHeight) / 2;
// draw the original image
graphics.drawImage(originalImage, centerX, centerY, maxWidth, maxHeight, null);
graphics.dispose();
return resizedImage;
}
Before:
After:

Rotating Image with AffineTransform Produces Visually Correct Pixels, but getRGB Doesn't Match

I'm attempting to rotate an image 180 using AffineTransform. My "out.jpg" visually appears rotated, however when I attempt to programatically verify that the output image has been rotated , the code disagrees. Here's my code:
public static void main(String[] args) throws Exception {
BufferedImage origImage = ImageIO.read(new File("input.jpg"));
AffineTransform af = new AffineTransform();
int centerX = origImage.getWidth() / 2;
int centerY = origImage.getHeight() / 2;
// Rotate 180 degrees
af.quadrantRotate(2, centerX, centerY);
AffineTransformOp affineTransformOp = new AffineTransformOp(af, null);
BufferedImage destImage =
new BufferedImage(origImage.getWidth(), origImage.getHeight(), origImage.getType());
affineTransformOp.filter(origImage, destImage);
// Verify that destImage has indeed been rotated by point checking a random pixel.
int origX = 90;
int origY = 32;
// Where we expect this pixel to have been translated to.
int expX = destImage.getWidth()-origX;
int expY = destImage.getHeight()-origY;
// Always prints false. Why????
System.out.println("Pixels are equal: " +
origImage.getRGB(origX, origY) == destImage.getRGB(expX, expY));
ImageIO.write(destImage, "jpg", new File("out.jpg"));
}
What am I doing wrong with either my rotation or the programatic check?
I've verified in the documentation:
getRGB() returns an int, there's no need to use .equals()
BufferedImage indexes from (0,0) in the upper left corner
AffineTransform.quadrantRotate() with an anchor, translates back to (0,0) once it completes the rotation
AffineTransform.quadrantRotate() goes clockwise (although this shouldn't matter for 180 rotation)
Thanks for your help!
You are looking at the wrong pixel.
Assume your image is 1x1 pixel in size. You're originally looking at pixel (0, 0). Then with your code, in the destination image, you're looking at pixel (width - 0, height - 1), which turns out to be pixel (1, 1). When clearly you still need to be looking at (0, 0) as the image doesn't have a pixel at (1, 1).
In your case, your transformed coordinate isn't outside your image, but it points to the wrong pixel.
To fix your code, change the lines that calculate your expX and expY values, and subtract 1 from each:
int expX = destImage.getWidth() - origX - 1;
int expY = destImage.getHeight() - origY - 1;
That returns the correct pixel values when I run your code (on my own image, as you haven't provided the image that you used).
There is another issue in the code that will only surface with an odd-numbered width or height: you're using integer arithmetic to determine the center of rotation, which rounds down when it shouldn't. Change the lines that determine the center to (to use floating-point numbers, so that you can get a fractional number as a result) :
double centerX = origImage.getWidth() / 2.0;
double centerY = origImage.getHeight() / 2.0;
I ran your code using a png image of mine. Here are my test results. Image type 6 is TYPE_4BYTE_ABGR from the BufferedImage source code.
Image size: 416 x 199
Image type: 6
Origin pixel: 90,32
Destination pixel: 326,167
Pixel values: ffeeeeee ffffffff
Pixels are equal: false
Here's the image.
Here's the rotated image.
I'm not sure why the colors are different. I can't visually distinguish the difference between an eeeeee pixel and an ffffff pixel.
Edited to add: I did a little more research and found this bit of documentation for the Image getRGB method.
Returns an integer pixel in the default RGB color model(TYPE_INT_ARGB)
and default sRGB colorspace. Color conversion takes place if this
default model does not match the image ColorModel.
So, it appears that the getRGB method itself could be causing the color conversion.
Here's the code I ran.
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
public class RotateImage {
public static void main(String[] args) throws Exception {
String input = "C:\\Users\\Owner\\OneDrive\\Pictures"
+ "\\Saved Pictures\\CustomJTextField.png";
BufferedImage origImage = ImageIO.read(new File(input));
AffineTransform af = new AffineTransform();
int centerX = origImage.getWidth() / 2;
int centerY = origImage.getHeight() / 2;
// Rotate 180 degrees
af.quadrantRotate(2, centerX, centerY);
AffineTransformOp affineTransformOp = new AffineTransformOp(
af, null);
System.out.println("Image size: " + origImage.getWidth() +
" x " + origImage.getHeight());
System.out.println("Image type: " + origImage.getType());
BufferedImage destImage = new BufferedImage(
origImage.getWidth(), origImage.getHeight(),
origImage.getType());
affineTransformOp.filter(origImage, destImage);
// Verify that destImage has indeed been rotated by point
// checking a random
// pixel.
int origX = 90;
int origY = 32;
// Where we expect this pixel to have been translated to.
int expX = destImage.getWidth() - origX;
int expY = destImage.getHeight() - origY;
System.out.println("Origin pixel: " + origX + "," + origY);
System.out.println("Destination pixel: " + expX + "," + expY);
int origPixel = origImage.getRGB(origX, origY);
int expPixel = destImage.getRGB(expX, expY);
System.out.println("Pixel values: " +
Integer.toHexString(origPixel) + " " +
Integer.toHexString(expPixel));
// Always prints false. Why????
System.out.println("Pixels are equal: " +
(origPixel == expPixel));
String output = "C:\\Eclipse\\Eclipse-2020-workspace"
+ "\\com.ggl.testing2\\resources\\output.png";
ImageIO.write(destImage, "png", new File(output));
}
}
Edited to add: I wrote my own transform to see whether or not it was the transform changing the color or the getRGB method changing the color.
Here are the test results from my own transform.
Image size: 416 x 199
Image type: 6
Origin pixel: 90,32
Destination pixel: 326,167
Pixel values: ffeeeeee ffeeeeee
Pixels are equal: true
I created an int array of pixels from the original image and wrote those pixels to the rotated image.
Here's the code.
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
public class RotateImage {
public static void main(String[] args) throws Exception {
String input = "C:\\Users\\Owner\\OneDrive\\Pictures"
+ "\\Saved Pictures\\CustomJTextField.png";
BufferedImage origImage = ImageIO.read(new File(input));
System.out.println("Image size: " + origImage.getWidth() +
" x " + origImage.getHeight());
System.out.println("Image type: " + origImage.getType());
int[] pixels = getPixels(origImage);
BufferedImage destImage = new BufferedImage(
origImage.getWidth(), origImage.getHeight(),
origImage.getType());
destImage = putPixels(destImage, pixels);
// Verify that destImage has indeed been rotated by point
// checking a random
// pixel.
int origX = 90;
int origY = 32;
// Where we expect this pixel to have been translated to.
int expX = destImage.getWidth() - origX;
int expY = destImage.getHeight() - origY;
System.out.println("Origin pixel: " + origX + "," + origY);
System.out.println("Destination pixel: " + expX + "," + expY);
int origPixel = origImage.getRGB(origX, origY);
int expPixel = destImage.getRGB(expX, expY);
System.out.println("Pixel values: " +
Integer.toHexString(origPixel) + " " +
Integer.toHexString(expPixel));
System.out.println("Pixels are equal: " +
(origPixel == expPixel));
String output = "C:\\Eclipse\\Eclipse-2020-workspace"
+ "\\com.ggl.testing2\\resources\\output.png";
ImageIO.write(destImage, "png", new File(output));
}
private static int[] getPixels(BufferedImage image) {
int length = image.getWidth() * image.getHeight();
int[] pixels = new int[length];
int index = 0;
for (int h = 0; h < image.getHeight(); h++) {
for (int w = 0; w < image.getWidth(); w++) {
pixels[index++] = image.getRGB(w, h);
}
}
return pixels;
}
private static BufferedImage putPixels(BufferedImage image,
int[] pixels) {
int index = 0;
for (int i = pixels.length - 1; i >= 0; i--) {
int h = index / image.getWidth();
int w = index % image.getWidth();
image.setRGB(w, h, pixels[i]);
index++;
}
return image;
}
}

Draw a rectangle around image

I want to draw a rectangle around BufferedImage so it will create a border like frame.
So I load 2 BufferedImage:
BufferedImage a = ImageIO.read(new File(aPath));
BufferedImage b = ImageIO.read(new File(bPath));
And send it for drawing:
private void drawImageBorder(BufferedImage imageWithoutBorder) {
Graphics2D graph = imageWithoutBorder.createGraphics();
graph.setColor(Color.BLACK);
//create a black Rectangle - 1px bigger the original image
graph.fill(new Rectangle(imageWithoutBorder.getMinX(), imageWithoutBorder.getMinY(), imageWithoutBorder.getWidth() + 1, imageWithoutBorder.getHeight() +1));
//draw the image inside it
graph.drawImage(imageWithoutBorder, 0, 0, null);
graph.dispose();
}
For some reason it does nothing, there are similer questions like drawing-filled-rectangle-over-a-bufferedimage but I could not finnd helpful answers.
Thanks.
Almost right, but for the enlarged size and positioning.
BufferedImage image = ImageIO.read(new File(imagePath));
int w = image.getWidth();
int h = Image.getHeight();
int border = 1;
BufferedImage framedImage = new BufferedImage(w + 2*border, h + 2*border, image.getType());
Graphics2D graph = framedImage.createGraphics();
graph.setColor(Color.BLACK);
graph.fill(new Rectangle(0, 0, w + 2*border, h + 2*border));
graph.drawImage(image, border, border, null);
graph.dispose();
Possible reason can be, that you don't persist the changes made to the image, for example writing them back into an image file with ImageIO.write.

Problems with rotating a BufferedImage

I'm trying to rotate an image clockwise and counter clockwise. With the help of this answer from Sri Harsha Chilakapati I managed to get it working according to my needs. This is the code
String rotateURL = tmp_dir + "/" + strusername + "rotate.jpg";
File fileJPG = new File(tmp_dir + "/" + strusername + "FullSize.jpg");
fullImage = ImageIO.read(fileJPG);
leftImage = rotateLeft(fullImage, 90);
writeImageToFile(leftImage, "jpg", new File(rotateURL));
public static BufferedImage rotateLeft(BufferedImage img, double angle)
{
double sin = Math.abs(Math.sin(Math.toRadians(angle))),
cos = Math.abs(Math.cos(Math.toRadians(angle)));
int w = img.getWidth(null), h = img.getHeight(null);
int neww = (int) Math.floor(w*cos + h*sin),
newh = (int) Math.floor(h*cos + w*sin);
BufferedImage bimg = new BufferedImage(neww, newh, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bimg.createGraphics();
g.translate((neww-w)/2, (newh-h)/2);
g.rotate(Math.toRadians(angle), w/2, h/2);
g.drawRenderedImage(img, null);
g.dispose();
return bimg;
}
However, I seem to have a caching (not sure about this) problem where I need to put a Thread.sleep(9000);above the fullImage = ImageIO.read(fileJPG); line otherwise my image element would contain the right dimension but doesn't show it rotated. Since a picture says more than a 1000 words:
To be clear, this is how it should look like:
Now the sleep is a solution but to be honest I don't want to wait 9 seconds (counted it, 8 doesn't work) before it rotates. Also, I'm not sure what it gives on a slower computer because it could be possible that it will take longer?
Since AffineTransform quadrantrotate should be more efficient (and the question is marked as duplicate) I tried following answer but the problem still occurs. The code:
AffineTransform at = new AffineTransform();
at.translate(100, 40);
at.quadrantRotate(1, img.getWidth() / 2, img.getHeight() / 2);
g.drawImage(img, at, null);
g.dispose();
still gives the same issues. I tried a different approach on reading the image
fullImage = Toolkit.getDefaultToolkit().getImage(new File(rotateURL).getAbsolutePath());
leftImage = rotatePicture(fullImage, 90);
but still the problem remains. So there seems to be a problem with the reading of the image, are there any other ways I didn't think about while reading the image?

ImageIO.write is saving image with distortion

Here is my painting method:
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
bi.setRGB(0, 0, width, height, rgbIntArray, 0, width);
ImageIO.write(bi, "bmp", new File("C:/Users/Felipe/Desktop/img2.bmp"));
This is how I populate the rgbIntArray:
rgbIntArray = new int[(rgbArray.length / 3)];
int j = 0;
for (int i = 0; i < rgbArray.length; i += 3)
{
rgbIntArray[j] = unsignedToBytes(rgbArray[i]) +
unsignedToBytes(rgbArray[i + 1]) * 256 +
unsignedToBytes(rgbArray[i + 2]) * 65536;
j++;
}
I tested these values, they seem to be correct.
I think the problem is on the last parameter of setRGB, it asks for the "scanline stride", what to be honest I don`t have a clue what it is. (but I found somewhere it could be the width of the image). I'm assuming the other parameters are correct.
Here are the results:
Original image:
Original image http://i.minus.com/jy7iVQxtghO0l.bmp
Result:
Result http://i.minus.com/jz86D3YkuPPhG.bmp
I will manipulate the image after. I'm just opening and saving the same image.
I don't know how you initialize rgbArray, but each row ends with a black pixel (outside the image). It might represent a new line if you initialized rgbArray by reading the bytes directly from the image file. Or you didn't initialize rgbArray correctly.
The black pixels show on the sheared image as a diagonal line.
You can skip the black pixels at the end of each row by changing this:
bi.setRGB(0, 0, width, height, rgbIntArray, 0, width);
to this:
bi.setRGB(0, 0, width, height, rgbIntArray, 0, width + 1);
The last parameter, width + 1, basically says that if a given row starts at a certain index in the array, then the next row will start at the index which is width + 1 higher in the same array.

Categories

Resources