-->

AffineTransform truncates image, what do I wrong?

2019-01-15 22:17发布

问题:

I have here an black/white png file of the dimensions 2156x1728 which I want to rotate 90 degrees using AffineTransform. The resulting image doesn't have the right proportions. Here some example code (given I have successfully loaded the png file into the BufferedImage ):

public BufferedImage transform(BufferedImage image){

    System.out.println("Input width: "+ image.getWidth());
    System.out.println("Input height: "+ image.getHeight());

    AffineTransform affineTransform = new AffineTransform();
    affineTransform.setToQuadrantRotation(1, image.getWidth() / 2, image.getHeight() / 2);

    AffineTransformOp opRotated = new AffineTransformOp(affineTransform, AffineTransformOp.TYPE_BILINEAR);
    BufferedImage transformedImage = opRotated.createCompatibleDestImage(image, image.getColorModel());
    System.out.println("Resulting width: "+ transformedImage.getWidth());
    System.out.println("Resulting height: "+ transformedImage.getHeight());

    transformedImage = opRotated.filter(image, transformedImage);
    return transformedImage;
}

The output is accordingly:

Input width: 2156

Input height: 1728

Resulting width: 1942

Resulting height: 1942

How comes that the rotation returns such completely unrelated dimensions?

回答1:

I'm not a pro at this, but why not just create a BufferedImage of the correct size? Also note that your center of revolution is incorrect. You will need to rotate over a center of [w/2, w/2] or [h/2, h/2] (w being width and h being height) depending on which quadrant you're rotating to, 1 or 3, and the relative height and width of the image. For instance:

import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

public class RotateImage {
   public static final String IMAGE_PATH = "http://duke.kenai.com/"
         + "models/Duke3DprogressionSmall.jpg";

