Skip to content

LCD & OLED Font Generator (Bitmap to C Array)

Turn Chinese or English text and images into C arrays for SSD1306, SH1106, ST7920 and Adafruit GFX. Crisp 12×12 and 16×16 pixel fonts, all four PCtoLCD2002 scan modes, plus a decoder for unknown arrays.

No Tracking Runs in Browser Free
Everything runs in your browser — text and images never leave this device.

Matches SSD1306 / SSD1309 / SH1106 / ST7565 page memory and MicroPython framebuf.MONO_VLSB — write the bytes straight to the display.

Preview

Click a pixel to toggle it.
    4 glyph(s) · 64 bytes
    Reference: the character 中 (16 × 16) in every scan direction

    Lit pixel = 1, GNU Unifont glyph. Compare these bytes with your own array to tell which settings it was made with.

    Scan direction Bit order Bytes (hex)
    Row by row (horizontal bytes) MSB first — first pixel in bit 7 01 00 01 00 01 00 01 00 3F F8 21 08 21 08 21 08 21 08 21 08 3F F8 21 08 01 00 01 00 01 00 01 00
    Row by row (horizontal bytes) LSB first — first pixel in bit 0 80 00 80 00 80 00 80 00 FC 1F 84 10 84 10 84 10 84 10 84 10 FC 1F 84 10 80 00 80 00 80 00 80 00
    Column by column (vertical bytes) MSB first — first pixel in bit 7 00 00 00 00 0F F0 08 20 08 20 08 20 08 20 FF FF 08 20 08 20 08 20 08 20 0F F0 00 00 00 00 00 00
    Column by column (vertical bytes) LSB first — first pixel in bit 0 00 00 00 00 F0 0F 10 04 10 04 10 04 10 04 FF FF 10 04 10 04 10 04 10 04 F0 0F 00 00 00 00 00 00
    Pages of columns (vertical bytes, SSD1306 page order) MSB first — first pixel in bit 7 00 00 0F 08 08 08 08 FF 08 08 08 08 0F 00 00 00 00 00 F0 20 20 20 20 FF 20 20 20 20 F0 00 00 00
    Pages of columns (vertical bytes, SSD1306 page order) LSB first — first pixel in bit 0 00 00 F0 10 10 10 10 FF 10 10 10 10 F0 00 00 00 00 00 0F 04 04 04 04 FF 04 04 04 04 0F 00 00 00
    Columns of rows (horizontal bytes, 8-pixel strips) MSB first — first pixel in bit 7 01 01 01 01 3F 21 21 21 21 21 3F 21 01 01 01 01 00 00 00 00 F8 08 08 08 08 08 F8 08 00 00 00 00
    Columns of rows (horizontal bytes, 8-pixel strips) LSB first — first pixel in bit 0 80 80 80 80 FC 84 84 84 84 84 FC 84 80 80 80 80 00 00 00 00 1F 10 10 10 10 10 1F 10 00 00 00 00

    Pixel fonts: GNU Unifont (16 px) and Fusion Pixel Font (12 px), both under the SIL Open Font License 1.1. License texts

    All eight scan direction and bit order combinations are checked byte for byte against an independent reference implementation, and the presets against the SSD1306, SH1106 and ST7565 datasheets and the Adafruit GFX, U8g2, LVGL and MicroPython source code. — Go Tools Team · Oct 3, 2026

    Built and verified by the Go Tools engineering team.

    Quick answers

    Which settings does an SSD1306 OLED need?

    Pages of columns · LSB first Pages of columns, LSB first, lit pixel = 1 — when you write the bytes straight into display memory.

    Which settings does Adafruit GFX drawBitmap() need?

    Row by row · MSB first Row by row, MSB first, lit pixel = 1, each row padded to whole bytes.

    How many bytes is a 16 × 16 character?

    32 bytes 32 bytes in every scan direction. 12 × 12 is 24 bytes, an 8 × 16 ASCII character is 16.

    What do 顺向 and 逆向 mean in PCtoLCD2002?

    逆向 = LSB first 顺向 = MSB first (first pixel in bit 7); 逆向 = LSB first (first pixel in bit 0).

    What is a font bitmap (font modulus)?

    Most graphic OLED and LCD controllers — the SSD1306, SH1106 and ST7565 among them — have no built-in font. To show a character, firmware copies a small grid of on/off pixels into display memory, one bit per pixel, eight pixels per byte. Turning a glyph into those bytes is called taking the font modulus — 取模 in Chinese embedded tutorials, the job the Windows program PCtoLCD2002 has done for years.

    The bytes alone are not enough. The same 16 × 16 character can be packed four ways (row by row, column by column, pages of columns, strips of rows) and each way can put the first pixel in bit 7 or bit 0. Eight byte streams describe the same glyph, and a display draws only one of them correctly. That is why a font copied from a tutorial so often shows up mirrored, split in half or rotated.

    // 中 (16x16), pages of columns, LSB first, lit = 1 — SSD1306 page order
    const unsigned char zhong[32] = {
      0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0xFF,0x10,0x10,0x10,0x10,0xF0,0x00,0x00,0x00,
      0x00,0x00,0x0F,0x04,0x04,0x04,0x04,0xFF,0x04,0x04,0x04,0x04,0x0F,0x00,0x00,0x00
    };
    // First 16 bytes = page 0 (rows 0-7), one byte per column, bit 0 = top row

    What this generator does

    Pixel-perfect 12 × 12 and 16 × 16 Chinese

    Browser fonts are anti-aliased outlines; thresholded at 12 px in a Song typeface, the horizontal strokes of 中 simply vanish. This tool ships real bitmap fonts instead: the 16 × 16 font covers all 6,763 GB2312 characters, while the 12 × 12 font lacks 143 rare ones, which fall back to a system font and are flagged.

    All four PCtoLCD2002 scan modes, both bit orders

    Row by row, column by column, pages of columns and columns of rows, each MSB-first or LSB-first, lit pixel = 1 or 0. The option names follow PCtoLCD2002 and their meaning is checked against the SSD1306 datasheet and common OLED example projects, so you can pick the same settings an existing project uses.

    Presets that say what they match

    Pick SSD1306, Adafruit GFX or XBM and the options are set for you. Change any option by hand and the tool tells you which displays and libraries read that byte order — or that none do.

    Decode an array you already have

    Paste a font array of unknown origin and all eight scan/bit-order readings are drawn side by side. The readable one tells you the settings; one click applies them.

    Image to bitmap with real dithering

    Logos, icons and photos are scaled to your display size and converted with a threshold, Floyd–Steinberg or Atkinson dithering. Transparent areas count as background, and the threshold works in every mode.

    Output that drops into your project

    C51-style 2-D arrays with /*"中",0*/ comments, Arduino PROGMEM with offsets, MicroPython bytearray dictionaries, A51 DB lines or plain hex. Generated C compiles cleanly with -Wall -Wextra -pedantic.

    Worked examples

    中 for an SSD1306 OLED (16 × 16, pages of columns, LSB first)

    中
    00 00 F0 10 10 10 10 FF 10 10 10 10 F0 00 00 00 00 00 0F 04 04 04 04 FF 04 04 04 04 0F 00 00 00

    32 bytes: 16 columns of page 0 (rows 0–7), then 16 columns of page 1 (rows 8–15). In 0xFF at column 7 every bit is set: the vertical stroke runs through all eight rows of both pages.

    The same 中 for Adafruit GFX drawBitmap (row by row, MSB first)

    中
    01 00 01 00 01 00 01 00 3F F8 21 08 21 08 21 08 21 08 21 08 3F F8 21 08 01 00 01 00 01 00 01 00

    Two bytes per row, 16 rows. 0x01 0x00 is the centre stroke alone: the leftmost pixel is bit 7 of the first byte, so the pixel in column 7 lands in bit 0. Feed these bytes to an SSD1306 page write and you get garbage — same glyph, different packing.

    Half-width A in a 16-pixel font (8 × 16)

    A
    00 00 00 00 18 24 24 42 42 7E 42 42 42 42 00 00

    ASCII characters are half width: 8 × 16 is 16 bytes, one byte per row when scanning row by row. Tick "Give half-width characters the full cell width" if your driver expects every glyph to be 16 × 16.

    Why 12 × 12 is 24 bytes, not 18

    中 (12 × 12, row by row, MSB first)
    00 00 04 00 04 00 FF E0 84 20 84 20 84 20 FF E0 04 00 04 00 04 00 04 00

    Each 12-pixel row needs two bytes; the last four bits of the second byte are padding. 12 rows × 2 bytes = 24. Every display library addresses rows as (width + 7) / 8 bytes, so packing the bits tighter would break all of them.

    How to generate font data for an LCD or OLED

    1. 1

      Enter text or drop an image

      Text uses a real pixel font: 12 × 12 (Fusion Pixel) or 16 × 16 (GNU Unifont), with ASCII at half width. For logos and icons switch to the Image tab and set the target size, such as 128 × 64.

    2. 2

      Choose the target display or library

      SSD1306 / SH1106 / ST7565 need vertical bytes in page order with the top pixel in bit 0. Adafruit GFX, TFT_eSPI, U8g2 drawBitmap and LVGL need horizontal bytes with the left pixel in bit 7. XBM / U8g2 drawXBM is the same but with the left pixel in bit 0.

    3. 3

      Check the preview, fix pixels if needed

      The preview shows exactly the bitmap that is encoded. Click any pixel to toggle it, or nudge the glyph with the shift fields.

    4. 4

      Copy or download

      Pick C (PCtoLCD2002 C51 style), Arduino PROGMEM, MicroPython bytearray, A51 assembly or plain hex. The first line of the output records the scan direction, bit order and glyph size, so the array documents itself.

    Garbled characters: symptom → wrong setting

    Each group of 8 pixels is mirrored left to right

    Horizontal bytes with the wrong bit order. Adafruit GFX drawBitmap() needs MSB first; XBM and U8g2 drawXBM() need LSB first.

    ✗ Wrong
    U8g2 drawXBM() fed MSB-first bytes
    ✓ Correct
    drawXBM() → XBM preset (row by row, LSB first)

    Every 8 rows are upside down

    Vertical bytes with the wrong bit order. SSD1306-type controllers put D0 in the top row, so they need LSB first.

    ✗ Wrong
    Pages of columns, MSB first → SSD1306
    ✓ Correct
    Pages of columns, LSB first → SSD1306

    Top and bottom halves interleaved

    Column by column and pages of columns mixed up. They give the same bytes only for glyphs up to 8 pixels tall; at 16 pixels the byte order differs.

    ✗ Wrong
    Column by column → SSD1306 page writes
    ✓ Correct
    Pages of columns → SSD1306 page writes

    Glyph flipped along its diagonal, or inverted

    A diagonal flip means horizontal bytes were read as vertical ones (or the reverse). Dark text on a lit background means lit = 0 was chosen where the display expects lit = 1.

    ✗ Wrong
    Row by row bytes sent to an SSD1306 page
    ✓ Correct
    Paste the array into the decoder tab and pick the readable preview

    When you need this

    Chinese text on a 0.96" SSD1306 OLED
    A temperature reading, a menu or a status line in Chinese on an STM32 or 51 board. Use the 16 × 16 font with the SSD1306 preset and paste the array into the oled_font.h your example project already has.
    Boot logo on an Arduino or ESP32 display
    Drop a PNG logo, set 128 × 64, choose the Adafruit GFX preset and call display.drawBitmap(0, 0, logo, 128, 64, WHITE).
    Icons and units for an LVGL or U8g2 UI
    Battery, Wi-Fi and °C symbols as 1-bit images. Use the Adafruit GFX / LVGL preset for horizontal MSB-first bytes, or the XBM preset for U8g2 drawXBM().
    Working on a Mac or Linux machine
    PCtoLCD2002 and the classic 字模提取 tools are Windows programs. This one runs in any modern browser and needs no install.
    Reverse-engineering an inherited project
    The previous developer left a Hzk[][32] array and no notes. Paste it into the decoder to find out how it was generated before adding new characters in the same format.

    How the four scan directions work

    Row by row (horizontal bytes)
    Rows are taken top to bottom; within a row, each byte holds eight neighbouring pixels from left to right. A 16-pixel row is two bytes. This is the layout of Adafruit GFX drawBitmap(), U8g2 drawBitmap(), XBM files, ST7920 GDRAM and LVGL 1-bit images.
    Pages of columns (vertical bytes) — the SSD1306 order
    The glyph is cut into pages eight rows tall. For each page, columns are taken left to right and each byte holds that column's eight pixels. This matches the GDDRAM of the SSD1306, SH1106 and ST7565, where the datasheet states that data bit D0 is written into the top row — so the matching bit order is LSB first.
    Column by column, and columns of rows
    Column by column takes each column top to bottom across all pages before moving right; it matches the SSD1306 vertical addressing mode. Columns of rows takes an 8-pixel-wide strip, all of its rows, then the next strip; no mainstream library uses it, but older PCtoLCD2002 projects do. Each looks identical to its sibling only when the glyph is at most 8 pixels tall (or wide), which is why the mix-up goes unnoticed on 8 × 8 tests.
    Bit order and lit-pixel value
    MSB first puts the first pixel taken (leftmost, or topmost) in bit 7; LSB first puts it in bit 0. PCtoLCD2002 labels these 顺向 (MSB first) and 逆向 (LSB first). Lit = 1 (阴码) is what OLEDs, TFT drawBitmap() and LVGL A1 expect; lit = 0 (阳码) only for drivers that define 0 as on.
    Padding when the size is not a multiple of 8
    Every row (horizontal bytes) or every page (vertical bytes) is padded to whole bytes on its own, and padding bits are 0. With pages of columns and a 12-pixel glyph, the second page carries 4 padding rows; a driver that writes whole pages clears those 4 rows under the glyph. The tool warns when that applies.

    Getting it right the first time

    Choose the preset by the drawing function, not by the screen
    An SSD1306 driven by Adafruit_SSD1306 goes through drawBitmap(), so it needs horizontal MSB-first bytes even though the controller's own memory is vertical. Pick the preset that matches the function your code calls.
    Use the pixel fonts for 12 and 16 px
    Outline fonts below about 20 px lose strokes when thresholded. Fusion Pixel 12 and Unifont 16 are drawn on the pixel grid, and the 2× options give clean 24 × 24 and 32 × 32.
    Keep the header comment
    The first output line records scan direction, bit order and size. Six months later it is the only record of how the array was made.
    Test with an asymmetric character
    Symmetric glyphs such as 中, 田 or H hide mirror errors. Try F, 7 or 乙 first: if it reads correctly, every option is right.
    Fix whole-screen mirroring in the driver
    If everything on the screen is mirrored or upside down, not just your font, change the segment remap (A0h/A1h) and COM scan direction (C0h/C8h) in the init sequence. Changing font settings would only break other code.

    Frequently asked questions

    Which settings should I use for an SSD1306 or SH1106 OLED?
    If your code writes the font bytes straight to the display (most 51 and STM32 examples): pages of columns, LSB first, lit = 1 — the SSD1306 preset. If it draws through Adafruit_SSD1306 or U8g2 drawBitmap(), use the Adafruit GFX preset instead, because those functions read horizontal MSB-first bytes and convert them themselves.
    My characters show up mirrored, upside down or scrambled. What is wrong?
    Each symptom points at one setting. Every 8 pixels mirrored left to right: wrong bit order with horizontal bytes. Every 8 rows upside down: wrong bit order with vertical bytes. Top and bottom halves interleaved: column by column and pages of columns mixed up. Flipped along the diagonal: horizontal and vertical bytes mixed up. Inverted colours: lit = 1 and lit = 0 swapped. If the whole screen is mirrored, not just your text, fix the driver's init sequence instead.
    Why does 12 × 12 take 24 bytes and not 18?
    Each row (or each 8-row page) is padded to whole bytes on its own: 12 pixels need 2 bytes, and 12 rows × 2 = 24. Display libraries address rows as (width + 7) / 8 bytes, so a tightly packed 18-byte glyph would be read wrongly by all of them.
    Why is my 12-pixel text wiping out the line below it on the OLED?
    With pages of columns, a 12-pixel glyph fills one full page and half of the next; the other 4 bits of that second page are padding zeros. A driver that writes whole pages clears those 4 rows. Draw through a frame buffer that sets individual pixels, or place 12-pixel text so the empty rows fall where nothing else is drawn.
    Can I use this instead of PCtoLCD2002 on a Mac?
    Yes. It runs in any modern browser, offers the same four scan modes, both bit orders and lit = 1 / lit = 0, and the C output follows PCtoLCD2002's C51 style with /*"字",0*/ comments, so it pastes into projects built around Hzk[][32]-style arrays. It does not generate full font library files or indexes. Glyph shapes come from GNU Unifont and Fusion Pixel rather than Windows' SimSun, so the bytes will not match an array PCtoLCD2002 made character for character — regenerate the whole table here instead of mixing the two. This site is not affiliated with PCtoLCD2002 or its author, is not an official web version and does not offer the program for download.
    Why use a pixel font instead of my system font?
    System fonts are outlines drawn with anti-aliasing, which a browser cannot switch off. At 12 or 16 pixels, thresholding that grey edge loses thin strokes — the horizontal bars of 中 disappear entirely in a 12-pixel Song typeface. GNU Unifont (16 px) and Fusion Pixel (12 px) are designed on the pixel grid, so nothing is lost. System fonts remain available for larger sizes: a sans-serif (Hei) typeface holds up from about 24 px, while Song typefaces can still lose strokes there.
    Which characters do the pixel fonts cover?
    All of GB2312 — 6,763 simplified Chinese characters plus full-width punctuation — and printable ASCII, accented Latin letters (é, ß, ł, ş, ő), Greek, Cyrillic, Japanese kana and common symbols such as ℃, arrows and box drawing. Fusion Pixel 12 px lacks some rare characters and symbols. Korean Hangul and Chinese characters outside GB2312 (traditional forms, many Japanese kanji) are drawn with a system font instead and listed under the preview, so you can see which glyphs to check.
    How do I convert a logo or icon to a C array?
    Open the Image tab, drop the file, set the display size (128 × 64 for a 0.96" OLED), and keep "Fit inside" to preserve proportions. Dark pixels become lit pixels; tick invert for light artwork on a dark background. Use no dithering for logos and text, Floyd–Steinberg or Atkinson for photos.
    How can I tell how an existing font array was generated?
    Paste it into the "Preview an existing array" tab. Comments are ignored, and C, Arduino, A51 and MicroPython syntax are all accepted. The glyph size is guessed from the byte count and all eight scan direction and bit order combinations are drawn. The one that reads correctly is your setting, and "Use these settings" copies it to the generator.
    Is my text or image uploaded?
    No. Glyphs are rendered and encoded in your browser. The pixel fonts are downloaded once from this site and nothing you type or drop is sent anywhere.

    Related Tools

    View all tools →