JSFiddle - React, Tailwind, and code Playground

by Scott Kaye

HTML

<span id="out"></span>

<div class="message">Drag and drop an image to box on the right.</div>

CSS

body {
    margin: 0;
    background: #111;
	color: #fff;
}

#out {
    font-family: monospace;
    font-size: 16px;
    white-space: pre;
    display: block;
    zoom: 0.5;
}

canvas {
    position: fixed;
    top: 0;
    right: 0;
    padding: 0.5vw;
    margin: 0.5vw;
    background: #666;
    max-width: 25vw;
    max-height: 25vw;
}

canvas.drag {
    background: #38f;
}

.message {
	background: #222;
	padding: 25px;
	font-family: Segoe UI, sans-serif;
}

JavaScript

const out = document.getElementById("out");
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const RESOLUTION_FACTOR = 5;
const VERTICAL_SQUISH = 2;

document.body.appendChild(canvas);

canvas.ondragover = function(e) {
    canvas.classList.add("drag");
    e.preventDefault();
};

canvas.ondragleave = function(e) {
    canvas.classList.remove("drag");
    e.preventDefault();
};

canvas.ondrop = async function(e) {
    e.preventDefault();
    canvas.classList.remove("drag");

    let file = e.dataTransfer.files[0];
    if (!file) {
        return;
    }

    let image = await window.createImageBitmap(file);
    loadImage(image);
    generateASCII();
};

let pixels = ` .,╹░█`;

const scale = (num, in_min, in_max, out_min, out_max) => {
    return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

function loadImage(img) {
    canvas.width = img.width / RESOLUTION_FACTOR;
    canvas.height = img.height / RESOLUTION_FACTOR / VERTICAL_SQUISH;

    ctx.drawImage(img,
        0, 0, img.width, img.height,
        0, 0, img.width / RESOLUTION_FACTOR, img.height / RESOLUTION_FACTOR / VERTICAL_SQUISH
    );
}

function generateASCII() {
    let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

    let result = imageData.data.batch(4).reduce((a, c, i) => {
        let avg = (c[0] + c[1] + c[2]) / 3;
        let pixel = pixels[scale(avg, 0, 255, 0, pixels.length - 1) | 0];
        return a + pixel + (i % (imageData.width) !== 0 ? "" : "\n");
    }, "");

    out.textContent = result;
}

Uint8ClampedArray.prototype.batch = function(size) {
    let result = [];

    for (let i = 0; i < this.length; i += size) {
        let subResult = [];

        for (let j = 0; j < size; ++j) {
            subResult.push(this[i + j]);
        }

        result.push(subResult);
    }

    return result;
};