雜記 › 生活隨筆 › 照片轉了 90°,換一部裝置方向又回來了:EXIF 標記與真實旋轉的分工

照片轉了 90°,換一部裝置方向又回來了:EXIF 標記與真實旋轉的分工

手機上明明轉正了,傳到電腦又躺倒

前年在手機上拍的一張照片,相簿裡點了一下旋轉,看著是正的;傳給電腦拖進一個老式看圖軟體,又躺回去了。當時懷疑沒存成功,來回轉三趟,瀏覽器裡那張被轉了兩遍,直接倒過來。

後來才看清:相簿那次旋轉沒動像素,只改了一個叫 Orientation 的標記;而在 圖片旋轉翻轉 裡點一次「向右轉 90°」,像素是真搬了一遍,兩件事付的帳完全不同。

只改標記,像素一行沒動

核驗時手寫了一組 JPEG,把 APP1 段(FFE1 加 Exif\0\0 加小端 TIFF)裡的標記 0x0112 依次寫成 1 到 8。宣告 6 的那張 1268 B,剝掉元資料再解碼是 80×120——檔案裡躺著的像素真躺了 90°,「正」只活在標記裡;帶著標記解碼,naturalWidth 乘 naturalHeight 就是 120×80。

讀標記的看到正的,不讀的只能按儲存像素排版——那次「旋轉」改的是看法,不是檔案。

瀏覽器替程式碼把方向還了回來

範本一共 206 行,grep 一遍 exif、orientation、createImageBitmap,命中 0 處:它一行都不解析方向標記。解碼只有 new Image() 配 URL.createObjectURL,onload 接住轉正後的兩個值(第 130 行)。

Orientation 1 到 8 全被還原成同一個顯示方向:儲存 80×120 的 5、6、7、8 進工具後都是 120×80,匯出畫布內容兩兩相同,1–4 同 hash 962d4215、5–8 同 hash da1c444c。寫法不規範也會翻車:同一份 IFD、同一個 6,PNG 的 eXIf 寫成規範裸 TIFF 那份 727 B 被轉正成 80×120,照搬 JPEG 式前綴的 733 B 那份被忽略、仍是 120×80,資訊列與檔名一起變。

Orientation 1–81–4 · 儲存 120×80顯示 120×80962d42155–8 · 儲存 80×120顯示 120×80da1c444cPNG eXIf · 727 B 轉正 · 733 B 沒轉206 行範本裡 0 處讀方向

圖 1|儲存寬高、瀏覽器還回來的寬高與兩組畫布 hash。

真旋轉要付的兩筆帳

90° 的置換本身精確:det_q90 的 90° 畫布與手工整倍重排的參考同為 160x240:eceb4a99:153600:24483724,無插值。但匯出必過 canvas.toBlob(..., 0.92)(第 186–187 行),品質寫死、介面調不了。det_q90.jpg 的 28210 B 就算停在 0° 直接下載也變 28003 B、38373 個像素改變、平均 13.814;q100 那張 104764 B 掉到 30510 B、38400 個像素全不同、平均 73.74。逐位元組不變的只有 PNG:mark.png 581 B 匯出還是 581 B。真要省的是體積(圖片壓縮)。

第二筆是元資料。23 條匯出記錄一律 outAPP1=0、outOri=null,PNG 輸出只剩 IHDR/IDAT/IEND、不帶 eXIf;那張寫了 5 條 IFD 的手機照片,SAMSUNG、SM-G9910、2026:03:07 21:14:52 幾個字串欄位匯出後全部不存在。廠商、機型、拍攝時間、GPS 一起沒了,換來轉 90° 後輸出 JPEG 的 SOF 直接寫成 80×120,下游讀不讀標記都只剩這一個方向。

下載四趟比連點四趟傷得多

同一次會話裡連點 4 次「向右轉 90°」,角度折回 0(0→90→180→-90→0),畫布 hash 回到起點,兩次匯出都是 28003 B、逐位元組相同。

