ByteScope

Image to C Array

來回兩個方向:圖變成韌體編得過的陣列,陣列也變回一張你看得懂的圖。

圖片不會離開你的瀏覽器——每一個像素都在本機轉換。

來源圖片

把 PNG / JPG / SVG / GIF / WebP 拖進來

也可以點一下選檔。解碼在這一頁裡做,不會上傳。

每邊最多 4096 px。面板通常遠比原圖小,而在這裡縮到多小,就決定了一個位元要扛多少細節。

像素格式

抖色
像素排列
位元順序
位元組順序

輸出預覽

這不是把原圖縮小:它是把打包好的陣列再讀回來畫出的圖,所以抖色的網紋、位元順序、每列補的位元,都跟真正輸出的一樣。

C 原始碼

還沒有東西。
還沒有東西。

把陣列讀回成圖

貼一段 C 陣列,或丟一份原始傾印進來,然後拉寬度,拉到圖不再斜斜錯開為止。這是辨認來路不明的 framebuffer 的標準手法,也是確認自己剛產的陣列有沒有排錯最快的方式。

16/32 位元 token 的位元組順序

只對超過一個位元組的 token 有效,例如 TFT_eSPI 陣列裡那些 uint16_t 的值。

或丟一份 .bin 傾印

快閃記憶體的一塊、SD 卡的一個磁區,只要裡面有像素都行。

寬度不對的 framebuffer 會歪成斜條紋。拉到條紋打直的那一刻,那就是真正的寬度。

像素排列
位元順序
位元組順序

還沒有載入任何東西。

關於這個工具

顯示器真正要的那份陣列

顯示驅動不吃 PNG。它吃的是一串位元組,而且要照它的控制器讀取的方式排好,其中每一項都得有人決定:一個像素一個位元還是十六個位元?最左邊那個像素放在第 7 位還是第 0 位?一列接一列,還是切成八個像素高的頁?紅色在高五位還是低五位?把圖丟進來、選好格式,位元組就照著那個排法打包——mono、gray2、gray4、gray8、LVGL 要的 alpha1 到 alpha8 遮罩,以及 rgb565、rgb888 和 argb8888。控制項旁邊那張預覽不是把原圖縮小:它是把打包好的陣列再讀回來畫的,所以你看到的就是面板會顯示的。

一個像素只有一個位元,這個位元怎麼花

128x64 的 OLED 有 8,192 個像素,每個一個位元,所以整張圖的成敗就在黑白分界線切在哪裡。線稿和圖示用單純的門檻就對了,滑桿就是在移動那條線。照片則要把誤差散開:Floyd–Steinberg 把誤差擴散給旁邊的像素,做出報紙印刷那種顆粒感;Bayer 4x4 的規則矩陣則給你一片整齊的網紋,在小面板上往往更好認。亮度和對比在切之前作用,invert 在切之後,而且每一個都會在你拖動的當下重畫預覽——因為一個圖示撐不撐得住一個位元,只能用它實際會顯示的尺寸看出來。

把 framebuffer 讀回成一張圖

