圖片轉 ASCII 轉換是一種在本機端、確定性的處理流程,會將一張解碼後點陣圖的可見色調,轉成一格文字組成的網格:先把來源圖片縮放成指定的輸出寬度,每個輸出格只取樣一個像素,再把每個樣本的相對亮度對應到一組固定的、由十個字元組成的密度階梯,從 @ 一路遞減到愈來愈淺的符號,最後到空白。輸出是視覺化的 ASCII 藝術:它不是 OCR、不是圖片說明、也不是文字辨識。結果中的每一個字形都對應到一個取樣像素的亮度,這也是為什麼最終區塊可以當作純文字複製、存成 TXT 檔,或貼到任何接受字元的聊天、文件或終端機中。由於解碼、縮放、取樣與亮度計算全部都在當前的瀏覽器分頁內執行,原始圖片與產生的文字永遠不會離開使用者的電腦,而來源壓縮檔的顏色設定檔、後設資料或動畫時序等資訊,也不會被保留在產生的字串中。正是固定的階梯、單一像素取樣與純粹的本機處理,構成了本文脈絡下「圖片轉 ASCII」轉換的定義。

image to ascii explained
Image to ASCII Explained: How Pixels Become Text

圖片轉 ASCII 轉換實際上做了什麼

轉換流程只接受一張點陣圖,將其解碼,並產出一段文字區塊。中間所有步驟都是固定的管線:縮放至指定的欄數、每個輸出格讀取一個 RGBA 樣本、將樣本與白色合成、轉換為相對亮度,再從密度階梯中挑出十個字形之一。輸出內容只包含被選中的字形,以及分隔列的換行字元;它不帶任何顏色、透明度、動畫,也不包含來源檔案中的任何內嵌設定檔。因為每一格就是一個字元,所以輸出的大小是由字元總數決定,而非像素數,這就是為什麼一張高瘦的圖片在寬欄設定下,即使解碼後的像素面積看起來不大,卻仍可能撞到文字上限。

重要的是,必須把這項技術與相近的手法區分開來。圖片轉 ASCII 不會辨識圖片中已存在的字母、單字、物體或任何語意內容。若來源是一張印刷頁面的照片,結果只會是一片形狀像該頁面的灰色紋理,而不是頁面上原本承載的文字。QR code、圖說、浮水印,或任何你能用眼睛讀到的東西,情況都一樣。轉換流程只知道亮度,而亮度也是它唯一能表示的東西。

挑選每個字元的亮度公式

每個輸出格是由一個簡短、定義明確的計算決定,從取樣像素開始,到十個階梯位置中的某一格結束。瀏覽器會把來源圖片繪製到一個小畫布上,畫布尺寸與預定的 ASCII 欄數和列數完全一致,並把畫布填滿白色,讓透明區域有一個明確的背景,接著使用高品質影像平滑來執行縮放。縮放是由瀏覽器實作,因此不同引擎所產生的重新取樣像素可能略有差異,特別是在高頻紋理或大幅縮小的情況下;但後續演算法不受這些細微差異影響,結果仍是確定性的。

從縮放後的畫布中,getImageData 會為每個輸出格回傳一個 RGBA 樣本。Alpha 通道會先與白色合成,接著每個 sRGB 通道會用標準的 W3C 相對亮度轉換函式進行線性化。亮度值的計算公式為 0.2126 R + 0.7152 G + 0.0722 B,因此綠色像素的權重比紅色高,紅色又比藍色高。所得的 0 到 1 之間的值,會乘以 9 並四捨五入,以從十個階梯項目中選出一個。較暗的樣本落在階梯前端,較亮的樣本落在尾端,中間灰階的樣本則落在中段。反向模式只是把索引 0 對應到階梯最後一個字元,索引 9 對應到第一個字元,因此在一般模式下印成空白的明亮天空,在反向模式下會印成 @。

你可以從 MDN 的 drawImage 參考文件MDN 的 getImageData 參考文件 中,看到重新取樣步驟與逐像素讀取步驟的說明;這兩個瀏覽器基礎介面,正是整個轉換流程所依賴的兩個原語。

輸出寬度與列數如何計算

你輸入的輸出寬度是一個嚴格的 10 到 200 之間的十進位整數。超出範圍的值會直接失敗,而不是被夾住限制範圍;像 +120、120.0、0120 或 1.2e2 這類寫法會被拒絕。接著,列數會根據來源長寬比計算,並對等寬字元格的形狀進行明確的修正。

一般等寬字形的寬度大約是高度的一半,因此列數的計算方式是:將「圖片高度除以圖片寬度」乘以你設定的輸出寬度,再乘以 0.5。計算結果會四捨五入到最接近的整數列數,最少為 1 列。以一張 800×600 的來源圖,輸出寬度設為 100 為例,計算為 round(600 ÷ 800 × 100 × 0.5) = round(37.5) = 38 列,最終產生 100 × 38 + 37 個換行字元 = 3,837 個字元的總序列化輸出。同樣的圖若以最大寬度 200 處理,則會得到 round(600 ÷ 800 × 200 × 0.5) = round(75) = 75 列,共 15,074 個字元。

