A minimal, zero-dependency command-line tool that decodes an image and writes
it directly to a Linux framebuffer device — no X11, no Wayland, no
compositor, no libdrm.
It works against any /dev/fbN node: classic legacy fbdev drivers, and the
generic fbdev-emulation layer exposed by modern DRM/KMS drivers (common on
newer SoCs, e.g. Rockchip RK3576). Some of those DRM fbdev-emulation shims
don't implement mmap, so fbkmshow only ever uses plain write() — the
same approach dd uses to push raw pixels to /dev/fb0 — which makes it
work everywhere a framebuffer device exists at all.
- Decodes JPEG, PNG, BMP, GIF, and other formats supported by stb_image (public domain / MIT)
- Plays animated GIFs frame-by-frame, honoring each frame's own delay from
the file, with an optional loop count (
--loops) - Queries the framebuffer's real geometry live via
FBIOGET_VSCREENINFO— no hardcoded resolution - Scales and centers the image to fit, preserving aspect ratio
- Optional rotation:
0,90,180, or270degrees, for panels mounted sideways or upside down - Converts to the framebuffer's native 32bpp pixel layout automatically
- Writes via
write()only — works even when the driver doesn't supportmmap - Single C file, single vendored header, no shared-library dependencies — a static build runs on a bare embedded Linux image with nothing else installed
- A Linux system exposing a 32bpp framebuffer device (e.g.
/dev/fb0) - A C compiler to build from source (see below); prebuilt static binaries
are also published on the Releases
page for
aarch64,armv7(armhf), andx86_64— just download and run
Each Release also
includes a .deb package per architecture:
# arm64, armhf, or amd64 — pick the one matching your system
sudo apt install ./fbkmshow_<version>_arm64.debThis installs fbkmshow to /usr/bin/fbkmshow. Uninstall with
sudo apt remove fbkmshow.
make
./fbkmshow --helpInstall the matching cross-toolchain package, then run the matching target:
# Debian/Ubuntu
sudo apt-get install gcc-aarch64-linux-gnu libc6-dev-arm64-cross \
gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc
make aarch64 # -> fbkmshow-aarch64 (static)
make armv7 # -> fbkmshow-armv7 (static)
make x86_64 # -> fbkmshow-x86_64 (static)
make all-arch # builds all threemake dockerThis builds a throwaway image from the public ubuntu:24.04 Docker Hub
base (no private registry, no login) and cross-compiles all three
architectures inside it.
fbkmshow [-h|--help] [--version] [--rotate=0|90|180|270] [--fb=/dev/fb0] [--loops=N] <image-file>
| Flag | Description | Default |
|---|---|---|
--rotate=N |
Rotate the image 0, 90, 180, or 270 degrees |
0 |
--fb=PATH |
Path to the framebuffer device | /dev/fb0 |
--loops=N |
Animated GIFs only: play the animation N times (0 = forever) |
1 |
-h, --help |
Print usage and exit | — |
--version |
Print the version number and exit | — |
# Show an image on the default framebuffer
./fbkmshow photo.jpg
# Show it rotated 180° (e.g. panel mounted upside down)
./fbkmshow --rotate=180 photo.jpg
# Target a specific framebuffer device
./fbkmshow --fb=/dev/fb1 splash.png
# Play an animated GIF twice, then exit on its last frame
./fbkmshow --loops=2 spinner.gif
# Play an animated GIF forever (e.g. a boot/loading screen)
./fbkmshow --loops=0 loading.gif
# Stop a --loops=0 animation cleanly from another process/script once
# whatever it was waiting on (network, another step) is ready:
# fbkmshow --loops=0 loading.gif & PID=$!
# ...do other work...
# kill "$PID"; wait "$PID"- Opens the framebuffer device and queries its resolution, bit depth, and
stride via
ioctl(FBIOGET_VSCREENINFO / FBIOGET_FSCREENINFO). - Decodes the input image with
stb_imageinto RGBA. Animated GIFs are decoded throughstb_image's dedicated multi-frame GIF path (stbi_load_gif_from_memory), which also returns each frame's delay; every other format (and single-frame GIFs) goes through the plain single-image decoder. - For each frame: scales it to fit the framebuffer (or the rotated logical canvas, for 90°/270°), centering it on a black background.
- Converts each pixel to the framebuffer's native format while placing it at its rotated destination coordinates.
- Writes the resulting buffer to the device with a single
write()call. For animated GIFs, sleeps for that frame's delay, then repeats for the next frame — looping--loopstimes (or forever if0).SIGTERMis checked between frames (never mid-render), so a--loops=0animation stopped externally always exits on a complete, cleanly-written frame.
Only 32bpp framebuffers are currently supported.
src/ this project's source (fbkmshow.c)
third_party/ vendored dependencies (stb_image.h)
Contributions are welcome — see CONTRIBUTING.md.
MIT — see LICENSE. Bundles stb_image.h by Sean Barrett, dual licensed public domain / MIT.