tBMP banner

tBMP

12 devlogs
13h 37m 50s

A tiny bitmap format library for compact, efficient image storage and decoding.

tBMP gives you a binary image format with multiple encoding options, schema-driven structure, and a clean C library for reading, writing, and manipulating tiny bitm…

A tiny bitmap format library for compact, efficient image storage and decoding.

tBMP gives you a binary image format with multiple encoding options, schema-driven structure, and a clean C library for reading, writing, and manipulating tiny bitmaps.


I made this like last February (2026), completed the initial library in March (2026) but forgot to record it as a Flavortown project sobbbb, so this will mostly be updates and stuff to the existing project with One Big Devlog summarizing the stuff I did before.

This project uses AI

None at all, :3

Demo Repository

Loading README...

NellowTCS

Shipped this project!

Hours: 13.63
Cookies: 🍪 462
Multiplier: 28.27 cookies/hr

okay so.

i built a whole image format.

like. a whole one. with a spec and everything. and then i forgot it existed for like a month and a half.

tBMP (Tiny Bitmap Format) started as “haha what if I made a tiny bitmap format because BMP is so… bad” and then spiraled into “oh god I’m writing a whole C library and a schema and a test suite and a CLI tool and a docs site and-”

classic me.

anyway here’s what it is now:

a C library (six encoding modes!! RAW, RLE, Zero-Range, Span, Sparse Pixel, Block-Sparse!!)
a Rust crate (on crates.io!!!!!!!!!!)
a WASM module + npm package
a web demo where you can drag in .tbmp files and see the palette and masks and metadata and zoom and pan and copy palette colors to your clipboard
a CLI with a whole little TUI because I accidentally built a terminal UI framework
a docs site
a logo (encoded as a tBMP. obviously.)

the format is just [HEADER][DATA][EXTRA][META].
predictable.
tiny.
no “here’s a chunk, good luck” energy. the whole thing is zero-allocation on parse which makes it work on embedded targets too which is so cool omg. (esp32 port when? Give me some time off, i’m eepy 😭)

I’m so proud of this one. I built a format :D (i mean, for the 4th or 5th time but still)

NellowTCS

I think I’m done
I published it
I PUBLISHED ITTTTTTTTTTTTTT
WAIT AAAAAAAAAAAAAAAAAAAAAA

freeze frame
yup. that’s me.
hi.
so, you’re probably wondering how I got in this situation.
uh.
so it all started when i did a little bit of work on tBMP.

anyway here’s the chaos:

C Library Cleanup

I started with “just cleaning up a few headers.”
and now:

  • the API is cleaner
  • the headers are reorganized
  • the WASM entry is less cursed
  • the tests are categorized
  • the test runner prints results like a real tool
  • the docs reference everything correctly
  • the metadata parser is sane
  • the encoder doesn’t scream anymore

Rust Bindings

So I added Rust bindings, of course.
Which meant:

  • writing a custom header
  • writing a build script
  • compiling the C core from Rust
  • generating bindings
  • fixing the cursed constant names
  • adding an example
  • adding CI
  • adding a release workflow
  • publishing to crates.io!!!!!!!!!!!!!!!

WASM + npm (aka “why is Emscripten like this”)

I rewrote the entire WASM build pipeline because the old one was held together with duct tape and hope.

Now it:

  • builds cleanly
  • exports a full API
  • generates TypeScript definitions
  • copies artifacts to both npm/ and Demo/
  • has a proper npm package
  • with a README
  • and a LICENSE
  • and a scoped name
  • and a CDN import example
  • and CI
  • and a release workflow

Docs

I updated the docs.
And by “updated” I mean:

  • rewrote the API index
  • added Decoder API
  • added Metadata API
  • added WASM API
  • added installation tabs
  • added a favicon
  • added a gallery
  • added screenshots
  • added examples
  • added guides
  • fixed links
  • fixed more links

en finalement

(oui, j’ai étudié et j’étudie Français)

tBMP is now:

  • a C library
  • a Rust crate
  • a WASM module
  • an npm package
  • a docs site
  • a demo
  • a CLI tool
  • a test suite
  • a whole ecosystem?

I built a format ;D

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

I did a bunch of code cleanup sigh

Stuff Done

  • WASM build improvements
  • formatting scripts
  • docs screenshots
  • demo gallery
  • metadata panel polish
  • API cleanup
  • header rewrites
  • MessagePack docs
  • TODO annihilation

Demo Gallery :D

I added a whole demo gallery to the docs and then I built a grid layout for it :D
It’s great :D

Miscellaneous Cleanup

I improved various things across the library, headers, variables, some WASM API parity improvements, etc!

