效能最佳化
上一篇講了怎麼把壓縮位元組快速解成像素。這篇接著講:解出來的像素,怎麼做到一張幾億像素的大圖也能流暢瀏覽——拖曳、縮放都跟手不卡。
一張圖能有多大
手機隨手拍是 1200 萬像素,單眼 2400 萬,而一張衛星圖可以達到 18641 × 18641 ≈ 3.5 億像素。如果老老實實把它全部解碼進記憶體:
1.4 GB 記憶體、好幾秒的等待——只為了顯示一張縮到螢幕裡、肉眼看不清每個像素的圖。絕大多數像素根本沒有機會被看到。 果核看圖的思路是:只在需要的清晰度、需要的區域上花力氣。這靠三件事配合:金字塔、圖塊、按需載入。
金字塔:依需要的清晰度解碼
把原圖反覆縮小一半,存成一疊由大到小的層——這就是影像金字塔。最底層是原始尺寸,往上每層邊長減半,直到縮成一張 256×256 的小圖。一張 18641² 的圖大約有 8 層。
以「符合視窗」開啟圖片時,整張圖縮進螢幕,根本不需要 18641² 的原始資料——直接拿某一個中間層來顯示,又快又省。等你放大查看局部細節,再切到更下面、更清晰的層。永遠使用剛好夠清晰的那一層,這就是金字塔的意義。多出來的層因為每層只有下一層的四分之一大,全部加起來也只比原圖多三分之一,代價很小。
圖塊:只解看得見的那部分
光有金字塔還不夠——放大看細節時用的是底層大圖,它依然有幾億像素。所以每一層都再切成 256×256 的小方塊(圖塊),顯示時只處理落在螢幕內的那些。
一個 4K 可視區域鋪滿,也只有約 135 個圖塊的工作量——和「全圖幾億像素」完全不是同一個量級。圖塊還能平行解碼:把這一百多塊分給多個 CPU 核心同時處理,呈現更快。
按需載入:拖到哪,解到哪
把金字塔和圖塊合起來,就有了一套「可視區域驅動」的載入機制。每次縮放或平移,畫布會算出目前可視區域涵蓋了哪一層、哪些圖塊,把還沒解碼的那幾塊交給背景執行緒去解,解好一塊就貼上一塊。
解碼全在背景執行緒進行,不會卡住介面;已經解過的圖塊會快取起來,拖回去不用重新解碼。於是不論圖多大,每一刻真正在解碼的,永遠只是螢幕上這一小片。
先看見,再變清晰
還有最後一步體驗最佳化。切換到一張大圖的瞬間,可見圖塊還沒解完,畫布會先鋪上一張整張圖的低解析度預覽兜底——讓你立刻看到完整畫面,然後清晰的圖塊再一塊塊蓋上來,清晰度只增不減。
這裡有個容易出錯的細節:預覽圖和圖塊是兩層獨立繪製,如果切換時機沒處理好,會出現「清晰預覽 → 閃一下變更模糊 → 再變清晰」的倒退。我們特別確保了這條單調變清晰的路徑,整個過程不會出現回退閃爍。
把這套組合起來
- 金字塔決定「用多清晰的資料」——依顯示縮放選層,不浪費在看不清的細節上;
- 圖塊決定「解多大範圍」——只處理螢幕上的那一小片;
- 按需載入 + 背景平行決定「什麼時候解」——拖到哪解到哪,不卡介面;
- 低解析度預覽兜底決定「先看到什麼」——立刻有畫面,再逐步清晰。
四者合起來,記憶體和時間就只花在你真正看得見的像素上。這就是幾億像素的大圖也能隨手拖曳、縮放都跟手不卡的原因。