反方向這件事沒人做,而它正好回答你手上真正的問題:這份陣列到底對不對?貼一段 C 陣列(0x 的位元組、十進位、\x 跳脫、一整塊裸的十六進位,有沒有大括號、註解和 #define 行都無所謂),或是丟一份從快閃記憶體挖出來的 .bin,它就會被畫成圖。接著拉寬度滑桿。寬度不對的 framebuffer 會歪成一道道斜條紋,而寬度一對,整張圖就當場立起來——這是辨認來路不明傾印的標準手法。它同樣可以拿來檢查自己的輸出:轉一個圖示,把陣列貼回來,如果出來的東西看起來就是你的圖示,那位元順序和每列補的位元都對了。讀不出來的 token 會連同位置一起報出來,不會被吞掉,所以貼上的陣列打錯一個字,賠掉的是一個值,不是後面整份。

樣板、位元組順序,還有那個一定會咬人的

同樣一串位元組,看是誰要編譯它,包裝就不一樣:素的 static const uint8_t 陣列、附 _WIDTH 和 _HEIGHT 巨集的 Adafruit GFX PROGMEM 陣列、用 #define 寫尺寸的 u8g2 XBM、LVGL v8 的 lv_img_dsc_t 和 v9 的 lv_image_dsc_t、丟給 TFT_eSPI pushImage() 的 uint16_t 陣列,還有旁邊直接把 framebuf 建構式寫好的 MicroPython bytes 字面值。會咬人的是 TFT_eSPI:它讀 16 位元像素時先讀高位元組,所以只有這個樣板要把 RGB565 用大端序打包,其他一律小端序。在這裡你選樣板,位元組順序就跟著走——一份編得乾乾淨淨、顏色卻整個錯掉的陣列,是沒有人喜歡花時間去找的臭蟲。整套都在這個分頁的 Web Worker 裡跑:不上傳、不用帳號,還沒公開的產品美術素材不會離開你的機器。

常見問題

SSD1306 的 OLED 要設成什麼?

格式選 mono、像素排列選分頁、位元順序選 LSB 在前。SSD1306 和 SH1106 把記憶體存成八個像素高的直向頁,一欄一個位元組,該頁最上面那個像素放在第 0 位——分頁加上 LSB 在前產生的正好就是這個。如果你老老實實逐列排一份陣列送過去,畫面看起來會像被切成八條交錯的片段,原因就在這裡。要是你不是直接寫控制器,而是餵給 u8g2 的 XBM 輔助函式,那就改成逐列加 MSB 在前。

為什麼送進 TFT_eSPI 顏色整個跑掉?

幾乎都是位元組順序。pushImage() 讀 16 位元緩衝區時先讀高位元組,所以低位元組在前打包的 RGB565,會把純紅 0xf800 變成 0x00f8——一個暗藍灰。陣列編得過、大小也對,只有顏色是錯的。在這裡選 TFT_eSPI 樣板,它會替你用大端序打包,並且把這件事寫出來。如果是從別處抄來的陣列顏色亂掉,把它丟進反向檢視器切換位元組順序,圖會立刻告訴你原本是用哪一種寫的。

可以產 LVGL 的二進位圖檔嗎?

不行,而且不會假裝可以。這裡吐出來的是 C 原始碼:像素陣列,加上一個欄位都有名字的 lv_img_dsc_t(v8)或 lv_image_dsc_t(v9)結構體字面值——就是你加進專案裡編譯的那種東西。帶自有標頭的 .bin 是另一種產物,與其憑印象亂猜,這裡選擇不做。色彩格式那一欄也留給你:你給什麼 token 就原樣寫出去,留空就會得到一個 UNKNOWN 加一行 TODO,絕不會替你發明一個常數。

我有一份快閃記憶體傾印,但完全不知道圖多寬。

把 .bin 丟進反向檢視器,猜一個像素格式,然後把寬度掃描從 1 往上拉。只要寬度不對,每一列就會固定偏移一點,直線邊緣因此變成斜線,畫面就歪掉——直到寬度剛好等於真正的 stride,所有東西同時對齊。接著調整位移量,把像素前面那段標頭跳過去。資料不夠長在這裡不算錯:後面沒有位元組的像素會畫成透明並計數,所以半截的傾印照樣掃得動,工具不會拒絕顯示。

我的圖會被上傳嗎?

不會。檔案由你自己的瀏覽器解碼,像素的量化和打包在這個分頁的 Web Worker 裡做完,分頁一關就全部沒了。沒有上傳步驟、沒有帳號,這頁也沒有伺服器端。這件事在這裡比在多數工具上更要緊:還沒發表的產品那一整套圖示,正是那種「別貼到線上轉檔網站」的檔案。