Attachment
Attachment
0
NellowTCS

I did some more stuff hehe

Summary

  • implemented custom metadata
  • added nested metadata rendering
  • updated the CLI
  • updated the WASM
  • updated the demo
  • AND THEN
  • designed a tBMP logo
  • exported it as PNG, PPM, and tBMP (because of course)
  • and plastered it across the docs, demo, and README

CUSTOM METADATA (aka “why was this so fast??”)

I added full support for custom metadata!

  • MessagePack -> JSON conversion
  • arrays, maps, nested objects
  • nulls, bools, ints, strings
  • CLI printing
  • WASM export
  • web demo rendering
  • nested tables with captions like custom[0]
  • a new example file (custom_meta.tbmp)

This was supposed to be a “later” feature.
It became a “done in one sitting” feature.

THE LOGO ARC (aka “I accidentally became a graphic designer, not that I wasn’t already (unofficially, :P)”)

Then I made a tBMP logo.

A tiny pixel‑art logo.
A PNG.
A PPM.
A tBMP of the tBMP logo because self‑hosting is the only correct way.

And then I put it everywhere:

Web Demo

The header now proudly shows:

[tBMP Logo]   tBMP/demo

It looks so official yayayayayay

Docs

The homepage now has a big centered logo.
There’s even a whole Logo section with download links for the PNG and tBMP.

README

The README now has:

  • a flexbox header
  • the title
  • the tiny logo

i wish github rendered it right though 😔

Oops

At this point tBMP has:

  • a logo
  • a demo
  • built‑in examples
  • metadata
  • custom metadata
  • masks
  • palettes
  • WASM
  • CLI tools
  • docs
  • a README
  • more stuff

AND WE’RE ALMOST DONE :D

Attachment
Attachment
Attachment
Attachment
0
NellowTCS

So this was quick

I modularized the entire tBMP web demo which, in practice, meant:

  • copy‑pasting chunks of my own code into new files
  • Find‑and‑Replace‑ing function names like a gremlin
  • hoping nothing exploded
  • and somehow… it worked??? (i mean i do do this every time so I’d be more surprised if it didn’t work)

The demo now has:

  • canvas.js
  • dom.js
  • examples.js
  • render.js
  • state.js
  • wasm-helpers.js

Built‑in Examples!!!!!!

The demo now comes with a whole dropdown of example .tbmp files you can load instantly.

No more digging through folders.
No more dragging random files around.
Just click -> boom -> pixels.

Palette Click = Clipboard Copy 😶‍🌫️

I added a tiny feature that seems kinda obvious now:

clicking a palette color now copies its RGBA to your clipboard.

I don’t know why this feels so powerful but it does :O

Attachment
Attachment
0
NellowTCS

Today was The Day.
The day tBMP finally rendered a tBMP as a tBMP instead of me round‑tripping it through PNG like a coward. (I mean I did see it rendered in a JS Canvas, so can you really call it viewing a tBMP??? 😔)

I saw my own format… in its natural habitat… for the first time… heheheeeeeeee
Anyway.

Web Demo Time

I finally got the full web demo working:

  • WASM build
  • Vite project
  • Drag‑and‑drop loader
  • Pixel viewer
  • Zoom + pan + grid
  • Palette inspector
  • Masks inspector
  • Structured metadata panel
  • EXTRA chunk viewer
  • All the little UI bits that make it feel like a Real Tool™

The funniest part?

The palette reset bug.
One tiny array that refused to clear between loads.
One hour of debugging.
One hour of me whispering “why are you STILL here” at my screen.

Also:

I gave up on Cheerp.

Sharing state between JS and C++ felt like trying to pass a USB stick through a brick wall.
Emscripten may be cursed, but at least it’s predictably cursed.

And then…

format.sh.

Ah yes.
I ran the new format script i made.
And it…
uhh…
my lines changed.
We don’t talk about it.
We simply accept that clang‑format is a force of nature and move on.

But the funniest thing?

This was the first time I’ve ever seen a tBMP rendered as a tBMP.
Not converted to PNG.
Not dumped to PPM (gosh, that awful thing I made for converting a tbmp to ppm shudders).
Not viewed through a side tool.
Just… decoded directly by my own library, in my own viewer, in my own format.

It was tiny (well, the image was, but still).
It was pixelated.
It was perfect.

tBMP is officially a real format with a real viewer and not just a C library I yell at when C does C things and I question my life choices.

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

When you realize you forgot to implement 2/3rds of your library 😔

So today I had one of those moments where you stare at your own codebase and go:

“Wait…
WAIT…
DID I ACTUALLY IMPLEMENT ANY OF THIS??”