實際動作是轉一趟就下載再看:det_q90 起始 28210 B,4 輪產物依次 28373、26986、26324、26167 B,最終畫布與原圖 38396 個像素不同、平均偏差 24.549。4 個 90° 不等於沒轉。只有 PNG 真回了原點:mark.png 581 B 走 591、580、594、581 B,最終 hash 與原始相同、0 個像素差異,且每輪 orientationTags 都是 null。

兩個會留痕的開關

關掉「擴大畫布容納整張圖」不是少留白,是真裁:det_q90 關掉的畫布仍是 240×160,只保住 25600 個不透明像素,90° 丟 12800(33.33%)、45° 丟 8823(22.60%)、30° 丟 7211(18.54%),只有 180° 一個像素不丟。

關閉擴大畫布 · 丟掉的不透明像素(總 38400)90°12800 · 33.33%45°8823 · 22.60%30°7211 · 18.54%180°0 · 0.00%畫布一律 240×160 · 四角 [0,0,0,0]

圖 2|det_q90 關掉擴大畫布後被裁掉的像素。

「保持原格式」只對 image/jpeg 成立(第 176 行):mark.webp 692 B 得到 1327 B 的 PNG,mark.bmp 28854 B 變 581 B,shape.svg 165 B 變 2687 B(SVG 一載入就按 180×120 光柵化),anim.gif 597 B 只留第一幀,逐幀得先 GIF 拆圖。名實不符時更險:fake.webp 實為 1200×900 的 JPEG,39587 B,匯出 504277 B 的 PNG。

填透明卻選「JPG(不透明)」也不吭聲:第 185 行把格式改成 PNG,alpha.png 1873 B 拿到的是 alpha_旋轉200x120.png 1873 B,與「保持原格式」逐位元組相同,下拉框還停在 JPG;填充改白色才編出 5342 B 的 JPG。

兩種旋轉的分工

事項只改 Orientation真搬像素再匯出
檔案裡的像素80×120 不動搬成 120×80,SOF 同步
位元組幾乎不變28210 B 變 28003 B
元資料只剩標記在變outAPP1=0、outOri=null
不讀標記的下游仍然躺倒只剩一個方向

下游都讀標記時,改標記就夠;要傳進不讀標記的老系統,就得真搬一次像素並接受重編碼。另外一次只有 1 張:只讀 files[0](第 88 行),載入新圖時第 134 行 resetAll() 把 angle=135 與水平翻轉清零,上一張的參數不帶過去。

Q:在電腦上轉正存一次,以後就不會再躺了嗎?

真搬像素再匯出才不會:那種檔案標記歸零、像素已正,讀不讀 EXIF 都只剩一個方向。只在相簿裡點旋轉,下游一不讀標記就還是躺的。

Q:轉完為什麼機型和拍攝時間都沒了?

匯出是畫布重畫再 toBlob,元資料進不了這條鏈路。23 條匯出記錄裡那張 5 條 IFD 的 SAMSUNG 照片字串欄位全空,PNG 也不帶 eXIf。

Q:JPEG 轉 90° 到底會不會糊?

置換不糊(畫布 hash 160x240:eceb4a99 與手工置換參考一致),糊在重編碼:0.92 寫死在第 186 行,det_q90 28210 B 停在 0° 也變 28003 B、38373 個像素改變。

📎 相關文章

每月同一天和每 28 天一次,一年差出 29 天 2026-10-22 用隨機比較器打亂名單,抽籤就已經不公平了 2026-10-21 加侖有兩種、馬力有兩種、位元組有兩套進位:換算前先問是哪一種 2026-10-21 2 月 29 日出生的人,平年哪天滿 18 歲?兩種週年口徑差一天 2026-10-21 全馬 3:30 要跑多快?九段遞進配速表、前後半差和那段觸底的配速 2026-10-21