這個 1:2 的格子假設是實用的預覽近似值,而不是對你最終用來顯示藝術字所用字型的實際量測。不同的等寬字型、行高、終端機、編輯器縮放或字距,都可能讓下載下來的文字區塊看起來比預覽更高或更矮。當視覺精確度很重要時,請先用已知的等寬字型來呈現這個 TXT 檔,再判斷其長寬比。

密度階梯與反向模式改變了什麼

這個固定的階梯剛好只有十個項目。第一個是密度最高的字元 @,最後一個是空白;中間八個則是依序遞減亮度的符號,設計上是為了讓字形的可見密度能對應到它所代表的亮度區間。反向模式使用相同的十個字元,但順序顛倒,因此索引 0 對應到空白,索引 9 對應到 @。這個簡單的交換適用於把 ASCII 藝術放在深色背景上的情況,因為在一般模式下,階梯會讓來源中明亮的區域變得看不見。

階梯中的位置一般模式用途反向模式用途
第一個字元(輸出最暗)密度最高的符號 (@)密度最低的符號 (空白)
中間字元依序遞減亮度的符號依序遞增密度的符號
最後一個字元(輸出最亮)密度最低的符號 (空白)密度最高的符號 (@)

切換階梯方向並不會改變管線中的其他任何部分。來源圖片、縮放、Alpha 合成、亮度公式與列數計算全部維持不變,只有最後的對照表被翻轉。

將本機圖片轉成 ASCII 文字

整個轉換刻意設計得很短。打開瀏覽器中的 Image to ASCII 工具,然後依照下列三個步驟操作。

  1. 在可見的編碼與解碼上限內,選擇一個 PNG、JPEG、GIF 或 WebP 檔案。瀏覽器必須真的把檔案解碼後,轉換才能進行。檔案副檔名或 MIME 類型相符,只會通過最初的格式檢查;內容毀損或不支援的編解碼器設定檔仍會在解碼階段失敗,並不會產生任何 ASCII 輸出。
  2. 輸入一個介於 10 到 200 字元之間的整數輸出寬度,並可選擇是否反轉固定密度階梯。寬度欄位會拒絕任何不是純粹十進位整數的輸入;反向模式是一個單一開關,只會在下一次產生時翻轉階梯對照。
  3. 產生完整的 ASCII 結果,然後複製可見的文字或下載本機的 TXT 檔。預覽與下載會從同一個產生流程一起產出,因此 TXT 永遠會與你看到的內容一致。修改寬度、階梯方向或來源圖片,會撤銷先前的下載連結,並清除先前的結果與複製狀態,因此舊的區塊不會被意外儲存。

決定轉換是否成功的限制

多項限制會互相影響,只要任何一項未通過,就會中止轉換,且不會產生部分輸出。工具會回報觸發的特定限制,並清除任何先前的結果。

邊界數值存在的原因
編碼後檔案大小上限 15 MiB避免非常大的壓縮檔案被解碼。
解碼後邊長上限 8,192 像素限制完全解碼點陣圖的任一邊長度。
解碼後像素總面積上限 24,000,000 像素無論長寬比為何,皆限制解碼後佔用的總記憶體。
輸出寬度10 到 200 字元僅接受嚴格整數;超出範圍的值會被拒絕,而非夾住限制範圍。
序列化輸出總量上限 50,000 字元(含換行)高瘦的長寬比即使在合法寬度下,仍可能超出預算而中止。

解碼後的各項限制,是為了避免當壓縮檔很小、但其點陣圖卻很大時,瀏覽器意外耗用大量解碼記憶體;而 50,000 字元的限制則是用來防止產生的 TXT 在複製與下載步驟中膨脹到無法處理。精確的輔助測試會錨定這些邊界:寬度 10 與 200 可通過,9 與 201 會被拒絕;50,000 字元可通過,50,001 會被拒絕。

轉換在哪裡執行,以及為什麼這很重要

所有步驟都在當前的瀏覽器分頁中執行。解碼會優先使用 createImageBitmap(若該 API 可用);產生的 ImageBitmap 會在被取代、發生過時完成時,或在元件卸載時被關閉。若 createImageBitmap 對該檔案不可用,則會使用一個短暫的本機物件網址載入 Image 元素,並在載入完成或失敗後撤銷。在 fallback 路徑中,解碼後的影格會立刻快照到 Canvas,因此當你調整寬度或階梯方向時,動畫 GIF 不會在控制項背後持續變動。

來源尺寸會與輸出控制項分開驗證,因此在非同步解碼仍在進行時變更寬度,並不會讓舊的寬度決定該圖片是否被接受。複製操作會以世代計數器與掛載狀態檢查加以保護,因此較舊的剪貼簿 Promise 不可能在後續編輯後,把「已複製」的指示重新恢復。複製計時器會以身分識別進行比對,在被取代或卸載時取消;下載用的物件網址同樣會在被取代與卸載時撤銷。圖片、其像素、產生的文字與檔名永遠不會離開瀏覽器,這就是為什麼 TXT 輸出只包含 ASCII 字元與換行分隔符,不會夾帶其他內容。

若想進一步了解,請參閱 使用瀏覽器工具為 GIF 加入文字

若想進一步了解,請參閱 EXIF 移除工具:透過本機重建像素來去除後設資料