1

I have an image and I have to rotate it by 45, 90, 135, 180 degrees. What I am doing:

try {
    BufferedImage src = ImageIO.read(new File("src.png"));
    double ang = Math.toRadians(90);

    AffineTransform t = new AffineTransform();
    t.setToRotation(ang, src.getWidth() / 2, src.getHeight() / 2);

    AffineTransformOp op = new AffineTransformOp(t, null);
    BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
    op.filter(src, dst);

    ImageIO.write(dst, "png", new File("output.png"));
} catch(Exception ex) { ex.printStackTrace();
}

The problem is that image changes its position and gets out of bounds of destination image:

The problem

I've googled this and found the solution in this question: AffineTransform truncates image, what do I wrong? But I quite don't understand it and it works only for quadrants. I've tried to multiply twice width and height of destination, but it failed:

Another fail

How to fix this? The destination image shouldn't have any extra (except required for diagonal rotation) whitespace or truncated area. Angle problems (0 == 180 or is it clockwise) aren't important.

Thanks for any help.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Cenius
  • 103
  • 1
  • 1
  • 9
  • What about the extra space along the diagonal at 45 and 135? Should it be transparent? Is 180 identical to 0? Please edit your question to include an [sscce](http://sscce.org/) that exhibits the problem you describe. [`FauxImage`](http://stackoverflow.com/a/8090328/230513) may be a convenient adjunct. – trashgod May 03 '12 at 07:41
  • I have an example and pictures that illustrate the problem. Code is ready to run. – Cenius May 03 '12 at 07:57

2 Answers2

4

Edit: Now it works for the general case.

The rotation is performed around the center, and the center is placed at the same position in the destination image as it was in the source image (correct behavior).

I've modified your code to transform the source image rectangle so we can easily get the new dimensions/image offset. This is used to construct a destination BufferedImage of the correct dimensions, and to append a translation to your AffineTransform so the image center is placed at the center of the destination image.

        BufferedImage src = ImageIO.read(new File(INPUT));
        int w = src.getWidth();
        int h = src.getHeight();

        AffineTransform t = new AffineTransform();
        double ang = Math.toRadians(35);
        t.setToRotation(ang, w / 2d, h / 2d);

        // source image rectangle
        Point[] points = {
            new Point(0, 0),
            new Point(w, 0),
            new Point(w, h),
            new Point(0, h)
        };

        // transform to destination rectangle
        t.transform(points, 0, points, 0, 4);

        // get destination rectangle bounding box
        Point min = new Point(points[0]);
        Point max = new Point(points[0]);
        for (int i = 1, n = points.length; i < n; i ++) {
            Point p = points[i];
            double pX = p.getX(), pY = p.getY();

            // update min/max x
            if (pX < min.getX()) min.setLocation(pX, min.getY());
            if (pX > max.getX()) max.setLocation(pX, max.getY());

            // update min/max y
            if (pY < min.getY()) min.setLocation(min.getX(), pY);
            if (pY > max.getY()) max.setLocation(max.getX(), pY);
        }

        // determine new width, height
        w = (int) (max.getX() - min.getX());
        h = (int) (max.getY() - min.getY());

        // determine required translation
        double tx = min.getX();
        double ty = min.getY();

        // append required translation
        AffineTransform translation = new AffineTransform();
        translation.translate(-tx, -ty);
        t.preConcatenate(translation);

        AffineTransformOp op = new AffineTransformOp(t, null);
        BufferedImage dst = new BufferedImage(w, h, src.getType());
        op.filter(src, dst);

        ImageIO.write(dst, "png", new File(OUTPUT));
Torious
  • 3,364
  • 17
  • 24
  • Any chance you would work that into an [SSCCE](http://sscce.org/)? There are some [images at my site](http://pscode.org/media/#image) to which you can hot-link for the source image. – Andrew Thompson May 17 '12 at 23:58
0

I suggest to replace

AffineTransformOp op = new AffineTransformOp(t, null);

with

AffineTransformOp op = new AffineTransformOp(t,  AffineTransformOp.TYPE_BILINEAR);

It will improve a lot the quality of the output.

Prasad Khode
  • 6,602
  • 11
  • 44
  • 59
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post - you can always comment on your own posts, and once you have sufficient [reputation](http://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](http://stackoverflow.com/help/privileges/comment). - [From Review](/review/low-quality-posts/11272967) – Muhammed Refaat Feb 15 '16 at 14:46
  • @MuhammedRefaat How does this not attempt to provide an answer? It might be completely wrong, [but that doesn't mean we should abuse the review queues to delete it.](http://meta.stackoverflow.com/q/287563/1849664) – Undo Feb 16 '16 at 00:18
  • @Undo I always believe that suggestions are to be placed as comments, if every suggestion written as an answer, the question will physically have like 10 answers but logically has only 2, I'm just trying to obey the community rules. – Muhammed Refaat Feb 16 '16 at 08:07
  • @MuhammedRefaat Please read the link in the comment above. The community rules are that we don't delete things from review that are attempts to answer. Of course, you are welcome to spend your downvotes, and delete votes when you reach 20k, as you wish. Just don't use review to delete these. – Undo Feb 16 '16 at 14:34
  • @Undo well, thanks for your advice, may be you are right. – Muhammed Refaat Feb 17 '16 at 08:57
  • Sorry, I'm a newbie and don't have enough reputation to add a comment. That's why I posted my suggestion as a new answer. – Michele Ranieri Mar 22 '17 at 09:15