Encoding and Graphic Codes
Three motives of encoding, four densities of graphics
A Brief History of Encoding and Graphic Codes
Encoding is at bottom swapping one set of symbols for another to write down information, yet a brief history of encoding hides three quite different motives: save — express the same content with shorter symbols; stabilize — keep information alive through a noisy channel; verify — give data a fingerprint that exposes tampering. The three lines lead to compression, error-correcting codes, and hash functions, and the everyday confusion of “encoding, encryption, hashing” is itself evidence that this history never got sorted out. The first half of this article runs from the 1838 telegraph line to the modern cryptographic hash; the second half turns the camera on encodings that live inside images.
A Brief History of Encoding: Save, Stabilize, Verify
Variable-length encoding
Frequent symbols get short codes
Morse code is humanity’s first widely deployed character encoding, and its best decision came early in the design.
Morse’s assistant Alfred Vail counted letter frequencies in the type cases of English newspapers and gave the shortest symbols to the most frequent letters. E is a single dot, T a single dash, while Q takes four strokes.
In English, E appears about 12% of the time and T about 9%. Letting each save two or three strokes cut the cost of a whole telegram by a wide margin.
That was the first landing of “allocate code length by frequency”, more than a century before Shannon’s information theory. The entire intuition of compression began as a telegraph bill.
Variable-length encoding immediately raises a new problem: how do you know where a character ends?
Fixed-length codes can be cut at fixed intervals; variable lengths demand an extra convention.
Morse’s answer was to use time as the separator. A dot lasts 1 time unit, a dash 3; the gap between dots and dashes within a character is 1 unit, between characters 3, between words 7.
Keying Morse is essentially encoding a one-dimensional character stream into an alternating sequence of “signal and silence”. The separators are themselves part of the encoding.
In the Morse Code Converter, convert a sentence to code and watch the spacing convention between characters; the rule that “variable length necessarily pays for separation” becomes visible.
Operators also developed a second layer of reuse on top: building abbreviations out of frequent combinations.
SOS — three short, three long, three short — is not an abbreviation of any word; it was chosen purely because the dot-dash alternation is unmistakable and misreads least. And when sent as a single signal, the pauses between characters are deliberately dropped.
That is the classic design of “reserving the most recognizable pattern for the most critical meaning”.
Similar cases are the Q-code abbreviations and the habitual shorthand between operators. When code length itself has a cost, “re-encode frequent meanings as whole units” is the inevitable direction of evolution. A century and a half later the same idea reappeared, in another guise, in HTTP header compression.
Huffman
Separators cost bandwidth. Is there a variable-length code that needs none?
Yes. In 1952 David Huffman, in a course assignment at MIT, gave the prefix code: no character’s code may be a prefix of another’s.
Then, walking down the code stream from the start, each complete codeword can be cut off the moment it is read. Decoding is unambiguous and separators disappear entirely.
The prefix property translates into an elegant geometric condition (the Kraft inequality): for code lengths , a prefix code exists if and only if
The intuition is to treat a binary tree as the coding space: each codeword occupies one leaf, prefix-disjointness is equivalent to “no codeword sits where an ancestor would”, and all leaves partition a probability interval of total length 1.
Huffman’s construction algorithm is just as clean. Count each symbol’s frequency and put them in a priority queue. Repeatedly take the two least frequent, merge them into a new node (each gains one bit in its code), and put the node back with the merged frequency. Repeat until one root remains.
This greedy construction can be shown optimal under “code lengths must be whole bits”. No other prefix code has a shorter average length.
And the optimal average length has a lower bound: the entropy Shannon defined in 1948.
For English text the per-character entropy is around 4.1 bits, while ASCII spends 8. Huffman compresses English to roughly 4.5 bits per character, one step from the theoretical bound.
Compression closes its theoretical loop here: entropy is the price floor, Huffman is the integer solution that approaches it, and the remaining gap (fractional bits) is filled by later techniques such as arithmetic coding.
From Huffman to zstd
Huffman is still everywhere in today’s compressors, but it has moved to the second line. The bulk of compression was taken over by another family of algorithms.
gzip’s core is DEFLATE, a two-stage architecture. First an LZ77-style pass finds repeated fragments in the data and replaces “repeat” with a “back-reference” (distance plus length); this step kills structural redundancy. The remaining reference symbols and literal bytes then go through Huffman coding, which kills frequency redundancy.
Modern zstd and brotli keep the same layering; they just find references more accurately (larger windows) and entropy-code better (swapping integer-bit Huffman for ANS, which approaches fractional bits).
So Huffman’s position is clear: modeling belongs to LZ, coding belongs to entropy. Entropy coding is every compressor’s last mile, and that mile is still often run by a variant of a seventy-year-old course assignment.
Fixed-length encoding and byte streams
Teletypes and the triumph of the byte
The opposite route to Morse’s penny-pinching is fixed length: every symbol gets the same number of bits regardless of frequency.
Baudot’s code of 1874 gave each letter 5 bits, serving mechanical teletypes. ASCII in 1963 used 7 bits, and later got hitched to the byte (8 bits) as a de facto standard.
Fixed-length coding wastes bandwidth but buys two valuable things. Addressing is trivial (the nth character sits at position , which dovetails with modern memory paging) and separators are unnecessary.
The computer era settled on fixed length as the base, with compression layered on as needed. The character set handles uniform writing; compression is delegated to a separate layer like gzip.
Base64
By the era of email and the web a new problem appeared: many channels only carry text.
Early mail systems were designed for 7-bit ASCII, and high-bit bytes were simply clipped. In URLs, spaces and slashes carry syntax. JSON is natively a text format.
To push an image, a certificate, or a binary signature through those channels, you have to disguise it as plain text. That is Base64’s job.
The algorithm is as plain as it gets. Cut the input into groups of 3 bytes (24 bits), re-slice each into four 6-bit chunks, and map every 6-bit value through a table to a printable character (A–Z, a–z, 0–9, +, / — 64 in all). A tail that does not fill 3 bytes is zero-padded and marked with equals signs.
The price is a size inflation to 4/3 of the original.
One very common misunderstanding must be nailed down here: Base64 is not encryption.
It has no key; anyone holding the string can restore it instantly, and the mapping table is a public standard. It usually makes data longer rather than shorter.
Its identity is encoding. Changing how something is written so it can cross a particular channel has nothing to do with secrecy.
When you see the three “garbage” segments in a JWT (JSON Web Token), or a data URL for an inline image, know that both are Base64 encoding, not encryption.
To feel “encoding is reversible, anyone can decode it” firsthand, do Base64 and URL encode/decode in the Codec; encode and decode the same text back and forth and the sense that “this is not secrecy” builds itself.
Base64 also has a set of practical traps worth recording.
The first is variant fragmentation. The plus and slash of the standard alphabet have syntactic meaning in URLs, so a URL-safe variant swaps them for hyphen and underscore. JWT uses exactly that.
The second is padding ambiguity. = merely pads to a multiple of 4 and is optional on decode, so the same content has two legal spellings, “padded” and “unpadded”; integrations that fail to talk to each other often jam right here.
The third is line breaks. The MIME spec requires Base64 to wrap every 76 characters, so a blob copied out of an email carries newlines and decodes straight into an error.
Three traps, one lesson: given a stretch of Base64, first ask whose Base64 it is.
UTF-8
The Base64 story leaves a deeper question: should character encoding itself be fixed or variable length?
After the early computers’ incompatible encodings collided on the internet, a unified character set, Unicode, became the consensus; the remaining question was how many bytes each character takes.
Fixed-length UTF-32 is absurdly wasteful (an English letter takes 4 bytes). UTF-16 compromised at 2 bytes, stretching rare characters to 4 with surrogate pairs, but its ASCII compatibility is zero and it carries the byte-order baggage.
The winner was UTF-8, designed in 1992 by Ken Thompson and Rob Pike on a placemat. A 1-to-4-byte variable-length encoding where the lead byte’s high-bit pattern declares “this character is N bytes” and continuation bytes uniformly start with 10.
This structure brings three lavish gifts.
ASCII is a natural subset. Bytes below 128 pass through untouched, so existing text upgrades seamlessly.
Self-synchronization. Start reading at any position in the byte stream and the lead-byte pattern finds the next character boundary; one corrupted byte does not poison the whole run.
The old wisdom of prefix codes. The lead byte’s bit pattern is exactly a Huffman-style variable-length prefix design: frequent Latin characters take 1 byte, Chinese characters 3, frequency tied to length once again.
Today more than 98% of pages on the internet are UTF-8. The victory is usually credited to compatibility, but structural self-synchronization and the variable-length balance are the real reason it pushed UTF-16 out of the web.
Verification and correction
From parity to Hamming
Information gets bit-flipped by noise in the channel, storage media age — this is where the third motive, stability, comes from.
The plainest instrument is parity. Add one bit per byte so the count of ones is even, and any single-bit flip shows up immediately. The price: it detects but cannot correct, and two flipped bits slip through.
In 1950 Richard Hamming, at Bell Labs, pushed the capability to “automatic correction”. Three check bits are inserted among 7, placed at powers of two (positions 1, 2, 4), each responsible for a specific set of positions.
The receiver recomputes the check equations, adds up the indices of the failing ones in binary, and the result is the position of the bad bit. Flip it and the correction is done.
This (7,4) Hamming code corrects any single-bit error and went on to guard ECC memory for decades.
The core idea of error-correcting codes deserves to be stated outright: use redundancy to spread legal codewords apart in the coding space.
Define the “Hamming distance” of two codewords as the number of differing bits. If every pair is at least 3 apart, any single-bit error lands nearest its original codeword and still at least 2 away from every other; a nearest-neighbor decision restores it.
Distance 3 detects two and corrects one — that is how the exchange rate between redundancy and correction power is fixed.
In engineering, the more common tool is the cyclic redundancy check (CRC). Treat the data as a polynomial, divide by an agreed generator polynomial and keep the remainder as the check value; in hardware it is a row of XOR gates that catches burst errors at very low cost. Ethernet frame trailers and ZIP files both use it.
Checksums in daily form
This idea has long since seeped into daily life; most people just never recognize it.
The last digit of an ISBN on a book’s back cover is a check digit. Weight the first 12 alternately by 1 and 3, sum, and append the digit that makes the total divisible by 10. A scanner that misreads one digit beeps immediately.
The 18th digit of an ID number works the same way: the first 17 are weighted by fixed factors and the result taken modulo 11.
The Luhn algorithm on bank card numbers is cleverer. From the right, double every second digit and subtract 9 when the result exceeds 9; the total must divide by 10. It catches nearly every “two adjacent digits transposed” error, a very common input mistake, because doubling reacts differently to mistakes in different positions.
Everyday “invalid input” warnings after one wrong digit all rest on this same family: spend one or two bits of redundancy to stop the overwhelming majority of single-point errors at the door.
Hashing
Why hashing is not encoding
The first three stories share reversibility: encoding exists to change the writing, and must be able to change it back when needed.
Hash functions deliberately cut that road. They squeeze an input of any length into a fixed-length output (MD5 is 128 bits, SHA-256 is 256), and since the input space is far larger than the output space, the pigeonhole principle guarantees collisions. Infinitely many inputs share one digest.
Once the information has been squeezed out, “unhashing” does not exist mathematically.
So hashing’s correct identity is a fingerprint for data. A fingerprint identifies a person uniquely, but you cannot grow a person back from one.
What makes a good fingerprint? Cryptographic hashes have three escalating hard requirements.
Preimage resistance: given a digest, you cannot find a corresponding input.
Second-preimage resistance: given an input, you cannot find another input with the same digest.
Collision resistance: finding any pair of inputs with the same digest is infeasible.
What backs these properties is the avalanche effect. Flip one input bit and about half the output bits flip, so an attacker cannot walk backwards from the output into a local region of the input.
The history of this defense line is worth remembering. MD5 got a practical collision construction from Wang Xiaoyun’s team in 2004 and has been unusable for any security purpose since. SHA-1 was knocked down by a public collision in 2017. The current default is the SHA-2 family (SHA-256).
In the Hash Generator, type the same text and compare the MD5 and SHA-256 outputs, then change one letter and regenerate. The avalanche effect can be confirmed by eye.
Three everyday battlegrounds for hashing
The first battleground is integrity checking. When downloading a file, compare against the officially published SHA-256; a mismatched digest means the transfer corrupted or someone tampered. What is being used here is second-preimage resistance — an attacker cannot forge a bad file with the same digest.
The second battleground is password storage. The database stores only salt plus hash; at login the input is hashed and compared.
Two details matter. Salting makes identical passwords produce different digests, so a cracker cannot batch-hit them with precomputed tables. Modern practice goes further and deliberately uses slow hashes (bcrypt, scrypt, argon2, with thousands of built-in iterations and memory cost), raising the price of brute force by orders of magnitude.
Storing passwords with plain SHA-256 is a common accident, because a commodity GPU computes tens of billions per second.
The third battleground is deduplication and sharding. Instant uploads on cloud drives, Git’s content-addressed storage, blockchains’ block linking — all are applications of “the digest is the identity”.
Bloom filters
Collisions are the enemy in cryptography and raw material elsewhere.
The 1970 Bloom filter turns hashing’s “many-to-one” directly into space compression. Allocate a long bit array; on insert, k independent hash functions set k positions to 1. On query, check whether all k bits are 1. All ones means “possibly present”, any zero means “definitely absent”.
The error is one-directional: absent means definitely absent; present may be a false positive.
At a false-positive rate of a few thousandths, a set of hundreds of millions of elements compresses to a few bits each. So one memory lookup filters out the overwhelming majority of “definitely not there” probes. Cache-penetration defense, crawler deduplication, and the “which file might this key be in” prefilter in storage engines are all its territory.
There is exactly one design point: bit array length, element count, and hash count together determine the false-positive rate — the formula is a lookup away — but the semantic premise of “better a false positive than a miss” has to be settled first.
The Bloom filter is the textbook case of turning collisions from a defect into a feature. It also draws the line with cryptographic hashing again: the latter demands collisions be hard to find, the former does not care whom the collision lands on.
One table to tell them apart
| Reversible | Key | Information preserved | Typical use | Examples | |
|---|---|---|---|---|---|
| Encoding | Yes | None | Fully preserved | Crossing channels, standardized writing | Base64, URL encoding, Morse |
| Encryption | Yes | Yes | Fully preserved (readable only by the key holder) | Secrecy | AES, RSA |
| Hashing | No | None | Deliberately discarded | Verification, fingerprints, password storage | SHA-256 |
The rule of thumb compresses into one sentence: a keyless reversible transform is encoding, a keyed reversible transform is encryption, an irreversible digest mapping is hashing.
Every step of those 180 years reallocates cost under some constraint — no encoding is free; there is only which price you are willing to pay. The next chapter swaps the carrier from symbols to pixels: once the readers include not just programs but lasers, cameras, and human eyes, the same trade-offs pick up a physical constraint of their own.
Graphic Encoding: Four Densities, Four Trade-offs
Encoding information into an image covers a very wide spectrum: wide enough that a one-dimensional barcode spent decades of evolution learning to hold a dozen characters; dense enough that a QR code three centimeters on a side swallows an entire URL plus enough fault tolerance to survive a third of itself being damaged. This chapter lines four graphic encodings up along the density axis — Code128, QR, favicon, OG image. The first two are for machines to read and the last two for humans to look at, yet their engineering decisions are surprisingly isomorphic, all trading off scanning method, fault tolerance, and the context in which they travel.
How a QR code is composed
When a decoder faces a QR code, the first question is not what the content is but where the image is, how far it is rotated, and how much it is scaled.
The QR code’s answer is drawn in three corners. Top left, top right, and bottom left each carry a 7×7 finder pattern: a black outer frame, a white ring inside it, and a 3×3 black square at the center, in the strict black-to-white ratio 1:1:3:1:1.
The three patterns have two geometric properties. Any three uniquely determine an affine transform (translation, rotation, and scale all fall out of them), while any two do not — a 90-degree rotation swaps two corners and creates an ambiguity.
The three corners are not decoration; they are the entire reason a QR code dares let users photograph it at an angle, crooked, through a screen.
Between the finder patterns, one row and one column of alternating black and white form the timing pattern, marking out the module grid’s beat like a ruler. Large versions add alignment patterns near the fourth corner.
The blank quiet zone around the outermost ring is part of the encoding too: decoders use it to cut the code out of the background. The QR spec suggests four modules of quiet zone; the site’s QR Code Generator uses margin 2, which is less safe when printing edge to edge but usually fine on screen. It is a textbook conscious concession between what the spec recommends and what actually works.
That 1:1:3:1:1 ratio is not an aesthetic decision either. The first step of scanning is often not recognizing a shape but a scan line crossing light-dark boundaries. The decoder sweeps the finder pattern in any direction, records the widths of the black-white transitions, and 1:1:3:1:1 holds its ratio under rotation, scaling, and perspective distortion.
It is a similarity invariant: however far the camera is, whatever angle it sits at, the ratio still reads.
The design descends directly from the elongated guard bars at either end of a barcode: both are metronomes prepared for the scanner, saying start counting here.
One more role is routinely overlooked: the format information strips tucked directly below the top-right finder pattern and to the right of the bottom-left one. About thirty modules recording the error-correction level and mask number, protected separately by a BCH code and stored twice, XORed with the mask.
It has to be tougher than the data area. If the decoder cannot read the correction level, nothing downstream can begin.
Every layer of the design obeys one discipline: the closer information sits to the can-I-read-this decision, the thicker its protection.
In size, QR codes come in 40 versions. Version 1 is 21×21 modules; each step up adds 4 to the side, and version 40 reaches 177×177 — 31329 modules in total.
Capacity is priced in three tiers by character set. At the lowest correction level, version 40 holds 7089 digits, 4296 alphanumeric characters, or 2953 bytes. A typical URL length, a few dozen characters, is comfortably served by version 3 to 5.
Encoders pick the smallest version that fits, since fewer cells scan more easily.
Error correction
The most counterintuitive property of QR codes is that a covered corner, a stain, half a logo pressed on top, and it still scans.
Behind it sits the Reed–Solomon code, an old technique invented in the 1950s for deep-space communication.
The intuitive telling: treat the data as sample points on a polynomial curve, and the check symbols as a batch of extra points measured beyond it. The curve is uniquely determined by the data, so even if noise knocks some samples off, as long as enough remain, mathematics can refit the original curve.
Errors are not tolerated; they are computed away.
Strictly: adding 2t check symbols allows t symbol errors to be corrected. All the arithmetic happens in the 256-element finite field GF(2⁸), where one byte is exactly one symbol.
The mechanism is kin to the Hamming code. Hamming corrects single-bit errors; RS generalizes the same idea to correcting whole bytes, with the cost bought as needed.
Of encoding’s three motives — compression, fault tolerance, verification — the fault-tolerance line has its full genealogy laid out in the previous chapter’s verification and correction section. RS is the node on that line with the highest engineering payoff.
The detail that the repair unit is a byte matters more than it looks.
Noise never damages an image in tidy, bit-shaped bites. A scratch, a drop of ink, a logo overlay — what gets destroyed is always a contiguous patch.
With bit-level correction, 3 wrong bits in one byte are 3 independent errors, tripling the repair cost. RS takes the byte as its symbol, so a whole ruined run of contiguous bytes counts as a string of symbol errors and is compensated per symbol inside the same 2t budget.
Which is why it rules optical discs, deep-space links, and QR codes: its enemy is the burst error, not shotgun noise.
QR codes also scatter the data blocks into an interleaved layout, so the damage from a covered corner gets spread thin across every block and each block’s share stays within its repair budget. The code repairs, the interleaving cuts the disaster into pieces; the two meshed together produce the everyday marvel of a ruined corner that still scans.
QR codes come in four correction levels, plainly named: L recovers about 7% of the codewords, M about 15%, Q about 25%, H about 30%.
The higher the level, the less room for data at the same size. H spends almost a third of its capacity on insurance.
The site’s QR Code Generator exposes the choice as a dropdown (default M), alongside foreground and background colors and an output size of 128 to 1024 pixels. The practice of stamping a logo in the middle presupposes level H, reserving the covered modules’ share for RS to repair.
The text input is debounced at 300 ms and redraws live on every edit, which makes small questions — how many modules an extra slash in the URL will cost — feel instant to answer.
A word on the limits of custom colors. Scan cameras read by luminance difference, so foreground and background must keep enough contrast. Light code on a dark ground still scans on most cameras, but set both to the same luminance in a red-green scheme and no amount of correction buys back the contrast. Another instance of correction fixing data but not physics.
Correction also explains why mask patterns exist. If the data area’s black-and-white distribution happens to form large same-color patches or fake finder patterns, it confuses the decoder. So the spec predefines eight XOR masks; the encoder scores each one and applies whichever is penalized least.
Every QR code you see has already run an automatic contest for the best-looking noise.
All of this points at one engineering philosophy: encode for the worst physical world, not for an ideal bitstream.
The philosophy also explains a frequent accident at the print shop: why the QR code on a poster always gets asked to be printed bigger.
The physical floor of scanning is set by how many sensor pixels each module occupies. For the same code, the further away it is posted, the fewer pixels per module, and the more misreads correction has to rescue. Higher versions pack modules denser, so at the same physical size each module is smaller.
Hence the engineering rule of thumb: pick version and size from the scanning distance. A poster read from afar wants a low version at large size; only a receipt you lean in to scan can afford a high version.
Nor is a higher correction level always better here. H’s repair power is bought with payload room, forcing the version up and the modules denser, which makes long distances more likely to fail.
Fault tolerance, density, and size are three knobs you turn together — the closest thing to a lever principle in QR engineering.
Code128
The barcode starts half a century before QR. In 1974 a supermarket checkout in the United States rang up a UPC barcode for the first time; the item was a pack of chewing gum. From that day on, stripes meant price.
Today’s supermarket EAN-13 is UPC’s international extension: exactly 13 digits, each half encoded with its own odd-even structure. The first digit is not even drawn as stripes; it is carried implicitly by the parity pattern of the six left-hand characters. That is how far one-dimensional encoding stretches its bandwidth.
The site’s Barcode Generator implements Code128 subset B. The encoder is a hand-written 107-row pattern table: each character maps to a run of 11 modules of alternating bars and spaces (e.g. 212222 for six runs alternating at widths 2:1:2:2:2:2).
The whole symbol is: start character (fixed at 104 for subset B) + data characters + one check character + stop character (106).
Code128 actually has three subsets. A covers control characters, B covers upper and lower case plus symbols, and C packs two digits into one symbol. All-numeric tracking numbers double their capacity with subset C, which is why courier labels are uniformly long strips of digits.
The tool implements only subset B, enough for any printable ASCII text; characters outside 32–126 are quietly skipped.
The check character is computed with positional weights: start from 104 as the base, add each data character weighted by its position, and take the result mod 103. That remainder is used directly as a table index for the bar-space pattern.
The check character looks like any other, inconspicuous among the bars, but scanners verify it. One bit wrong and the whole symbol is void on the spot.
Interestingly, this implementation’s positional weights start somewhere other than the spec’s 1, 2, 3… (in the source the counter pre-increments from the start value 104, so the first data character’s weight is really 105), which pushes the post-mod-103 check value away from the standard. The image still renders, but a commercial scanner that verifies strictly may refuse it.
One line’s offset between a hand-written encoder and the spec is exactly the distance between renders fine and actually interoperates. Details like this only surface by checking the source against the spec bit by bit.
The capacity comparison is brutal. Code128 covers ASCII 32–126 at a fixed 11 modules per character, plus roughly three characters’ worth of overhead for start, check, and stop. A barcode 10 cm wide honestly holds twenty or thirty characters.
QR version 1 (21×21, a mere 441 modules) holds 41 digits at level L, and version 40’s 2953 bytes is a gap of four orders of magnitude.
But the one-dimensional code survives on more than nostalgia. A laser scanner reads it with a single scan line, at a cost and power draw low enough to embed in supermarket tills and factory lines. Along its transmission direction it is inherently stretch-resistant, so printing on a curved package does not distort it.
The barcode traded capacity for robustness and cheapness; the QR code traded scanning conditions for density. Both ledgers balance.
Favicon
The favicon’s history is longer than most people remember. When IE introduced the mechanism around 1999, the spec was one line: a 16×16 pixel ICO file, placed at the site root.
For the next twenty-odd years tenants kept moving into that 16-pixel canvas. Browser tabs use 16/32, desktop shortcuts 32/48, high-DPI screens start at 64, the iOS home screen icon starts fresh at 180×180, and Windows tiles and macOS trackpads each keep their own list of sizes.
One small icon grew into a multi-resolution family. The ICO format simply packs bitmaps of several sizes into one file, taken as needed.
In recent years modern browsers annexed SVG. One vector image adapts to every size automatically, so favicon generators can finally move from one render per size to one render for all.
The site’s Favicon Generator takes the SVG route. Type one or two letters, pick a background and a foreground color, and it produces a 256×256 rounded SVG. The preview renders six sizes side by side — 16, 32, 48, 64, 128, 256 — for inspection, and you download favicon.svg when satisfied.
It puts favicon design’s real question on the table: what is still recognizable at 16 pixels?
A refined letterform at 256 collapses into a smear of strokes at 16. What survives the 16-pixel trial is usually a fat single letter, a high-contrast two-color block, and a decisive outline. Which is why nearly every big-company logo has a favicon-only edition degraded into pixel art.
That row of thumbnails from 16 to 256 is, in essence, an automated stress test of the degradation chain.
The harshness of 16 pixels comes from physics. At a browser tab’s normal zoom, a 16×16 canvas is 256 pixels in total, and any stroke narrower than 2 pixels disappears the moment antialiasing joins in. For a letter to read at 16 pixels, the type size is effectively limited to 10–14 pixels and must be bold.
The tool draws on a 256×256 SVG canvas by default and lets the browser scale it, with vectors guaranteeing no blur at any size. But sharp is not the same as readable. Scaling solves resolution, not information bandwidth.
Light and dark themes add another layer. The same black-on-white icon turns into a jarring white blob in a dark tab bar. The modern answer is to embed a media query in the SVG and give each theme its own palette.
The generator hands foreground and background entirely to the user, which leaves the last step — the tab-bar reality check — to human eyes. Generating is only the start; the job is done once you have glanced at it in a dark-mode browser.
OG image
The first three sections were all cases of a machine or a person leaning in to look. The last encoding inverts that: it is the business card the page hands out.
When a page is shared to WeChat, X, Discord, or Slack, the title card that unfolds in the chat window reads the og:image, og:title, og:description and similar fields from the page’s head metadata.
The Open Graph protocol was set down by Facebook around 2010 and is now an unqualified cross-platform de facto standard. 1200×630 pixels is the default frame across platforms, about 1.91:1, exactly filling a timeline card’s window.
That frame’s physical meaning deserves a thought. 1200 pixels wide means that at roughly 2× density on a phone it fills exactly one row. The OG image is a spec designed for the first glance on a phone screen.
It and the favicon are two faces of the same coin. The favicon optimizes being recognized in a tab you already have open; the OG image optimizes being clicked in a link you have not opened yet. One has a bandwidth of 16 pixels, the other the whole card, but both are encoding for the channel called attention.
Static site generators generally typeset the post title into an OG template automatically, which amounts to a free poster for every article. This is often underrated: when a link gets forwarded, the card image is the only content that reaches the reader before the body does.
The site’s OG Image generator produces that frame. Two inputs for title and subtitle, two color pickers blended into a diagonal gradient background, one click for an SVG and a download of og.svg.
The best way to understand it is as a page-shaped QR code. A QR code makes people scan toward you; an OG image lets a link carry its own self-introduction the moment it is forwarded. Whether they scan is up to the user, but whether the card stands out decides whether the link ever gets opened.
Anyone who has run share cards will soon meet its two platform moods.
The first is caching. Platforms scrape OG data once and keep it for a long time, so if you change the image without changing the link path the card may keep showing the old version. Change the URL along with the image, or use the official debugger to force a re-scrape.
The second is fonts. A sans-serif written into an SVG depends on the renderer’s font environment; converting the SVG straight to a bitmap means a machine missing the font will set the title completely differently. A bulk pipeline either embeds the font in the SVG or renders with a headless browser to guarantee WYSIWYG.
The SVG this tool emits is positioned as a design source and a template. Copy, color, and layout are settled here; the loop only closes once it becomes the PNG platforms actually accept and goes into the page metadata.
The real value of SVG here is decoupling design parameters (copy, gradient colors) from output format. Fix the template once, change two strings per article, and an image comes out — the standard posture for mass-producing OG images on a static blog.
Density, fault tolerance, cost, context — four columns of constraints, four solutions, and none of them wins on all four.
The move underneath all four is the same one: model a piece of information against the capabilities of a particular reader. A laser can only sweep one line, a camera fears low contrast, a human eye at 16 pixels only sees outline, attention in a timeline grants a card half a second. Then let the encoding bow to that capability.