管理しているサイトで、記事にスマホ(iPhone)で撮った写真を載せる作業をしていたときのことです。写真は縦に持って撮ったものだったのですが、載せる前に縮めたところ、横倒しの画像になってしまいました。
調べると、原因は写真の中の EXIF という情報にありました。EXIF には、向きのほかに、撮った日時・機種・位置情報(GPS)も入っています。この記事では、写真の向きと位置情報の正体と、WordPress に入れたときにどのファイルに何が残るかを、作業環境で確かめた結果をまとめます。
写真の向きの正体(Orientation)
スマホは、縦に持って撮っても、ファイルの中の画素は横向きのまま保存することがあります。そのかわりに、EXIF の Orientation という項目に「表示するときに、こう回して見せる」と書いておき、写真のアプリやブラウザはこれを読んで、回してから表示します。
| Orientation の値 | 意味 |
|---|---|
| 1 | そのまま表示する |
| 3 | 180 度回して表示する(上下が逆) |
| 6 | 時計回りに 90 度回して表示する(縦に持って撮った写真に多い) |
| 8 | 反時計回りに 90 度回して表示する |
2・4・5・7 は、鏡のような反転を含む値です。今回の写真では出てこなかったので、この記事では扱いません。
縮める道具が Orientation を見ないと、横倒しになる
画像を縮める道具によっては、Orientation を見ずに画素だけを縮め、縮めたあとの画像にも Orientation を書きません。そのため、横向きの画素が回されないまま表示されてしまいます。Orientation が 3 の写真なら、上下が逆になります。
今回は、縦の写真のはずが、横倒しの 900×675 の画像になりました。そこで Orientation を読んで、先に回してから縮め直すと、縦の画像になりました。
位置情報(GPS)
スマホの設定によっては、写真に撮った場所の緯度・経度・高さが入ります。写真をそのまま公開すると、だれでもファイルを保存して撮った場所を読み出せるので、お店や事務所の写真なら問題になりにくいとはいえ、自宅の近くで撮った写真では注意が必要です。
今回の写真を調べた
PHP の exif_read_data() で、試しの写真 4 枚を調べました。
<?php
// 写真に Orientation と GPS の情報があるかを出す(GPS の値そのものは出さない)
// 使い方: php check.php 写真1.jpg 写真2.jpg ...
foreach (array_slice($argv, 1) as $file) {
$size = getimagesize($file);
$exif = @exif_read_data($file, null, true);
$orientation = $exif['IFD0']['Orientation'] ?? 'なし';
$gps = isset($exif['GPS']['GPSLatitude']) ? 'あり' : 'なし';
echo basename($file), ": {$size[0]}x{$size[1]} Orientation={$orientation} GPS={$gps}\n";
}
| 書き方 | 役目 |
|---|---|
exif_read_data($file, null, true) |
EXIF を読む。3 つ目を true にすると、IFD0・EXIF・GPS のように区切りごとの配列になる |
$exif['IFD0']['Orientation'] |
向きの値 |
$exif['GPS']['GPSLatitude'] |
緯度。ここに値があれば、位置情報が入っている |
getimagesize() |
ファイルの中の画素の幅と高さ(回す前) |
今回の作業環境の PHP(8.3)では、exif の拡張が有効になっていませんでした(php.ini の extension=exif がコメントのまま)。そのため、調べるときは php -d extension=exif check.php … のように、そのときだけ有効にして流しました。
| 写真 | 画素の幅×高さ | Orientation | GPS | 撮った日時・機種 |
|---|---|---|---|---|
| 写真 1 | 4032×3024 | 6 | あり(緯度・経度・高さ) | あり |
| 写真 2 | 5712×4284 | 6 | あり(緯度・経度・高さ) | あり |
| 写真 3 | 4032×3024 | 1 | あり(緯度・経度・高さ) | あり |
| 写真 4 | 5712×4284 | 3 | あり(緯度・経度・高さ) | あり |
4 枚とも、位置情報が入っていました。縦に持って撮った 2 枚は、画素は横長のまま、Orientation が 6 でした。
WordPress に入れると、どのファイルに何が残るか
WordPress は、画像を入れると、サムネイルなどの大きさ違いの画像を作ります。長い辺が 2560 ピクセルを超える画像では、縮めた -scaled の付いたファイルも作り、記事ではそちらを使います(big_image_size_threshold の決まり)。
では、元のファイルや、作られたファイルに、EXIF は残るのでしょうか。作業環境の WordPress に、写真 1(4032×3024・Orientation 6・GPS あり)を入れて確かめました。
確かめた条件
| 項目 | 今回の作業環境 |
|---|---|
| PHP | 8.3 |
| 画像の編集の道具 | GD(_wp_image_editor_choose() で確かめた。Imagick の拡張は入っていない) |
| exif の拡張 | 無効のときと、有効にしたときの 2 回を試した |
| 入れ方 | PHP の CLI で wp-load.php を読み込み、media_handle_sideload() で入れた |
| プラグイン | 画像を変えるプラグイン(WebP の変換・画像の圧縮)は、検証用スクリプトの中だけで読み込まないようにした(DB の設定は変えていない) |
<?php
// 検証用スクリプト(作業環境だけで使う)の中心の部分
require 'wp-load.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
$tmp = wp_tempnam('exif-test.jpg');
copy('試しの写真.jpg', $tmp);
$id = media_handle_sideload(['name' => 'exif-test.jpg', 'tmp_name' => $tmp], 0);
$meta = wp_get_attachment_metadata($id);
echo get_attached_file($id), "\n"; // 記事で使われるファイル
echo wp_get_original_image_path($id), "\n"; // 元のファイル
foreach ($meta['sizes'] as $name => $s) {
echo $name, ' ', $s['file'], ' ', $s['width'], 'x', $s['height'], "\n";
}
// 終わったら、ファイルごと消す
wp_delete_attachment($id, true);
| 書き方 | 役目 |
|---|---|
media_handle_sideload() |
サーバーの中のファイルを、メディアに入れる。管理画面から上げたときと同じく、大きさ違いの画像も作られる |
get_attached_file() |
記事で使われるファイル。大きい画像では -scaled のほう |
wp_get_original_image_path() |
上げたままの元のファイル |
wp_delete_attachment($id, true) |
メディアと、作られたファイルをまとめて消す |
結果:ファイルごとの EXIF
作られたファイルを、上の check.php で 1 つずつ調べました。
| ファイル | exif の拡張が無効のとき | exif の拡張が有効のとき |
|---|---|---|
| 元のファイル(exif-test.jpg) | 4032×3024・Orientation 6・GPS あり(上げたファイルとバイト単位で同じ) | 4032×3024・Orientation 6・GPS あり |
-scaled(記事で使うもの) |
2560×1920(横のまま)・Orientation なし・GPS なし | 1920×2560(縦に回された)・Orientation なし・GPS なし |
| 2048×2048 の大きさ | 2048×1536(横)・EXIF なし | 1536×2048(縦)・EXIF なし |
| large | 1024×768(横)・EXIF なし | 768×1024(縦)・EXIF なし |
| medium | 300×225(横)・EXIF なし | 225×300(縦)・EXIF なし |
| thumbnail | 150×150・EXIF なし | 150×150・EXIF なし |
| テーマが足した大きさ(切り抜きのものなど) | EXIF なし | EXIF なし |
今回検証した作業環境では、次のことを確認しました。
- 作られた大きさ違いの画像と
-scaledには、Orientation も GPS も、撮った日時・機種も残っていなかった - 元のファイルは、上げたときのまま残り、位置情報も入ったままだった
- exif の拡張が無効だと、写真は回されず、横倒しの画像が作られた
- exif の拡張が有効だと、Orientation を読んで縦に回してから、大きさ違いの画像が作られた
元のファイルは URL で開ける
元のファイルは、アップロードのフォルダの中に、-scaled を外した名前で置かれます。作業環境で元のファイルの URL を開くと、200 で、上げたときと同じ大きさのファイルがそのまま返りました。
記事の中では -scaled のほうが使われますが、ファイル名から元の URL は分かります。つまり、「WordPress が大きさ違いを作るときに EXIF を落とす」からといって、位置情報が公開されないとは言えません。
メディアの情報(DB)に入るもの
WordPress は、画像を入れるときに、EXIF の一部を読んで、メディアの情報として DB に入れます(wp_read_image_metadata())。exif の拡張が有効のとき、今回の写真では、絞り・機種・撮った日時・焦点距離・ISO・シャッター速度・向きが入りましたが、緯度・経度の項目はありませんでした。
また、ログインしていない状態で、REST API の /wp/v2/media/(id) を中で呼ぶと、200 が返り、この情報(機種・撮った日時など)が media_details の中に出ていました。exif の拡張が無効のときは、これらは空でした。
消したことの確かめ
確かめたあとは、2 回とも wp_delete_attachment() でファイルごと消しました。消す前にメモしたファイルの一覧(元・-scaled・大きさ違い)が 1 つも残っていないこと、メディアの情報が DB に残っていないこと、アップロードのフォルダのファイルの数が試す前と同じであることを確かめています。
なお、長い辺が 2560 ピクセル以下の写真(-scaled が作られないもの)と、Imagick を使う環境は、試していません。
載せる前に消す方法(試した結果)
元のファイルに位置情報を残さないいちばん確かな方法は、WordPress に入れる前に、写真から消しておくことです。
1. PHP の GD で、回してから保存し直す
Orientation を読んで画素を回し、縮めてから、新しく保存し直す方法です。GD は保存のときに EXIF を書かないので、位置情報も一緒になくなります。向きは画素に反映されているので、EXIF がなくても正しい向きで表示されます。
<?php
// 写真の向きを画素に反映して、EXIF を付けずに保存し直す(GD を使う)
// 使い方: php strip-exif.php 元のフォルダ 出すフォルダ
$in = rtrim($argv[1], '/');
$out = rtrim($argv[2], '/');
$max = 2000; // 長い辺の上限(ピクセル)
if (!function_exists('exif_read_data')) {
exit("exif の拡張が有効ではありません\n");
}
if (!is_dir($out)) {
mkdir($out, 0755, true);
}
foreach (glob($in . '/*.jpg') as $file) {
$img = imagecreatefromjpeg($file);
if (!$img) {
echo basename($file), ": 読めませんでした\n";
continue;
}
// 1. Orientation を読んで、画素を回す
$exif = @exif_read_data($file);
$orientation = (int) ($exif['Orientation'] ?? 1);
if ($orientation === 3) {
$img = imagerotate($img, 180, 0);
} elseif ($orientation === 6) {
$img = imagerotate($img, -90, 0); // 時計回りに 90 度
} elseif ($orientation === 8) {
$img = imagerotate($img, 90, 0); // 反時計回りに 90 度
}
// 2. 長い辺が $max を超えるときだけ縮める
$w = imagesx($img);
$h = imagesy($img);
$scale = min(1, $max / max($w, $h));
if ($scale < 1) {
$img = imagescale($img, (int) round($w * $scale), (int) round($h * $scale));
}
// 3. 新しく保存する(GD は EXIF を書かない)
$dest = $out . '/' . basename($file);
imagejpeg($img, $dest, 82);
echo basename($file), ": Orientation {$orientation} → ", imagesx($img), 'x', imagesy($img), "\n";
}
| 書き方 | 役目 |
|---|---|
function_exists('exif_read_data') |
exif の拡張が無効なら止める。無効のまま流すと、回さずに保存してしまうため |
exif_read_data($file)['Orientation'] |
向きの値。ないときは 1(そのまま)として扱う |
imagerotate($img, -90, 0) |
GD の角度は反時計回りなので、時計回りに 90 度回すときは -90 |
imagescale() |
長い辺が 2000 を超えるときだけ縮める(回したあとに縮める) |
imagejpeg($img, $dest, 82) |
新しいファイルとして保存する。82 は画質 |
試しの写真 4 枚のコピーで流し、できたファイルを check.php で調べました。
| 写真 | 前(画素・Orientation・GPS) | 後(画素・Orientation・GPS) |
|---|---|---|
| 写真 1 | 4032×3024・6・あり | 1500×2000(縦)・なし・なし |
| 写真 2 | 5712×4284・6・あり | 1500×2000(縦)・なし・なし |
| 写真 3 | 4032×3024・1・あり | 2000×1500(横)・なし・なし |
| 写真 4 | 5712×4284・3・あり | 2000×1500(横)・なし・なし |
4 枚とも、位置情報・撮った日時・機種が消え、XMP という別の形の情報も残っていませんでした。できた画像を開いて、4 枚とも正しい向きで表示されることも確かめました。
2. EXIF の区切りだけを抜く(向きの情報も消える)
画素を作り直さずに、JPEG の中の EXIF の区切り(APP1)だけを抜く検証用スクリプトも流しました。
| 写真 | 前 | 後 |
|---|---|---|
| 写真 1 | 4032×3024・Orientation 6・GPS あり | 4032×3024・Orientation なし・GPS なし |
位置情報は消えましたが、Orientation も一緒に消えるので、画素が横向きのままの縦の写真は、横倒しで表示されます。また、XMP などのほかの区切りは残りました。縦に撮った写真では使えないので、1 の方法にしました。
3. Windows のプロパティから消す
Windows では、ファイルの「プロパティ → 詳細 → プロパティや個人情報を削除」から、EXIF を消せることが知られています。とはいえ、今回は試していません。使うときは、消したあとのファイルを check.php のような方法で調べて、位置情報と向きがどうなったかを確かめてから載せるのがよいと思います。
まとめ
- スマホの写真は、画素が横向きのまま、EXIF の Orientation(6 や 3)で「回して見せる」と書かれていることがある
- Orientation を見ずに縮めると、横倒しや上下逆の画像になる。先に回してから縮める
- 試しの写真には 4 枚とも位置情報(緯度・経度・高さ)が入っていた
- 今回検証した作業環境(GD)では、WordPress が作った
-scaledと大きさ違いの画像には EXIF が残らなかった。元のファイルは位置情報が入ったまま残り、URL で開けた - WordPress が写真を回すのは、PHP の exif の拡張が有効なときだけだった
- 機種や撮った日時は、メディアの情報として DB に入り、REST API の応答にも出ていた(緯度・経度は入らなかった)
- 載せる前に、Orientation を読んで回し、保存し直すと、向きはそのままで位置情報を消せた
参考
- wp_maybe_exif_rotate()(developer.wordpress.org)
- wp_generate_attachment_metadata()(developer.wordpress.org)
- big_image_size_threshold(developer.wordpress.org)
- wp_get_original_image_path()(developer.wordpress.org)
- wp_read_image_metadata()(developer.wordpress.org)
- media_handle_sideload()(developer.wordpress.org)
- exif_read_data(php.net)
- exif のインストール手順(php.net)
- imagerotate(php.net)
- imagejpeg(php.net)


