JPEG画像の向き(EXIF Orientation)
JPEG画像には画像の向きを示す情報が記録されている場合があり、画像ビューワはこれをヒントとして、画像の向きを合わせて表示することが期待されています。そのソフトウェア的な対応方法を説明します。
最近Canon製の一眼レフデジカメ(Kiss X2)を購入しました。Canon製デジカメは、知る限りではコンデジも含めて全機種に重力センサが付いていて、撮影時に画像の天地向きを記録することができます。(しないこともできる)。これをgqviewなどの対応したソフトで閲覧すると、自動的に縦向き(portrait)の写真はクルっと回転して正しい向きに直して見ることができます。対応していないと縦画像が寝てしまいますので一長一短なのですが、デフォルトでは利用するように設定されています。
私が提供しているPhotostandでは、Linux版もWindows版も特に何も処理していませんでしたので、そのまま(縦向きの画像は寝た状態で)表示されます。つまり、システムの壁紙表示機能はこの仕様に対応していませんでした。
EXIFの仕様と対応
この向き情報は、EXIFのOrientationに記録されています。従って、表示の前にこの情報を読み出して、正しい向きに直してやる必要があります。仕様に関しては、このサイトが詳しいのですが、かいつまんで書くと以下のようなものです。
- EXIF仕様にはOrientation(Tag ID = 0x0112)が含まれている。
- unsigned shortサイズで、指定可能な数値は、1〜8。
値は右図の通り。赤枠(1)がオリジナル画像。
- 通常使うのは、横位置(landscape)の写真の(1)、シャッター(右手)が上の縦位置(portrait)写真の(8)、シャッターが下の縦の(6)でしょう。なお、鏡像のもの(2,4,7)が含まれているのですが、どういうシチュエーションで使うのかは見当が付きません。
ソフトウェアの対応
Windowsの場合
C#.NETで実装しました。ExifはImageのPropertyItemsで対応されるため、ここから読み出せばOKです。このサイトに少し説明があります。また、画像の回転は、BitmapのRotateFlipで行うことができます。
以下にコードを提示します。
Bitmap bm = new Bitmap(this.pictureFile);
// Check orientation hint of EXIF to know
// if rotation of the picture is necessary or not.
ushort ort = GetPictureOrientation(bm);
switch (ort) {
case 2:
bm.RotateFlip(RotateFlipType.RotateNoneFlipX);
break;
case 3:
bm.RotateFlip(RotateFlipType.Rotate180FlipNone);
break;
case 4:
bm.RotateFlip(RotateFlipType.Rotate180FlipX);
break;
case 5:
bm.RotateFlip(RotateFlipType.Rotate90FlipX);
break;
case 6:
bm.RotateFlip(RotateFlipType.Rotate90FlipNone);
break;
case 7:
bm.RotateFlip(RotateFlipType.Rotate270FlipX);
break;
case 8:
bm.RotateFlip(RotateFlipType.Rotate270FlipNone);
break;
default: // 0 or 1.
break;
}
------8<------8<------
/// <summary>
/// Determine the orientation of the picture by reading exif
/// </summary>
private ushort GetPictureOrientation (Bitmap bmp)
{
if (bmp == null) {
return 0;
}
foreach (PropertyItem itm in bmp.PropertyItems) {
if (itm.Id == 0x0112) { /// PropertyItem.PropertyTagOrientation)
Debug.WriteLine("GetPictureOrientation:" +
BitConverter.ToUInt16(itm.Value, 0).ToString() + ".");
return BitConverter.ToUInt16(itm.Value, 0);
}
}
return 0;
}
Linuxの場合
といいますか、scriptでの実装です。基本的には、exifの中身は、exiftoolかexifで読むことができます。(書き込みはexiftoolしかできません)。画像の回転やフリップはNetpbmのpamflipを使うのが手軽でしょう。以下の例は、perlでの実装です。
#--- check orientation of the picture.
`which exiftool`;
Dprint "exiftool returns = $? exit_code = %d signal = %d\n", $? >> 8, $? & 0x7f;
if (($? >> 8\) == 0) {
open(IN, "exiftool -Orientation -S -n \"$wallPaper\" 2>/dev/null |") or die("exiftool");
while(<IN>) {
s/\s*$//;
split();
Dprint "NOF($#_) $_\n";
if ($#_ ge 1) {
$orientation = $_[1];
Dprint "Image Orientation = ($orientation)\n";
}
}
close(IN);
if ($orientation eq 2) {
$Rotate = "-lr";
} elsif ($orientation eq 3) {
$Rotate = "-r180";
} elsif ($orientation eq 4) {
$Rotate = "-lr -r180";
} elsif ($orientation eq 5) {
$Rotate = "-lr -r270";
$foo = $imgwidth;
$imgwidth = $imgheight;
$imgheight = $foo;
} elsif ($orientation eq 6) {
$Rotate = "-r270";
$foo = $imgwidth;
$imgwidth = $imgheight;
$imgheight = $foo;
} elsif ($orientation eq 7) {
$Rotate = "-lr -r90";
$foo = $imgwidth;
$imgwidth = $imgheight;
$imgheight = $foo;
} elsif ($orientation eq 8\) {
$Rotate = "-r90";
$foo = $imgwidth;
$imgwidth = $imgheight;
$imgheight = $foo;
}
}----------8<-------8<-------
if (!defined($Rotate)) {
`anytopnm "$wallPaper" | pamscale -xyfill $scrwidth $scrheight | \
pnmtojpeg $smooth > $TmpPicture`;
} else {
`anytopnm "$wallPaper" | pamflip $Rotate | pamscale -xyfill $scrwidth $scrheight | \
pnmtojpeg $smooth > $TmpPicture`;
}
pamflipの引数で(6)と(8)の回転角の指定が逆のような気がしますが、実際にやってみるとこの通りになっています。ちょっと変な感じです。