7 Rendering and Bitmaps
dxtr edited this page 2026-10-18 16:08:49 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Rendering and bitmaps

One call: load, render, copy

char-bitmap face char &key (mode :normal) flags target → bitmap

Loads the glyph for char at the face's current size, renders it in mode, and returns a bitmap copy. A character the face lacks renders as its .notdef glyph.

glyph-index-bitmap face glyph-index &key (mode :normal) flags target → bitmap

The same for a glyph index, such as one produced by a shaper.

For both, flags is as for load-glyph. target defaults to the hinting that suits mode: :light, :mono, :lcd and :lcd-v use the target of the same name, and everything else uses :normal. Both functions reuse the face's glyph slot.

(setf (ft2:face-size face) 8)
(ft2:char-bitmap face #\A)
;; => #S(BITMAP :WIDTH 6 :HEIGHT 6 :PIXEL-MODE :GRAY :NUM-GRAYS 256 :LEFT 0 :TOP 6
;;              :DATA #2A((0 1 223 94 0 0) (0 67 148 183 0 0) (0 163 45 177 30 0)
;;                        (12 204 0 92 122 0) (97 255 255 255 217 0) (189 42 0 0 172 56)))

(ft2:char-bitmap face #\A :mode :mono)
;; => #S(BITMAP :WIDTH 5 :HEIGHT 6 :PIXEL-MODE :MONO :NUM-GRAYS 256 :LEFT 0 :TOP 6
;;              :DATA #2A((0 0 1 0 0) (0 0 1 0 0) (0 1 0 1 0)
;;                        (0 1 0 1 0) (0 1 1 1 0) (1 0 0 0 1)))

Step by step

render-glyph source &key (mode :normal) → source

Renders the glyph in the face's slot to a bitmap, in place, and returns the face. A glyph that is already a bitmap is left as it is. For a glyph object, render-glyph also accepts :origin.

glyph-bitmap source → bitmap

Copies the rendered glyph out of the face's slot, or out of a glyph object, into a fresh bitmap. The glyph must be in :bitmap format: rendered, loaded with :render, or loaded from a bitmap strike. Otherwise it signals :invalid-glyph-format:

(ft2:load-char face #\A)
(ft2:glyph-bitmap face)
;; error: Glyph 36 is in outline format and has not been rendered to a bitmap.
;;        Render it first with RENDER-GLYPH, or load it with the :RENDER flag.
;;        CHAR-BITMAP and GLYPH-INDEX-BITMAP load and render in one call.

Step by step:

(ft2:load-char face #\g :target :light)
(ft2:render-glyph face :mode :light)
(ft2:glyph-bitmap face)

;; or, in one load:
(ft2:load-char face #\g :flags '(:render))
(ft2:glyph-bitmap face)

Outlines of your own

To rasterize a vector shape of your own rather than a glyph, make an outline and call render-outline, which returns coverage as bitmap-data holds it for a :gray bitmap. See Outlines.

Render modes

Mode Result pixel mode Description
:normal :gray 8-bit antialiased coverage (256 levels)
:light :gray Same rasteriser as :normal. Pair it with :target :light hinting.
:mono :mono 1-bit, no antialiasing
:lcd :lcd Horizontal RGB subpixel coverage: three columns per pixel
:lcd-v :lcd-v Vertical subpixel coverage: three rows per pixel
:sdf :gray 8-bit signed distance field

These keywords make up the type render-mode; an unknown mode signals a type-error.

SDF

:sdf produces a signed distance field. 128 lies on the outline, values above 128 are inside, and values below are outside. FreeType pads the bitmap with a spread of 8 pixels by default, so the bitmap is larger than the glyph and left/top move outward to match:

(setf (ft2:face-size face) 16)
(let ((b (ft2:char-bitmap face #\A :mode :sdf)))
  (list (ft2:bitmap-width b) (ft2:bitmap-height b) (ft2:bitmap-left b) (ft2:bitmap-top b)))
;; => (27 28 -8 20)

For high-quality multichannel fields, use a dedicated MSDF generator on glyph-outline instead.

LCD

:lcd bitmaps have three subpixel columns per output pixel, so bitmap-width is three times the pixel width. :lcd-v triples the rows instead. FreeType's default LCD filtering applies. Neither cl-ft2 nor cl-ft2/ffi binds FT_Library_SetLcdFilter. To change the filter, define it with cl-ffi:defcfun and call it on library-handle (see FFI layer).

(setf (ft2:face-size face) 8)
(array-dimensions (ft2:bitmap-data (ft2:char-bitmap face #\A :mode :lcd)))
;; => (6 24)        ; 6 rows; 24 subpixels = 8 pixels

The bitmap struct

A bitmap is a read-only Lisp struct, fully independent of FreeType:

Accessor Meaning
bitmap-width columns in data: pixels, or subpixels for :lcd
bitmap-height rows in data: pixels, or subpixels for :lcd-v
bitmap-pixel-mode :mono, :gray, :gray2, :gray4, :lcd, :lcd-v, :bgra (or :none for an empty glyph): the type pixel-mode
bitmap-num-grays number of gray levels, for :gray (normally 256). FreeType reports 256 for :mono bitmaps too, where it means nothing
bitmap-left pen x to the bitmap's left column, in pixels
bitmap-top baseline to the bitmap's top row, in pixels, up is positive
bitmap-data the pixels, as below

bitmap-data layout

Indexed (row column), top row first, regardless of FreeType's pitch. Packed formats are unpacked to one element per pixel:

Pixel mode Array Element values
:mono (height width) of bit 1 = ink
:gray (height width) of (unsigned-byte 8) coverage 0..num-grays − 1
:gray2 (height width) of (unsigned-byte 8) 0..3
:gray4 (height width) of (unsigned-byte 8) 0..15
:lcd (height width) of (unsigned-byte 8) width = 3 × pixels, in R G B order
:lcd-v (height width) of (unsigned-byte 8) height = 3 × pixels
:bgra (height width 4) of (unsigned-byte 8) premultiplied blue, green, red, alpha

These arrays make up the type pixel-data.

An empty glyph, such as a space, gives width and height of 0 and an empty #2A().

Placing a bitmap

With the pen at (pen-x, baseline) on a Y-down canvas, the bitmap's top-left pixel goes at:

(values (+ pen-x (ft2:bitmap-left bitmap))
        (- baseline (ft2:bitmap-top bitmap)))

The Cookbook has a complete example.

Color glyphs

Load with :color to get color glyphs as :bgra bitmaps. This covers COLRv0 layered glyphs (rendered by FreeType), CBDT/CBLC and sbix bitmap emoji. Bitmap emoji fonts are usually not scalable, so set face-strike to one of their strikes first:

(ft2:with-face (emoji lib "/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf")
  (setf (ft2:face-strike emoji) 0)                                  ; its only strike: 109 px/em
  (let ((b (ft2:char-bitmap emoji #\😀 :flags '(:color))))
    (array-dimensions (ft2:bitmap-data b))))                 ; => (128 136 4), :BGRA

The pixels are premultiplied. Divide by alpha if you need straight color. COLRv1 gradients and SVG glyphs are not rendered by FreeType itself.

On Windows, the FreeType that cl-ft2 carries is built without PNG support, so CBDT/CBLC and sbix emoji, which store their glyphs as PNG images, have no bitmaps there. COLRv0 glyphs render as everywhere else. See Installation.