@helping-ai-workflow/snapcode
v0.8.1
Published
檔案 ↔ 截圖友善色塊網格圖的無失真編解碼 CLI(編碼端純 stdlib)。node shim,需系統 python3 >= 3.9。
Maintainers
Readme
snapcode
檔案 ↔ 截圖友善色塊網格圖,無失真編解碼 CLI。任意檔案編碼成一張(或多張)色塊網格 PNG/終端 ANSI 圖,截圖(甚至印在螢幕上再重拍)就能解回原始位元組,並以 CRC32 逐 chunk 驗證一致性。
snapcode 是什麼
- 編碼端:純 Python 標準庫(
zlib+struct+ 自帶 Reed-Solomon),不依賴 numpy / pillow / scipy,適合丟到無網路的遠端環境跑。 - 解碼端:pillow + numpy + scipy,做定位方塊偵測、仿射校正、黑白/彩色電平 校準、erasure 標記與 Reed-Solomon 糾錯,容忍遮擋、旋轉、縮放、低解析度重拍。
- 一張圖 = 一個資料方陣,3 個 L 形定位方塊嵌入方陣三個角(QR 式,非外掛
邊框)+ 1 格 separator 白環 + metadata(10 byte,含版本/尺寸/RS 參數;v0.7 起
以黑白散點從左上定位方塊漸疏放射、融入彩色場,取代舊的連續黑白塊)+
資料格網,方陣外圍只留 2 格薄靜區(無獨立外框)。
單檔過大時自動切成多張,解碼端依檔頭的
file_id/idx/cnt自動重組、缺塊即報錯。
版面配置(v0.5:嵌入式定位方塊)
v0.4 的定位方塊是掛在資料方陣「外面」的獨立邊框(quiet 4 格 + finder 7 格 + separator 1 格,四邊都要),v0.5 起改成 QR 碼式——3 個定位方塊直接嵌進方陣 左上/右上/左下三個角,右下角留給資料,靜區也從 4 格收到 2 格:
┌── quiet 2 格 ──────────────────────────────┐
│ ┌────┐░ ·metadata 散點·放射· ┌────┐░ │
│ │ TL │░ │ TR │░ │
│ │5×5 │░ 資料格 … │5×5 │░ │
│ └────┘░ └────┘░ │
│ ┌────┐░ │
│ │ BL │░ 資料格 …(右下角無定位方塊)│
│ │5×5 │░ │
│ └────┘░ │
└─────────────────────────────────────────────┘░ = 1 格 separator 白環(定位方塊與資料格之間的緩衝,避免誤判邊界);quiet
只圍在整個方陣外圈一圈,不再另外疊加邊框。同一份資料下,v0.5 產出的 PNG 比
v0.4 明顯更小(實測面積省 ~25–50%,視資料量與長寬比而定,見〈容量表〉與
〈限制〉)。
v0.7 metadata 散點:metadata 從「左上一塊連續黑白方陣」改成從左上定位方塊
漸疏放射的孤立黑白散點(checkerboard 保證彼此不相鄰、離定位方塊留 moat),
融入彩色場、消除左上白斑,維持神秘感(看不出哪些格是 metadata)。同時 struct
由 16 byte 縮到 10 byte(散點 128→80),grid 再小一圈。散點座標由 (cols,rows)
以確定性整數演算法算出(encode/decode 兩端一致,屬格式契約);解碼端因散點位置
依賴 grid 尺寸,metadata 重試窗依 grid 大小自適應加寬以涵蓋非等向 cell 的估計誤差。
流程圖(文字版):
編碼: 原始檔案
│ zlib 壓縮
▼
切 chunk(依容量二分搜尋 chunk size)
│ 加容器檔頭(檔名/長度/CRC32)
▼
Reed-Solomon 編碼 + 確定性 interleave(置換抗連續遮擋)
│
▼
格網(metadata 區 + 資料區)→ PNG 或 ANSI half-block 印圖
解碼: 截圖 / 拍照
│ 定位方陣三角嵌入的 3 個 L 形定位方塊 → 解仿射矩陣
▼
讀 metadata 區(cols/rows/bits/RS 參數,CRC32 驗證)
│
▼
依仿射矩陣逐格取樣 → 黑白/彩色電平校準 → 最近色分類(超界標 erasure)
│
▼
deinterleave → Reed-Solomon 糾錯(erasure 優先,失敗退回盲糾)
│
▼
解容器檔頭 → 依 file_id/idx 重組多張 → CRC32 驗證 → 還原原始檔案Quick start
pip install -e .snapcode file.txt # 編碼:在終端印出色塊圖(截圖用;預設不寫檔)
snapcode file.txt --png # 編碼:改輸出 file.png 檔(不印終端)
snapcode 截圖.png # 解碼:自動判向(PNG 且解得開 → 當 snapcode 圖解碼)或用 npm 全域安裝(免 clone)
已上架 npm,一行安裝(需系統 python3 >= 3.9 在 PATH):
npm install -g @helping-ai-workflow/snapcode
snapcode file.txt # 用法與上面完全相同npm 版把純 Python 包 bundle 進套件,snapcode 指令透過系統 python3 執行——
編碼端零額外安裝(純 stdlib,不下載任何 runtime)。要解碼截圖才需另裝三方庫:
pip install pillow numpy scipy(解碼時若缺這些依賴,snapcode 會直接提示上面這行。)
其他旗標見下方〈CLI reference〉;安裝步驟以本節為準,其餘章節不重複。
資料夾
snapcode myproj/ # 打包整個資料夾 → myproj.png(大檔自動切多張)
snapcode myproj.png # 還原出完整 myproj/(已存在則 myproj.1/)打包預設跳過 .git / .svn / node_modules / __pycache__ / .venv / *.pyc 等;
--all 連這些一起打。預估超過 10 張圖時會要求確認(--yes 跳過、給腳本用)。
symlink / 裝置檔會被跳過並印警告。還原時擋絕對路徑與 ../ 逃逸。
遠端部署(無網路)
編碼端零第三方依賴,可以把整支工具打包成單一 .py 檔丟到無網路的遠端機器:
snapcode --bundle --key <key> -o qr.py # --key 省略時預設見 --help產出的 qr.py 是 base64+zlib 內嵌五個模組 + loader 的單檔,已剝除註解、抹去
內部名稱,並以通行碼閘門保護 —— 純標準庫可執行,但第一個參數必須是通行碼:
python3 qr.py <key> <file> # terminal 直接印出色塊圖,截圖回傳即可通行碼錯誤或未提供時只印一行 usage: qr.py <mode> <file> 並以 exit 1 結束(不透露
需要通行碼、不透露工具身分),適合放在共用工作站避免他人誤用。--key 可自訂通行碼。
拿到印出的色塊圖後,直接截圖(螢幕四周有其他 UI 也沒關係),回傳截圖後在有安裝
decode extra 的機器上 snapcode 截圖.png 解回原始檔案。
qr.py 本身不支援再 --bundle(會直接報錯拒絕),需在完整安裝環境
(有原始 snapcode/*.py)執行 --bundle。
CLI reference
snapcode [-h] [--zip] [--unzip] [-o OUT] [--bits {1,2,3,4,5,6}]
[--scale SCALE] [--aspect ASPECT] [--max-cols MAX_COLS]
[--ecc ECC] [--force] [--quiet] [--png] [--cli]
[--bundle] [--key KEY] [--all] [--yes] [--version]
[inputs ...]| flag | 說明 |
|---|---|
| inputs | 檔案或 snapcode 圖(自動判向:PNG 且解得開 → 解碼,否則編碼) |
| --zip | 強制編碼(即使輸入是可解的 snapcode PNG) |
| --unzip | 強制解碼(不嗅探,解不開直接報錯) |
| -o, --out | 編碼=輸出前綴(或目錄);解碼=輸出目錄 |
| --bits {1,2,3,4,5,6} | 每格位元數(預設 6=64 色高密度,乾淨截圖用);要耐壓縮/遮擋改小,1=黑白最穩 |
| --scale | PNG 每格像素(預設 6);截圖後解析度不足時建議調大 |
| --aspect | 長寬比 寬:高(預設 16:9) |
| --max-cols | 單張最大格寬(預設 240);超過容量自動切成多張 |
| --ecc | 每 255 byte 的 Reed-Solomon 糾錯位元組數(預設 24) |
| --force | 解碼輸出允許覆蓋同名檔(否則自動加 .1 .2 … 後綴) |
| --quiet | 靜音進度訊息(仍會印色塊圖,除非重導向) |
| --png | 編碼時輸出 PNG 檔,不在終端印色塊圖 |
| --cli | 編碼時在終端印色塊圖(預設行為,不寫 PNG 檔) |
| --bundle | 輸出 stdlib-only 單檔編碼器(見〈遠端部署〉),-o 指定輸出檔名(預設 qr.py) |
| --key | bundle 通行碼;產物需 python3 qr.py <key> <file>(預設見 --help) |
| --all | 資料夾打包含 .git/node_modules 等跳過清單項目 |
| --yes | 跳過資料夾 >10 張圖的確認提示 |
| --version, -v | 顯示版本 |
--zip 與 --unzip 互斥;--png 與 --cli 互斥;--ecc 需在 2..100 之間;
混合輸入(同批既有要編碼又有要解碼的檔)搭配 -o 時語意不明,會直接報錯,請分開跑。
bits 5/6 高密度說明
--bits 每增加 1,每格可表示的顏色數翻倍(--bits 4=16 色、--bits 5=32 色、
--bits 6=64 色),格數不變下圖能塞進更多資料、成品也更小張。但 bits 5 與
bits 6 的最小色距實測相同(都是同一批 lattice 調色盤在 32/64 點下的必然結果,
並非 bits 5 比 bits 6 更疏、更耐雜訊)——色距本身隨 bits 增加而變薄,分類容錯
隨之降低。因此 --bits 5/--bits 6 定位在乾淨截圖的高密度傳輸(螢幕截圖
直接貼、無二次壓縮);預設即為 --bits 6。若傳輸路徑會經 JPEG 或其他有損
壓縮(LINE/Messenger 等)、或截圖可能被部分遮擋,請改用更低的 --bits
(--bits 1 黑白最穩、色距最大;2/3 次之)——bits 越低格數越多,單格色距
更大、容錯更寬裕。(--bits 4 的 16 色手調色盤實測色距反而是全部
bits 中最窄的,不適合當耐壓縮首選,勿誤用。)
容量表
單張圖在預設 RS 糾錯(--ecc 24)與 16:9 長寬比下,扣除 RS 冗余與容器檔頭
(約 40 byte)後,zlib 壓縮後可用 payload 位元組數(近似值;文字檔/原始碼
等可壓縮性高的內容,zlib 壓縮前的原始檔案可再大上數倍):
| --max-cols | --bits 1 | --bits 2 | --bits 3 | --bits 4 | --bits 5 | --bits 6 |
|---:|---:|---:|---:|---:|---:|---:|
| 120 | 864 B | 1,765 B | 2,665 B | 3,566 B | 4,466 B | 5,367 B |
| 240(預設) | 3,606 B | 7,249 B | 10,891 B | 14,534 B | 18,200 B | 21,843 B |
| 360 | 8,166 B | 16,369 B | 24,595 B | 32,798 B | 41,024 B | 49,227 B |
(v0.5 起 3 個定位方塊嵌入方陣本體佔約 108 格;v0.7 起 metadata 由連續塊 128 格
改散點 80 格,reserved 共約 188 格。因為外圍不再疊加獨立邊框、metadata 又縮小,
同一份資料實際需要的 PNG 尺寸明顯縮小——真正的容量收益要看「同資料所需圖檔
面積」而非這張固定 --max-cols 對照表,見下方〈限制〉。)
超過單張容量時,snapcode 自動切成多張圖(檔名附 .NNN.png 序號),解碼端依
file_id/idx/cnt 自動重組,缺任一張會直接報錯要求補齊。
限制
v0.5 把定位方塊從資料方陣外的獨立邊框改成嵌入方陣三角(見〈版面配置〉),
靜區也從 4 格收到 2 格——同一份資料所需的 PNG 面積因此明顯縮小(實測約省
25–50%,視資料量與長寬比而定),且這個瘦身沒有拿 robustness 交換:旋轉
±5°、縮放 0.5×–2.4×、JPEG 品質 55 以上、局部遮擋、terminal 截圖(含行距造成
的縫隙、非等向字元格)等劣化通道,v0.5 的嵌入式版面實測結果與 v0.4 外框式
版面相同(皆全綠,見 tests/test_robustness.py)——不是「乾淨截圖限定」,
放心用在會經過壓縮或手機拍照的傳輸路徑。
真正的取捨在別處:在固定 --max-cols 下,因為定位方塊現在直接佔用方陣
內的資料格(而不是像 v0.4 那樣掛在外面不吃格),可用 payload 比 v0.4 略降
~1.5%(見上方容量表附註);要耐雜訊/劣化仍建議用較低 --bits(--bits 1
色距最大最穩),與 v0.4 的建議一致,並未因嵌入式版面改變。
螢幕解析度注意事項
- 建議每格像素(cellpix,取決於截圖時的顯示尺寸而非
--scale)維持 ≥ 3 px, 低於此值 finder 偵測與電平校準容易失敗;decode_image對第一趟失敗會自動 Lanczos ×2 放大重試一次,但仍以原圖夠大最可靠。 - 1080p 螢幕建議
--max-cols 240以內、--scale 6起跳;4K 螢幕可以放大到--max-cols 360+更高--scale,或維持同容量換取更粗的格線(抗遮擋/抗模糊)。 - 用
snapcode file在終端印圖時,若行寬不足會提示「終端寬度不足」;縮小字體 (或加大終端視窗)讓一整行塞得下,再重新執行。 - 截圖時周圍夾雜其他視窗 UI 沒關係,定位方塊偵測會自動排除雜訊聚焦到色塊圖本體; 但不要用有損通訊軟體(LINE/Messenger 等)二次壓縮傳輸再截圖——不過實測 JPEG 品質 55 以上仍可正常解碼,品質更低則不保證。
疑難排解
| 錯誤訊息 | 常見原因 | 對策 |
|---|---|---|
| 找不到定位方塊 | 截圖裁切到定位方塊、圖太小/太模糊、或前景另有大片深色區塊干擾 | 把截圖裁到色塊圖本體附近重試;用更大 --scale 重新產圖 |
| metadata 讀取失敗 | 截圖被過度縮小、metadata 區被裁到或被遮擋 | 確認截圖含完整色塊圖四角;用更大 --scale/原始解析度截圖 |
| Reed-Solomon 糾錯失敗,損壞過多 | 遮擋/雜訊超過 --ecc 糾錯上限 | 用更大的 --ecc 重新編碼;或換用 --bits 1(黑白對雜訊最穩) |
| 截終端機圖解不開,PNG 卻可以 | 終端 line-spacing(行距)> 1.0 時,行間縫隙會切斷定位方塊(深/淺色主題縫皆未支援) | 把終端 line-spacing 調回 1.0 再截圖;或改截 PNG 檔 |
開發
測試:
pytest tests/模組職責:
| 模組 | 職責 |
|---|---|
| snapcode/codec.py | 容器格式(SC v1)、置換/interleave、bit↔symbol、encode_file/reassemble — 純 stdlib |
| snapcode/layout.py | 版面幾何、調色盤、metadata 編解碼、build_grid/build_canvas — 純 stdlib |
| snapcode/render.py | 手寫 PNG writer、ANSI half-block 印圖 — 純 stdlib |
| snapcode/_reedsolo.py | vendored Reed-Solomon(見 NOTICE)— 純 stdlib |
| snapcode/detect.py | 解碼端:定位方塊偵測、仿射校正、電平校準、分類、RS 還原(需 pillow/numpy/scipy) |
| snapcode/cli.py | 參數解析、方向嗅探、檔名安全、--bundle 單檔打包 |
tests/helpers.py 提供測試共用的 encode_pngs(產多張 PNG)與 parse_ansi
(把 ANSI 輸出解析回格網索引),避免各測試檔各自重寫。
