Search code examples
javabufferedimage

BufferedImage getSubimage throws exception with seemingly good arguments


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?


Solution

  • 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.