Turns out:
no.
No I did not.

The Great Encoding Reckoning

I looked at the encoder and realized I had:

  • RAW
  • RLE
  • …and then a bunch of nothing pretending to be other encodings

So I spent a bit implementing:

  • Zero‑Range
  • Span
  • Sparse Pixel
  • Block Sparse
  • All the size estimators
  • All the write paths
  • All the decode round‑trips
  • All the tests
  • All the fuzzing
  • All the examples

Basically:
I implemented the entire rest of the format in one sitting.

It didn’t even take that long though so I’m not sure why I left it unfinished 😭

Fuzzing (aka throwing your library/tool/anything to the winds to make it stronger)

I added a fuzz tester that:

  • downloads a random Picsum image
  • generates random palettes
  • encodes it in every mode
  • decodes it
  • compares pixels
  • fails me if anything is even slightly wrong

Examples Folder Explosion

I added a whole gallery of:

  • raw
  • rle
  • span
  • sparse
  • block‑sparse
  • zero‑range

All using the same Picsum images so I can visually sanity‑check the encodings.

It’s basically a museum of “what if I stored this image in the stupidest way possible.”

Block Sparse: The Beast

Block Sparse encoding is:

  • 8×8 tiles
  • detect empty tiles
  • store only the non‑empty ones
  • pack them raw
  • index them
  • pray

It works :D

Tests testing things to test

I added:

  • round‑trip tests
  • edge case tests
  • fuzz tests
  • palette tests
  • encoding tests

tBMP is now tested.

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

tBMP has more wares now hehe

Auto‑Palette

tBMP can now generate palettes automatically for indexed formats.

Feed it a PNG -> get a palette.
No more hand‑crafting 16 colors like a medieval artisan.

Works with:

  • index1
  • index2
  • index4
  • index8

It’s actually good.
Like, suspiciously good, why is this so easy???

Floyd–Steinberg Dithering

Yes.
I added real dithering.

CLI flag:

--dither

It does the whole error‑diffusion thing.
Your pixel art looks crisp.
Your gradients look intentional.
Your tiny bitmaps look like they were processed by a real tool, which they were >:3

Best Encoding Picker (--pick-encoding)

I added a tiny heuristic that chooses between RAW and RLE based on estimated size.

It’s not fancy.
It’s not deep learning.
It’s literally:

“which one is smaller? ok do that.”

But it works shockingly well and makes the CLI feel smart.

Pixel Format Docs + Examples

I added a whole guide for pixel formats, with tables, examples, and actual PNGs.
Yes, I added PNGs to the repo.
Yes, they are adorable and like so coolll that tBMP -> PNG is so clean

Writer Improvements

The write path now supports:

  • auto‑palette
  • dithering
  • best‑encoding
  • indexed formats
  • more tests
  • cleaner API docs

It’s becoming a real ereader.
Wait. EREADER?!!
WDYM EREADER
*encoder, sigh

markdownlint

I linted the docs.
boringgg, I know, but I did it

CLI Updates

The CLI now supports:

  • --auto-palette
  • --dither
  • --pick-encoding

And the docs reflect all of it.

Docs Explosion (sadly not literally)

I updated:

  • CLI guide
  • encoding guide
  • pixel formats
  • design philosophy
  • versioning
  • metadata
  • examples

TODO Shrinkage

Checked off:

  • PNG exporter
  • badges
  • dithering
  • auto‑palette
  • best‑encoding
  • design philosophy
  • embedded systems guide
  • games guide
Attachment
Attachment
Attachment
0
NellowTCS

Okay so this one is small but also extremely funny because it started as
“hm something feels off”
and ended with
“oh right, PNM is cursed and my version check was also cursed.”

Version Validation Fix

I fixed the world’s most suspicious if statement:

if (head->version != TBMP_VERSION_1_0 &&
    head->version != TBMP_VERSION_1_0)

Yeah.
We don’t talk about it.

It now actually checks the version correctly.
We mostly do!

PNM Loader Improvements (again)

I went back into the PNM loader trenches because apparently I’m so good at reading docs wrong.

Changes:

  • fixed the magic‑number check so it actually matches P1–P4
  • corrected the ASCII/binary detection
  • fixed grayscale detection
  • fixed color detection
  • added proper P4 (1‑bit bitmap) decoding
  • fixed row padding
  • fixed bit indexing
  • fixed error messages
  • fixed the “I read the wrong byte” bug
  • fixed the “I read the wrong everything” bug

Basically: PNM is now actually supported instead of “supported in spirit.”

Better Error Messages for Image Loading

