Details & Tips
How the image mode's pixel sampling actually works: the source image is drawn onto an off-screen canvas that's been resized down to exactly the chosen column count in width, and to a row count computed as `round((imageHeight / imageWidth) × columns × 0.5)` in height — that final `× 0.5` is the aspect-ratio correction described above, sampling at half the vertical resolution the column count alone would otherwise imply. Letting the canvas itself perform this downscale (rather than sampling individual pixels from the full-resolution image one at a time) means each output cell's colour is already a smoothed average of the source region it covers, rather than a single sharp pixel that might not represent its neighbourhood well.
Once downscaled, `getImageData()` reads out the exact red, green and blue value of every remaining pixel — one pixel now corresponds to exactly one output character. Each pixel's luminance is computed with the formula above, then mapped to a character using `index = floor((luminance / 255) × (rampLength - 1))` against the fixed 10-character ramp `"@%#*+=-:. "` (from `@`, the densest/darkest character, down to a plain space, the lightest). Concretely: a pure white pixel (255, 255, 255) has luminance 255, giving `floor((255/255) × 9) = floor(9) = 9` — the very last character in the ramp, a space, correctly reading as fully bright/empty. A pure black pixel (0, 0, 0) has luminance 0, giving index 0 — `@`, the densest, darkest character. Every value in between lands somewhere along that same 10-step gradient. Output renders inside a `<pre>` block with a fixed monospace font, `line-height: 1` and no extra letter-spacing, so every row and column lines up into a clean, undistorted grid rather than drifting out of alignment as the text wraps or the browser's default line spacing intervenes.
For the text-banner mode, every supported character — the 26 letters, 10 digits, a plain space, and a small set of basic punctuation (period, comma, exclamation mark, question mark, hyphen, colon and apostrophe) — has its own hand-built 5×7 grid, stored as seven strings of five characters each, where a `#` marks a lit cell and a `.` marks an empty one. Typing a character outside that supported set — an accented letter, most other punctuation, an emoji — doesn't error or crash; it simply renders as a fully blank 5-column-wide gap in the banner, keeping the surrounding letters correctly aligned rather than breaking the whole output. Each character in your typed message is looked up, its 7 rows are appended left-to-right onto seven running output lines (one blank column added between each letter for legibility), and the finished seven-line block is joined and displayed exactly as built — since it's inserted using `x-text`/`textContent` rather than `innerHTML` at every step, nothing you type can ever be interpreted as HTML or a script, only ever as literal displayed text.
Worked example: the letter 'A' is stored as the seven rows `..#..`, `.#.#.`, `#...#`, `#...#`, `#####`, `#...#`, `#...#` — a peaked apex narrowing to a point at the top, a horizontal crossbar through the middle, and two straight legs down to the base, recognisable as a block capital A even at this coarse 5×7 resolution. Both modes run entirely as JavaScript in your browser; no image or typed text is ever uploaded anywhere, and the copy and download buttons work directly from the text already generated on your screen.