How it works
From "what is this?" to "it is in my project" in a few minutes.
1. What a PuttyPNG is
A PuttyPNG is an ordinary PNG image with your data pressed invisibly into its pixels. Because PNGs are lossless, you can copy, paste, download, and message the image around, and the data rides along untouched. Hand someone the picture; they read the data back. No server, no link, no account.
2. Make your first one
Type a message and press it into a PNG right here, then read it straight back to see the round-trip.
3. A whole page, or one zone
A drop target can be the entire page or a single bounded area. The box on the left is a bounded one: drop a PuttyPNG on it, paste one, or click to browse, and the result appears right here rather than anywhere else. The code on the right is what you write to get a zone of your own, in about twenty lines.
<!-- PuttyPNG importer: drop, paste, or browse a PuttyPNG -->
<div id="drop" tabindex="0" style="border:2px dashed #ccc;padding:24px;text-align:center;cursor:pointer">
Drop a PuttyPNG here, paste it, or click to browse
<input id="file" type="file" accept="image/png" hidden>
</div>
<script src="puttypng.js"></script>
<script>
// Your app decides what to do with the decoded data.
async function onData(result) {
if (result.type === "binary") PuttyPNG.download(result); // save the file
else console.log("Got:", result.text); // text or JSON
}
const drop = document.getElementById("drop");
const file = document.getElementById("file");
async function read(source) {
try { onData(await PuttyPNG.decode(source)); } // prompts if encrypted
catch (err) { alert(err.message); } // err.code is the E## code
}
drop.addEventListener("click", () => file.click());
file.addEventListener("change", () => file.files[0] && read(file.files[0]));
drop.addEventListener("dragover", e => e.preventDefault());
drop.addEventListener("drop", e => {
e.preventDefault();
if (e.dataTransfer.files[0]) read(e.dataTransfer.files[0]);
});
drop.addEventListener("paste", e => {
for (const item of e.clipboardData.items)
if (item.type.startsWith("image/")) read(item.getAsFile());
});
</script>
› Under the hood No server and no magic. Clever use of an ordinary lossless PNG.
A PNG stores its pixels losslessly - every color value survives a copy, a paste, even a trip through many chat apps. PuttyPNG hides your data in the lowest, least-visible bits of the red, green, and blue channels of the image's fully opaque pixels. Nudging those bits shifts each color by an amount your eye cannot see, but the bytes are perfectly recoverable.
The protocol
A tiny header (PPNG + version + a CRC32 checksum) marks the image and guarantees the data came out exactly as it went in. Descriptive details ride in a small JSON block; the version byte lets the format grow without ever misreading an old PuttyPNG.
Compression & encryption
Data is gzip-compressed automatically whenever that helps. Add a password and it is sealed with AES-256-GCM (a key stretched from your password with PBKDF2) - the filename and type are hidden too, so an encrypted PuttyPNG reveals nothing until it is opened.
› 4. Drop it into your own project Copy-paste steps. No build tools, no dependencies, works offline.
- The Engine: save puttypng.js and include it.
- The Exporter: a box and a button that make a PuttyPNG.
The Importer is the third, and it is further up this page, beside the live zone it builds.
Loading engine source...
<!-- PuttyPNG exporter: type something, get a PuttyPNG -->
<textarea id="text">Hello!</textarea>
<button id="make">Make PuttyPNG</button>
<a id="save" download="my.png">Save</a>
<img id="out" alt="your PuttyPNG">
<script src="puttypng.js"></script>
<script>
document.getElementById("make").addEventListener("click", async () => {
const text = document.getElementById("text").value;
const png = await PuttyPNG.encode(text); // add { password, depth, cover }, etc.
document.getElementById("out").src = png.dataUrl;
document.getElementById("save").href = png.dataUrl;
});
</script>
Going further
When you are ready for more: call the engine directly, add a styled password prompt, use your own cover art, or route your app's own PuttyPNGs with the developer tag. It is all on the Docs page.