If stb_image fails, it now prints:

error: cannot load 'file': <reason>

instead of silently dying like a Victorian child.

General Cleanup

  • removed duplicate variables
  • removed unreachable branches
  • removed cursed logic
  • made the loader code less spaghetti
  • made the CLI image loader smarter about detecting PNM vs non‑PNM
Attachment
0
NellowTCS

tBMP got a validator, bulk metadata loading, more docs, and… “modularization” (aka moving files around until it feels right)

Validator CLI (tbmp validate)

tBMP now has a real validator command.

Two modes:

  • Basic: checks that the file parses correctly
  • Strict: goes full hall monitor:
    • rejects unknown/malformed EXTRA chunks
    • rejects invalid structured metadata
    • screams if anything looks cursed

It’s actually super nice for pipelines and tooling.

Bulk Metadata (--meta-file)

You can now load a whole structured metadata blob from disk:

tbmp encode input.png out.tbmp --meta-file meta.msgpack

And then override fields with flags afterwards.
It’s like layering metadata like a cake.

Custom Fields + Namespacing

Added proper docs + examples for custom metadata maps.

Recommended styles:

  • com.example.tool.build
  • mygame.level_id

And the CLI now validates custom maps so you can’t accidentally shove garbage in there.

CLI Modularization

I split the CLI into modules so it’s not one giant file anymore:

  • tbmp_imgio.c - image loading (PNM + stb_image)
  • tbmp_meta_cli.c - metadata flags, validation, custom maps
  • tbmp_ui.c - all the TUI stuff
  • tbmp.c - the command dispatcher

It looks way more professional now, even though I basically just copy‑pasted functions into new files and called it “architecture.”

Image Loading Improvements

Added a dedicated loader that:

  • handles P1–P6 PNM manually
  • falls back to stb_image for PNG/JPEG/etc
  • always outputs RGBA
  • frees memory correctly depending on backend

tBMP encode is now much more robust.

Docs Updates

Updated:

  • CLI guide
  • metadata guide
  • custom fields + namespacing
  • examples
  • README
  • validator docs

TODO Shrinkage

Checked off:

  • validator CLI
  • bulk metadata loading
  • custom fields docs
  • structured metadata examples
  • EXTRA chunk docs
  • CLI modularization

The list is getting smaller and smaller and it feels so good :D

Attachment
Attachment
Attachment
Attachment
0
NellowTCS

Okay so I was supposed to just fix a few more TODOs
and then I got distracted…
Oops.

TUI Time

I accidentally (/j) built a full little terminal UI framework for the tbmp tool.

It now has:

  • banners
  • sections + subsections
  • boxed tables
  • key/value displays
  • spinners (yes. spinners.)
  • status lines (OK / ! / ERROR)
  • auto‑detects terminal width
  • color accents
  • and a --ci mode that disables all styling for scripts

Structured Metadata (the big boi)

I replaced the old “arbitrary key/value map” metadata with a strict structured schema, and honestly, it feels so much better.

The new schema has required fields:

  • title
  • author
  • description
  • created
  • software
  • license
  • tags (array)

Optional fields:

  • dpi
  • colorspace
  • custom (array of raw MessagePack maps)

Everything is validated, length‑checked, type‑checked, and encoded cleanly.

The CLI now supports:

--title
--author
--description
--created
--software
--license
--tags tag1,tag2
--dpi N
--colorspace NAME
--custom-map FILE

If you provide any structured metadata, the whole schema must be complete, which keeps files consistent.

Metadata Rewrite

I rewrote the entire metadata parser to:

  • decode into a fixed TBmpMeta struct
  • enforce schema rules
  • reject unknown keys
  • reject duplicates
  • handle custom maps safely
  • avoid heap allocations
  • support truncation warnings

CLI Updates

The CLI got a huge upgrade:

  • unified tbmp tool
  • new TUI output
  • --ci mode
  • structured metadata flags
  • prettier inspect output
  • better error messages
  • spinner animations during encode/decode (because why not)

It feels like a tiny professional tool now. (and yes, I may have borrowed some logic from tuiro. Sue me, it’s MY own thing :P)

Docs

Updated:

  • CLI guide
  • metadata guide
  • examples
  • README
  • API docs

The docs now show the structured schema and how to use it.

TODO Shrinkage

Checked off:

  • structured metadata
  • schema validator
  • CLI metadata flags
  • TUI
  • inspect improvements
  • raw RGBA dump
  • malformed metadata tests
  • malformed EXTRA tests
  • sparse/block‑sparse edge cases
  • docs updates

The TODO list is looking way cleaner now BUT THERE’S STILL MOREEEE

Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

