piculum
v0.1.3
Published
The piculum codec: designed data regions inside ordinary PNG images - define typed fields in a picture, fill them into a sealed record, decode them anywhere.
Downloads
530
Maintainers
Readme
piculum
The piculum codec. A piculum is a data dictionary you can draw: a designer defines the fields of a record - a date, a name, a phone number, a choice from a list, free text, a file - by painting them onto a picture, each with a caption and a measured capacity. Someone else opens that design as a form, fills it in, and gets back a PNG that is the record. The picture is the delivery mechanism: one file that is the schema, the entry form, the transport, and the storage, traveling anywhere a picture travels and decoding (with the right password) into the structured fields the designer defined.
Content is sealed with authenticated encryption (AES-256-GCM under a PBKDF2-SHA256 key). A wrong password or a tampered image doesn't produce plausible garbage - the seal refuses to open.
See piculum.com.
Install
npm install piculumThe main entry is dependency-free.
Entries
piculum- the codec: encode/decode, mask definitions (v1 + v2), carriers, framing, crypto primitives, capacity math. No dependencies; everything runs locally on the caller's machine.piculum/render- visible-layer rasterizers (human-readable text, images, QR/barcodes, rich text). All render peers are optional at install time (so the codec entry stays a one-package install), but importing this entry needsqrcodeanddompurifyinstalled;@zumer/snapdomis only needed if you rasterize rich text (it is lazy-loaded at call time).piculum/node- PNG pixel I/O for Node, backed by the optionalpngjspeer. Browsers need nothing; in Node, install it once and the whole codec works:
npm install piculum pngjsimport { encodeMessage, decodePng } from 'piculum';
import { installNodePng } from 'piculum/node';
installNodePng(); // once, before any encode/decode
const { png } = await encodeMessage('hello from the server', 'password', 200, 200);
const text = await decodePng(png.blob, 'password'); // 'hello from the server'The mask definition file (MDF)
The MDF is the data dictionary itself: a small JSON document declaring the record's layers - which are data fields, which are visible decoration - that parseMaskDef / validateMaskDef check and normalize. Layer geometry (the painted pixels) lives separately in the v2 mask format; the MDF is the pure schema half.
{
"version": 1,
"name": "contactInfo", // optional; self-describing for distributed files
"description": "A contact card.", // optional
"font": "monospace", "size": 12, // defaults for human-readable layers
"definitions": [
{ "label": "Caption", "type": "human-readable" },
{ "label": "Pic", "type": "image", "fit": "aspect" },
{ "label": "QR", "type": "qr", "ecc": "M" },
{
"label": "phone",
"type": "piculum", // a data-carrying field
"field": { "type": "phone", "placeholder": "e.g. 555-0100" },
"defaultAlloc": { "amount": 64, "unit": "bytes" }
}
]
}Piculum (data) layers can carry a typed field schema - text, date, number, phone, email, zip, or choice, with optional caption, required, options, placeholder, and defaultValue - which drives the entry widget when the design is filled as a form. defaultAlloc is a design-time sizing hint (unit: usable bytes or exact px) for tools that auto-place a layer's pixels; painted pixels remain the real capacity, and the codec never consults it.
The normalizer strips unknown properties, with one deliberate escape hatch: an ext object (any JSON, on the file or on a definition) is round-tripped untouched, so applications can carry experimental data through validation without a schema change.
Rendering visible layers
The codec alone encodes and decodes every data layer - nothing below is needed for that. The piculum/render entry is for drawing the layers a person sees on the picture (none of it is recovered on decode). Importing the entry loads qrcode and dompurify, so install both alongside it:
npm install piculum qrcode dompurifyThen, for example, QR/barcode layers:
import { rasterizeQr } from 'piculum/render';
rasterizeQr(pixels, width, height, region, 'https://example.com', { ecc: 'M' });Per rasterizer:
| Rasterizer | Draws | Extra install |
| --- | --- | --- |
| rasterizeText | auto-fit human-readable text | none |
| rasterizeImage | a picture fitted into the region | none |
| rasterizeQr | QR / Code 128 codes | qrcode |
| rasterizeRichText | formatted HTML text | dompurify, plus @zumer/snapdom at call time |
(rasterizeText and rasterizeImage have no libraries of their own, but they ship in the same entry, so qrcode and dompurify still need to be present unless your bundler tree-shakes the unused rasterizers away.)
Runtime support
The codec is pure Web APIs (WebCrypto, CompressionStream, Blob) plus one pluggable step: PNG bytes <-> RGBA pixels (PngIO).
- Browsers - works out of the box (canvas adapter, auto-detected).
loadPixelsaccepts any image format the browser decodes. - Node >= 20 - install the
pngjspeer and callinstallNodePng()frompiculum/node(PNG-only, byte-exact). Verified by the package'stest:noderound-trip suite. - Other runtimes (Deno, Bun, Workers) - the primitives exist there; bring a
PngIOadapter viasetPngIO()(the pngjs adapter may work as-is where Node compatibility is offered - unverified).
The render entry is DOM-only.
License
MIT - Copyright (c) 2026 Jeff Slarve
