Most image-to-ASCII tools begin with the same request: upload your image.
That is a poor default for screenshots, private photos, diagrams, and anything else you would rather keep on your own device. I built Semaphore so the complete conversion and export pipeline stays inside the browser tab.
This post explains the small TypeScript and Canvas pipeline behind it, the reason braille characters preserve more detail, and how the deployed site enforces its privacy claim.
The pipeline
Semaphore deliberately has no application framework and no runtime dependency. The conversion path is:
image -> canvas sampling -> luminance grid -> character mapping -> text or PNG
Enter fullscreen mode Exit fullscreen mode
The browser decodes the selected file, an offscreen canvas resamples it to the requested output dimensions, and getImageData() supplies the RGBA pixels. The engine adjusts brightness and contrast, optionally inverts the channels, and computes luminance for each sample:
const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
Enter fullscreen mode Exit fullscreen mode
For a conventional charset, the luminance value selects a character from a dark-to-light ramp. Semaphore includes six presets:
standarddetailedblocksminimalbinarybraille
The first five use ramps of different sizes. For example, the standard ramp is:
.:-=+*#%@
Enter fullscreen mode Exit fullscreen mode
Why braille retains more detail
Unicode braille characters are miniature 2 by 4 dot matrices. Instead of taking one luminance sample per output character, Semaphore samples two columns by four rows and packs the eight thresholded dots into one code point beginning at U+2800.
At the same text width, every braille cell therefore represents eight source samples. The output does not need extra terminal columns to preserve finer spatial structure.
Binary thresholding can make gradients look harsh, so braille mode optionally applies Floyd-Steinberg error diffusion before packing the dots. Each threshold error is distributed to neighboring samples using the familiar 7/16, 3/16, 5/16, and 1/16 weights. Dithering is intentionally limited to braille; the other presets already have multi-level character ramps.
Try the dedicated image-to-braille example, or open the main tool and switch charsets while the image is live.
Local files without a lingering object URL
The selected file never needs a server URL. fileToImage() creates a temporary object URL, lets the browser decode the image, and revokes the URL immediately on either success or failure:
export function fileToImage(file: File): Promise<HTMLImageElement> {
return new Promise(function (resolve, reject) {
const url = URL.createObjectURL(file);
const image = new Image();
image.onload = function () {
URL.revokeObjectURL(url);
resolve(image);
};
image.onerror = function () {
URL.revokeObjectURL(url);
reject(new Error("unsupported image file"));
};
image.src = url;
});
}
Enter fullscreen mode Exit fullscreen mode
The decoded image remains drawable on the canvas, but the temporary blob reference is released as soon as it is no longer needed.
Text, color, and PNG export
Plain output is just text. When original or grayscale color is enabled, consecutive characters with the same quantized color are grouped into <span> runs instead of creating one element per character.
PNG export uses another canvas. It measures the monospace advance width, draws each text row, and returns a blob through canvas.toBlob(). The UI also supports copying plain text, downloading .txt, and producing a share card.
Privacy enforced by the deployed page
Keeping conversion code in the browser is only part of the guarantee. The production response also sends a Content Security Policy containing:
connect-src 'none'
Enter fullscreen mode Exit fullscreen mode
That prevents page scripts from opening fetch, XHR, or WebSocket connections. Semaphore has no backend API, no client-side analytics script, no cookies, and no third-party runtime request. JetBrains Mono is served from the same site as a self-hosted subset.
Edge servers still receive ordinary requests for the static HTML, JavaScript, CSS, fonts, and sample assets. The selected image bytes never enter those requests.
Try it and inspect the source
- Live tool: semaphore.bobochang.cn
- Source: github.com/can4hou6joeng4/Semaphore
The project is MIT licensed. Feedback on the conversion output, charsets, accessibility, or export formats is welcome.
답글 남기기