tBMP now has a proper CLI!

tbmp CLI

I merged everything into a single executable called tbmp, and it feels SO much nicer to use.

It now supports:

  • encode - convert PNG/JPEG/PPM/etc -> .tbmp
  • decode - .tbmp -> PPM
  • inspect - print header, EXTRA chunks, metadata, warnings
  • dump-rgba - raw RGBA bytes for debugging

It’s lightweight, script‑friendly, and honestly feels like a real tool now.

CLI Guide

I wrote a full guide for it in the docs!!

It covers:

  • build instructions
  • all commands
  • format/encoding options
  • examples
  • notes on behavior

Feels very official. I love it.

Metadata Validation Helpers

Added proper helpers for:

  • required keys
  • type checking
  • length bounds

These are exposed in the public API now, and the tests cover all the weird edge cases.

Round‑Trip Metadata Tests

Metadata now has:

  • encode -> decode -> compare tests
  • malformed MsgPack tests
  • malformed EXTRA tests
  • sparse/block‑sparse edge case tests

Basically: the metadata system is solid now.

Raw RGBA Dump Tool

tBMP -> raw RGBA is now a CLI command hehe.

Super useful for debugging or piping into other tools.

TODO Updates

Checked off:

  • metadata validation helpers
  • metadata round‑trip tests
  • malformed MsgPack tests
  • malformed EXTRA tests
  • sparse/block‑sparse edge case tests
  • unified CLI
  • raw RGBA dump tool

The TODO list is still long, but it’s getting smoller.

Next up: probably the WASM demo… or maybe the PNG exporter… or maybe I’ll get distracted and add something completely different. Who knows.

Attachment
0
NellowTCS

So I did the thing again.
I forgot this project existed.

Like fully.
Brain: empty.
Me: “oh yeah, I built a whole image format once.”

So anyway, here’s the “everything I did up to now” devlog because apparently I built an entire bitmap ecosystem and then moved on and forgot about it.

So. tBMP.

Right.

This started as “haha what if I made a tiny bitmap format because BMP is so… bad.” and then spiraled into “oh god I’m writing a whole C library and a schema and a test suite and a CLI tool and a docs site and-”

Classic me 😭

The Format

I wanted something simple.
Like:

  • predictable
  • tiny
  • no: update one thing? fine let me calculate everything else
  • and also because i wanted to >:3

So tBMP is literally:

HEADER
DATA
EXTRA
META

Does that format seem familiar? Anyone who has seen my “ideal music format” should know it as I might have borrowed it hehe.

It’s just the best way to make a binary format, smh my head

Encodings

I could’ve stopped at RAW.

But no.
Now we have:

  • RAW
  • RLE
  • Zero‑Range
  • Span
  • Sparse Pixel
  • Block‑Sparse

it’s so great omg the docs heheeeeeeeeeeeee: https://nellowtcs.me/tBMP/docs/guide/encoding/

EXTRA

This is where palettes and masks live.
And also anything else I decide to shove in there.

It’s literally:

  • 4‑byte tag
  • 4‑byte length
  • body

Simple.
Predictable.
Unlike PNG which is like “here’s a chunk, good luck.”

Metadata

Currently metadata is just “throw some key-value pairs, not my problem’

It’s now going to be structured because apparently I care about standards (lie /j):

  • title
  • author
  • description
  • created
  • software
  • dpi
  • colorspace
  • tags
  • license
  • comment
  • more idk what

Plus custom fields because I’m not a monster.

The Library

The codebase is split into modules like a real grown‑up library:

  • reader
  • decoder
  • writer
  • metadata
  • msgpack
  • pixel helpers
  • rotation transforms

Everything is tiny, just like the format (gosh, I’ve been wanting to make that joke for ages now)

The Test Suite (not tiny smh my head again)

There are tests for:

  • decoding
  • encoding
  • metadata
  • edge cases
  • sparse pixels
  • block‑sparse
  • rotation
  • reader
  • writer

It’s… a lot.

(so much handwritten things like i often had to write binary by hand which was so much pain i hate it but i will do it again cause i’m silly and it’s kinda the fastest way to do it)

Docs

DocMD is great
Mic drop.

Stuff I did these two commits

  • prettified like a bunch tables in the docs
  • added metadata validation helpers
  • added metadata round‑trip tests
  • added malformed MsgPack tests
  • added malformed EXTRA tests
  • added sparse/block‑sparse edge case tests
  • checked off TODOs like a gremlin on a speedrun
  • updated TODO.md

What’s Left (haha everything)

There’s a TODO.md, go read it

Attachment
Attachment
Attachment
Attachment
0