こんにちは。阿部です。
Linuxには画像ファイルを回転させるconvert
コマンドがありますが、
Windowsには、そのようなコマンドがなさそうだったので、Javaで簡単なツールを作ることになりました。
ちょっと調べたところ、画像の回転はjava.awt.geom.AffineTransform
を使えばできそうです。
ファイルの読み書きを除けば実質4行です。
1
2
3
4
|
AffineTransform af = new AffineTransform();
af.rotate(Math.toRadians(8)); // 8°回転
AffineTransformOp op = new AffineTransformOp(af, AffineTransformOp.TYPE_BICUBIC);
op.filter(oldImage, newImage);
|
よし、できた!サンプルとしてデーコムラボのロゴを回転してみましょう。
画像がずれてしまう
回転するにはしたのですが……。
どうやら回転の原点が左上になってるみたいですね。
画像がずれてしまいます。
AffineTransform
には、画像を平行移動するメソッドもあるので、これを使いましょう。
回転で位置がずれてしまった画像を、真ん中に引き戻します。
回転後の画像の中心座標はx = (width * cosθ - height * sinθ) / 2
、y = (width * sinθ + height * cosθ) / 2
で求められます。
1
2
3
4
5
6
7
|
// 回転後の中心座標(cx, cy)
double cx = (w * Math.cos(angle) - h * Math.sin(angle)) / 2.0;
double cy = (w * Math.sin(angle) + h * Math.cos(angle)) / 2.0;
// 真ん中に引き戻すのに必要な移動量(dx, dy)
double dx = width / 2.0 - cx;
double dy = height / 2.0 - cy;
af.setToTranslation(dx, dy);
|
画像が欠けてしまう
その場で回転しましたが…
保存する画像のサイズ(幅、高さ)が元のままなので、はみ出した部分が欠けてしまいました。
回転した画像がぴったり収まるように幅と高さを指定してやる必要があります。
この計算を考えるのが一番大変でした。
元の画像の対角線が、回転後の画像の上下もしくは左右の辺に接していることを利用して計算しました。もっとスマートなやりかたがあるのかもしれませんが、私の頭では思いつかなかったので、そのままのコードを載せます。
完成
最初の4行に比べると、ずいぶん長くなってしまいましたが、完成です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Rotate {
public static void main(String[] args) {
if (args.length2 ? args[2] : args[1];
try {
BufferedImage img = ImageIO.read(new File(inFileName));
int w = img.getWidth();
int h = img.getHeight();
// 元画像の2つの対角線をそれぞれ回転した後、
// X軸、Y軸に対する射影の長さを求めます。
// X軸に対する射影の長さの大きいほうを、出力ファイルの幅、
// Y軸に対する射影の長さの大きいほうを、出力ファイルの高さとします。
Point diag1 = rotate(new Point(w, h), angle);
Point diag2 = rotate(new Point(-w, h), angle);
int width = Math.max(Math.abs(diag1.x), Math.abs(diag2.x));
int height = Math.max(Math.abs(diag1.y), Math.abs(diag2.y));
// 回転後の中心座標を求めます。
double cx = (w * Math.cos(angle) - h * Math.sin(angle)) / 2.0;
double cy = (w * Math.sin(angle) + h * Math.cos(angle)) / 2.0;
// 元画像を原点を中心に回転します。画像の中心がずれるので、
// 次に、回転後の中心が出力ファイルの中心となるよう、平行移動します。
AffineTransform af = new AffineTransform();
double dx = width / 2.0 - cx;
double dy = height / 2.0 - cy;
af.setToTranslation(dx, dy);
af.rotate(angle);
BufferedImage rotated = new BufferedImage(width, height, img.getType());
AffineTransformOp op = new AffineTransformOp(af, AffineTransformOp.TYPE_BICUBIC);
op.filter(img, rotated);
// 回転したイメージを保存します。
ImageIO.write(rotated, "bmp", new File(outFileName));
} catch (IOException e) {
e.printStackTrace();
}
}
public static Point rotate(Point point, double angle) {
double th = Math.atan2(point.getY(), point.getX());
double norm = Math.sqrt(point.getX() * point.getX() + point.getY() * point.getY());
double x = norm * Math.cos(th - angle);
double y = norm * Math.sin(th - angle);
return new Point((int)Math.round(x), (int)Math.round(y));
}
}
|
思っていた通りの画像が得られました。
まさか、実業務でサイン、コサインを使う日が訪れようとは思いもしませんでした。