   public static void main(String[] args) {
      try {
         URL imageUrl = new URL(IMAGE_PATH);
         BufferedImage img0 = ImageIO.read(imageUrl);
         ImageIcon icon0 = new ImageIcon(img0);

         int numquadrants = 1;
         BufferedImage img1 = transform(img0, numquadrants );
         ImageIcon icon1 = new ImageIcon(img1);

         JOptionPane.showMessageDialog(null, new JLabel(icon0));
         JOptionPane.showMessageDialog(null, new JLabel(icon1));

      } catch (MalformedURLException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }

   public static BufferedImage transform(BufferedImage image, int numquadrants) {
      int w0 = image.getWidth();
      int h0 = image.getHeight();
      int w1 = w0;
      int h1 = h0;

      int centerX = w0 / 2;
      int centerY = h0 / 2;

      if (numquadrants % 2 == 1) {
         w1 = h0;
         h1 = w0;
      }

      if (numquadrants % 4 == 1) {
         if (w0 > h0) {
            centerX = h0 / 2;
            centerY = h0 / 2;
         } else if (h0 > w0) {
            centerX = w0 / 2;
            centerY = w0 / 2;
         }
         // if h0 == w0, then use default
      } else if (numquadrants % 4 == 3) {
         if (w0 > h0) {
            centerX = w0 / 2;
            centerY = w0 / 2;
         } else if (h0 > w0) {
            centerX = h0 / 2;
            centerY = h0 / 2;
         }
         // if h0 == w0, then use default
      }

      AffineTransform affineTransform = new AffineTransform();
      affineTransform.setToQuadrantRotation(numquadrants, centerX, centerY);

      AffineTransformOp opRotated = new AffineTransformOp(affineTransform,
            AffineTransformOp.TYPE_BILINEAR);

      BufferedImage transformedImage = new BufferedImage(w1, h1,
            image.getType());

      transformedImage = opRotated.filter(image, transformedImage);
      return transformedImage;
   }
}

Edit 1
You asked:

can you explain to me why it must be [w/2, w/2] or [h/2, h/2] ?

To explain this best, it's best to visualize and physically manipulate a rectangle:

Cut out rectangular piece of paper and place it on a piece of paper such that its upper left corner is on the upper left corner of the piece of paper -- that's your image on the screen. Now check to see where you would need to rotate that rectangle 1 or 3 quadrants so that its new upper left corner is overlying that of the paper, and you'll see why you need to use [w/2, w/2] or [h/2, h/2].



回答2:

The above solution had problems with wdith & height of images the code below is independent of w > h || h > w

public static BufferedImage rotateImage(BufferedImage image, int quadrants) {

    int w0 = image.getWidth();
    int h0 = image.getHeight();
    int w1 = w0;
    int h1 = h0;
    int centerX = w0 / 2;
    int centerY = h0 / 2;

    if (quadrants % 2 == 1) {
        w1 = h0;
        h1 = w0;
    }

    if (quadrants % 4 == 1) {
        centerX = h0 / 2;
        centerY = h0 / 2;
    } else if (quadrants % 4 == 3) {
        centerX = w0 / 2;
        centerY = w0 / 2;
    }

    AffineTransform affineTransform = new AffineTransform();
    affineTransform.setToQuadrantRotation(quadrants, centerX, centerY);
    AffineTransformOp opRotated = new AffineTransformOp(affineTransform,
            AffineTransformOp.TYPE_BILINEAR);
    BufferedImage transformedImage = new BufferedImage(w1, h1,
            image.getType());
    transformedImage = opRotated.filter(image, transformedImage);

    return transformedImage;

}


回答3:

furykid's answer is great and helped me a lot. But it isn't so perfect. If the image is rectangular the resulting rotated image might contain some extra black pixels at one side.

I tried with a Marty Feldman photo, the original and the results can be viewed in this link: Marty Feldman rotation tests

It is difficult to see on a black background, but on any image editing software it is easy to see the little black border on the right and bottom side of the resulting images. This might not be a problem for some, but if it is for you, here's the fixed code(I kept the original as commentaries for easier comparison):

public BufferedImage rotateImage(BufferedImage image, int quadrants) {

    int w0 = image.getWidth();
    int h0 = image.getHeight();
    /* These are not necessary anymore
    * int w1 = w0;
    * int h1 = h0;
    */
    int centerX = w0 / 2;
    int centerY = h0 / 2;

    /* This is not necessary anymore
    * if (quadrants % 2 == 1) {
    *     w1 = h0;
    *     h1 = w0;
    * }
    */

    //System.out.println("Original dimensions: "+w0+", "+h0);
    //System.out.println("Rotated dimensions: "+w1+", "+h1);

    if (quadrants % 4 == 1) {
        centerX = h0 / 2;
        centerY = h0 / 2;
    } else if (quadrants % 4 == 3) {
        centerX = w0 / 2;
        centerY = w0 / 2;
    }

    //System.out.println("CenterX: "+centerX);
    //System.out.println("CenterY: "+centerY);

    AffineTransform affineTransform = new AffineTransform();
    affineTransform.setToQuadrantRotation(quadrants, centerX, centerY);
    AffineTransformOp opRotated = new AffineTransformOp(affineTransform,
            AffineTransformOp.TYPE_BILINEAR);

    /*Old code for comparison
    //BufferedImage transformedImage = new BufferedImage(w1, h1,image.getType());
    //transformedImage = opRotated.filter(image, transformedImage);
    */
    BufferedImage transformedImage = opRotated.filter(image, null);
    return transformedImage;

}

WARNING: Opinion ahead. I'm not sure of the reason why this happens but I have a guess. If you can explain better, please edit.

I believe the reason for this "glitch" is because of odd dimensions. When calculating the dimensions for the new BufferedImage, a height of 273 will generate a centerY of 136, for example, when the correct value is 136.5. That might cause the rotation to occur in a slightly off center place. However, by sending null to filter as the destination image, "a BufferedImage is created with the source ColorModel", and that seems to work best.