Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- (() => {
- if (window.__asciiHUD) return;
- window.__asciiHUD = true;
- let asciiChars = "@%#*+=-:. ";
- let inverted = false, flipH = false, flipV = false, rotation = 0;
- let originalImage = null, asciiWidth = 80, asciiHeight = 80, contrast = 1;
- let latestAsciiText = "";
- let selectedFont = "monospace";
- let rawText = "";
- let colorMode = "none"; // "none" | "html" | "ansi" | "blocks"
- const hud = document.createElement("div");
- hud.style.position = "fixed";
- hud.style.top = "50px";
- hud.style.left = "50px";
- hud.style.width = "640px";
- hud.style.height = "720px";
- hud.style.maxHeight = "900px";
- hud.style.background = "rgba(0,0,0,0.88)";
- hud.style.border = "2px solid #888";
- hud.style.borderRadius = "8px";
- hud.style.overflow = "hidden";
- hud.style.zIndex = 99999;
- hud.style.fontFamily = "sans-serif";
- const header = document.createElement("div");
- header.innerText = "ASCII HUD (Color Modes ✓)";
- header.style.cursor = "move";
- header.style.padding = "6px 8px";
- header.style.background = "rgba(0,0,0,0.95)";
- header.style.color = "#ccc";
- header.style.fontWeight = "700";
- header.style.userSelect = "none";
- header.style.display = "flex";
- header.style.justifyContent = "space-between";
- header.style.alignItems = "center";
- hud.appendChild(header);
- const headerLeft = document.createElement("div");
- headerLeft.innerText = "ASCII HUD";
- headerLeft.style.color = "#ddd";
- headerLeft.style.fontWeight = "700";
- headerLeft.style.fontSize = "13px";
- header.appendChild(headerLeft);
- const closeBtn = document.createElement("button");
- closeBtn.innerText = "✖";
- closeBtn.style.background = "transparent";
- closeBtn.style.border = "none";
- closeBtn.style.color = "#eee";
- closeBtn.style.fontSize = "16px";
- closeBtn.style.cursor = "pointer";
- closeBtn.style.marginLeft = "8px";
- closeBtn.onclick = () => { hud.remove(); window.__asciiHUD = false; };
- header.appendChild(closeBtn);
- const container = document.createElement("div");
- container.style.padding = "10px";
- container.style.overflow = "auto";
- container.style.height = "calc(100% - 46px)";
- hud.appendChild(container);
- // UI markup (including new mode selector and ANSI/PNG export)
- container.innerHTML = `
- <div style="display:flex;gap:8px;align-items:center;">
- <input type="file" id="asciiImageInput" title="Upload image">
- <button id="fromClipboardBtn" title="Paste image from clipboard">From Clipboard</button>
- <button id="webcamBtn" title="Toggle webcam">Webcam</button>
- <button id="exportPngBtn" title="Export colored ASCII as PNG">Export PNG</button>
- </div>
- <div style="margin-top:8px;display:flex;gap:8px;align-items:center;">
- <label style="color:white;">Upload Font:</label>
- <input type="file" id="fontUpload" multiple>
- <label style="color:white;">Select Font:</label>
- <select id="fontSelect" style="flex:1;background:#222;color:#eee;border:none;padding:4px;">
- <option value="monospace">monospace</option>
- <option value="Arial">Arial</option>
- <option value="Courier New">Courier New</option>
- <option value="Times New Roman">Times New Roman</option>
- <option value="Verdana">Verdana</option>
- </select>
- </div>
- <div style="margin-top:8px;">
- <label for="textInput" style="color:white;">Text Input (for text->ASCII):</label><br>
- <textarea id="textInput" style="width:100%;height:70px;background:#222;color:#eee;border:none;resize:none;padding:6px;"></textarea>
- <button id="useTextBtn" style="margin-top:4px;">Use Text</button>
- </div>
- <div style="margin-top:8px;display:flex;gap:8px;align-items:center;">
- <label style="color:white;">Custom Chars:</label>
- <input type="text" id="charSetInput" value="${asciiChars}" style="flex:1;background:#222;color:#eee;border:none;padding:4px;">
- <label style="color:white;">Mode:</label>
- <select id="colorModeSelect" style="background:#222;color:#eee;border:none;padding:4px;">
- <option value="none">Plain ASCII</option>
- <option value="html">HTML-colored</option>
- <option value="ansi">ANSI (export)</option>
- <option value="blocks">Block mode</option>
- </select>
- <button id="copyAnsiBtn" title="Copy ANSI to clipboard">Copy ANSI</button>
- </div>
- <div style="margin-top:8px;">
- <div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
- <button id="selectAllBtn">Select All</button>
- <button id="copyBtn">Copy Text</button>
- <button id="downloadBtn">Download TXT</button>
- <button id="invertBtn">Invert</button>
- <button id="flipHBtn">Flip H</button>
- <button id="flipVBtn">Flip V</button>
- </div>
- </div>
- <div style="margin-top:8px;display:flex;gap:8px;align-items:center;">
- <label style="color:white;">Rotation (°):</label>
- <input type="range" id="rotationSlider" min="0" max="360" value="${rotation}">
- <span id="rotationValue">${rotation}</span>
- </div>
- <div style="margin-top:6px;display:flex;gap:8px;align-items:center;">
- <label style="color:white;">Width:</label>
- <input type="range" id="widthSlider" min="-200" max="200" value="${asciiWidth}">
- <span id="widthValue">${asciiWidth}</span>
- <label style="color:white;">Height:</label>
- <input type="range" id="heightSlider" min="-200" max="200" value="${asciiHeight}">
- <span id="heightValue">${asciiHeight}</span>
- </div>
- <div style="margin-top:6px;display:flex;gap:8px;align-items:center;">
- <label style="color:white;">Size:</label>
- <input type="range" id="sizeSlider" min="-100" max="100" value="80">
- <span id="sizeValue">80</span>
- <label style="color:white;">Zoom:</label>
- <input type="range" id="zoomSlider" min="0.1" max="5" step="0.1" value="1">
- <span id="zoomValue">1x</span>
- </div>
- <div style="margin-top:6px;display:flex;gap:8px;align-items:center;">
- <label style="color:white;">Contrast:</label>
- <input type="range" id="contrastSlider" min="0.1" max="3" step="0.1" value="${contrast}">
- <span id="contrastValue">${contrast}</span>
- </div>
- <div id="webcamPreviewContainer" style="margin-top:8px;">
- <video id="webcamPreview" autoplay playsinline style="width:100%; display:none; border:1px solid #555;"></video>
- </div>
- <div style="margin-top:8px;">
- <label style="color:white;">Plain ASCII Output (copyable):</label>
- <textarea id="asciiOutput" readonly style="width:100%; height:260px; background:transparent; color:#eee; border:none; resize:none; font-family:monospace; font-size:8px; line-height:6px; padding:6px;"></textarea>
- <pre id="asciiHtmlOutput" style="display:none; width:100%; height:260px; overflow:auto; background:transparent; color:#eee; border:none; padding:6px; margin:0; box-sizing:border-box; font-family:monospace;"></pre>
- </div>
- `;
- document.body.appendChild(hud);
- // DOM refs
- const fontUpload = container.querySelector("#fontUpload");
- const fontSelect = container.querySelector("#fontSelect");
- const imageInput = container.querySelector("#asciiImageInput");
- const asciiOutput = container.querySelector("#asciiOutput");
- const asciiHtmlOutput = container.querySelector("#asciiHtmlOutput");
- const textInput = container.querySelector("#textInput");
- const useTextBtn = container.querySelector("#useTextBtn");
- const fromClipboardBtn = container.querySelector("#fromClipboardBtn");
- const downloadBtn = container.querySelector("#downloadBtn");
- const charSetInput = container.querySelector("#charSetInput");
- const webcamBtn = container.querySelector("#webcamBtn");
- const webcamPreview = container.querySelector("#webcamPreview");
- const colorModeSelect = container.querySelector("#colorModeSelect");
- const copyAnsiBtn = container.querySelector("#copyAnsiBtn");
- const exportPngBtn = container.querySelector("#exportPngBtn");
- const selectAllBtn = container.querySelector("#selectAllBtn");
- const copyBtn = container.querySelector("#copyBtn");
- const invertBtn = container.querySelector("#invertBtn");
- const flipHBtn = container.querySelector("#flipHBtn");
- const flipVBtn = container.querySelector("#flipVBtn");
- const widthSlider = container.querySelector("#widthSlider");
- const widthValue = container.querySelector("#widthValue");
- const heightSlider = container.querySelector("#heightSlider");
- const heightValue = container.querySelector("#heightValue");
- const sizeSlider = container.querySelector("#sizeSlider");
- const sizeValue = container.querySelector("#sizeValue");
- const zoomSlider = container.querySelector("#zoomSlider");
- const zoomValue = container.querySelector("#zoomValue");
- const contrastSlider = container.querySelector("#contrastSlider");
- const contrastValue = container.querySelector("#contrastValue");
- const rotationSlider = container.querySelector("#rotationSlider");
- const rotationValue = container.querySelector("#rotationValue");
- // IndexedDB helpers (unchanged)
- function openFontDB() {
- return new Promise((resolve, reject) => {
- const request = indexedDB.open("ASCIIHUD_Fonts", 1);
- request.onupgradeneeded = e => {
- const db = e.target.result;
- if (!db.objectStoreNames.contains("fonts")) db.createObjectStore("fonts", { keyPath: "name" });
- };
- request.onsuccess = e => resolve(e.target.result);
- request.onerror = e => reject(e);
- });
- }
- async function saveFontToDB(name, dataURL) {
- const db = await openFontDB();
- const tx = db.transaction("fonts", "readwrite");
- const store = tx.objectStore("fonts");
- store.put({ name, data: dataURL });
- return tx.complete;
- }
- async function loadFontsFromDB() {
- const db = await openFontDB();
- return new Promise((resolve, reject) => {
- const tx = db.transaction("fonts", "readonly");
- const store = tx.objectStore("fonts");
- const request = store.getAll();
- request.onsuccess = e => resolve(e.target.result);
- request.onerror = e => reject(e);
- });
- }
- // Load stored fonts
- (async () => {
- const storedFonts = await loadFontsFromDB();
- storedFonts.forEach(f => {
- const fontFace = new FontFace(f.name, `url(${f.data})`);
- fontFace.load().then(loaded => {
- document.fonts.add(loaded);
- const opt = document.createElement("option");
- opt.value = f.name;
- opt.textContent = f.name;
- opt.style.fontFamily = f.name;
- fontSelect.appendChild(opt);
- }).catch(err => console.error("Failed to load stored font:", err));
- });
- })();
- // Font upload
- fontUpload.onchange = () => {
- for (const file of fontUpload.files) {
- const reader = new FileReader();
- reader.onload = async e => {
- const fontName = file.name.replace(/\..+$/, "");
- try {
- const fontFace = new FontFace(fontName, `url(${e.target.result})`);
- await fontFace.load();
- document.fonts.add(fontFace);
- const opt = document.createElement("option");
- opt.value = fontName;
- opt.textContent = fontName;
- opt.style.fontFamily = fontName;
- fontSelect.appendChild(opt);
- fontSelect.value = fontName;
- selectedFont = fontName;
- await saveFontToDB(fontName, e.target.result);
- if (rawText) generateTextImage();
- } catch (err) { console.error("Font failed to load:", err); }
- };
- reader.readAsDataURL(file);
- }
- };
- fontSelect.onchange = () => { selectedFont = fontSelect.value; if (rawText) generateTextImage(); };
- charSetInput.addEventListener("input", () => { asciiChars = charSetInput.value || "@%#*+=-:. "; renderASCII(); });
- // Utility: draw image with rotation/flip to a canvas scaled to desired ascii size
- function drawToScaledCanvas(img, targetW, targetH) {
- if (!img) return null;
- const radians = rotation * Math.PI / 180;
- const imgW = img.width;
- const imgH = img.height;
- const sin = Math.abs(Math.sin(radians));
- const cos = Math.abs(Math.cos(radians));
- const rotWidth = imgW * cos + imgH * sin;
- const rotHeight = imgW * sin + imgH * cos;
- const canvas = document.createElement("canvas");
- canvas.width = Math.max(1, Math.round(targetW));
- canvas.height = Math.max(1, Math.round(targetH));
- const ctx = canvas.getContext("2d");
- ctx.clearRect(0,0,canvas.width,canvas.height);
- ctx.save();
- ctx.translate(canvas.width/2, canvas.height/2);
- ctx.rotate(radians);
- ctx.scale(canvas.width/rotWidth, canvas.height/rotHeight);
- ctx.translate(-imgW/2, -imgH/2);
- // flips
- ctx.translate(flipH ? imgW : 0, flipV ? imgH : 0);
- ctx.scale(flipH ? -1 : 1, flipV ? -1 : 1);
- ctx.drawImage(img, 0, 0, imgW, imgH);
- ctx.restore();
- return canvas;
- }
- // Core: plain ascii (brightness -> chars)
- function imageToASCIIString(img, maxWidthChars = 80, maxHeightChars = 80) {
- const canvas = drawToScaledCanvas(img, maxWidthChars, maxHeightChars);
- if (!canvas) return "";
- const ctx = canvas.getContext("2d");
- const data = ctx.getImageData(0,0,canvas.width,canvas.height).data;
- let ascii = "";
- for (let y=0;y<canvas.height;y++){
- for (let x=0;x<canvas.width;x++){
- const i = (y*canvas.width + x)*4;
- const r = data[i], g = data[i+1], b = data[i+2];
- const lum = (0.299*r + 0.587*g + 0.114*b)/255;
- let bright = Math.min(Math.max((lum-0.5)*contrast+0.5,0),1);
- let idx = Math.floor(bright*(asciiChars.length-1));
- if (inverted) idx = asciiChars.length-1-idx;
- ascii += asciiChars[idx];
- }
- ascii += "\n";
- }
- return ascii;
- }
- // New: HTML-colored ASCII (span per char)
- function imageToHtmlColored(img, maxWidthChars = 80, maxHeightChars = 80, useBlocks=false) {
- const canvas = drawToScaledCanvas(img, maxWidthChars, maxHeightChars);
- if (!canvas) return "";
- const ctx = canvas.getContext("2d");
- const data = ctx.getImageData(0,0,canvas.width,canvas.height).data;
- // Use a <pre> friendly string with spans
- let out = "";
- for (let y=0;y<canvas.height;y++){
- for (let x=0;x<canvas.width;x++){
- const i = (y*canvas.width + x)*4;
- const r = data[i], g = data[i+1], b = data[i+2], a = data[i+3];
- if (a === 0) {
- out += '<span style="color:transparent"> </span>';
- continue;
- }
- // Choose character by brightness if not using blocks
- const lum = (0.299*r + 0.587*g + 0.114*b)/255;
- let bright = Math.min(Math.max((lum-0.5)*contrast+0.5,0),1);
- let ch = useBlocks ? "█" : asciiChars[Math.floor(bright*(asciiChars.length-1))];
- if (inverted && !useBlocks) ch = asciiChars[asciiChars.length-1 - Math.floor(bright*(asciiChars.length-1))];
- // Use foreground color (could also use background)
- out += `<span style="color: rgb(${r},${g},${b})">${escapeHtml(ch)}</span>`;
- }
- out += "\n";
- }
- return out;
- }
- // New: ANSI (returns string with 24-bit ANSI escape sequences)
- // Uses \x1b[38;2;R;G;Bm for foreground color and resets per char
- function imageToAnsi(img, maxWidthChars = 80, maxHeightChars = 80, useBlocks=false) {
- const canvas = drawToScaledCanvas(img, maxWidthChars, maxHeightChars);
- if (!canvas) return "";
- const ctx = canvas.getContext("2d");
- const data = ctx.getImageData(0,0,canvas.width,canvas.height).data;
- let out = "";
- for (let y=0;y<canvas.height;y++){
- for (let x=0;x<canvas.width;x++){
- const i = (y*canvas.width + x)*4;
- const r = data[i], g = data[i+1], b = data[i+2], a = data[i+3];
- if (a === 0) {
- out += " ";
- continue;
- }
- const lum = (0.299*r + 0.587*g + 0.114*b)/255;
- let bright = Math.min(Math.max((lum-0.5)*contrast+0.5,0),1);
- let ch = useBlocks ? "█" : asciiChars[Math.floor(bright*(asciiChars.length-1))];
- if (inverted && !useBlocks) ch = asciiChars[asciiChars.length-1 - Math.floor(bright*(asciiChars.length-1))];
- out += `\x1b[38;2;${r};${g};${b}m${ch}\x1b[0m`;
- }
- out += "\n";
- }
- return out;
- }
- // Escape HTML for safe spans
- function escapeHtml(s) {
- return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
- }
- // Export colored HTML view as PNG (rendering asciiHtmlOutput to canvas)
- async function exportAsciiHtmlToPng() {
- // create a canvas and draw colored ascii using same drawToScaledCanvas but scaled for font
- const scale = Math.max(1, (asciiWidth + asciiHeight) / 160);
- const zoom = parseFloat(zoomSlider.value);
- const charW = 8 * scale * zoom; // approximate char width
- const charH = 12 * scale * zoom; // approximate line height
- const cols = Math.max(1, asciiWidth);
- const rows = Math.max(1, asciiHeight);
- const canvas = document.createElement("canvas");
- canvas.width = Math.ceil(charW * cols);
- canvas.height = Math.ceil(charH * rows);
- const ctx = canvas.getContext("2d");
- // white background to make png readable
- ctx.fillStyle = "#00000000";
- ctx.fillRect(0,0,canvas.width,canvas.height);
- // draw per-cell colored char (use block mode for best visuals)
- const imgCanvas = drawToScaledCanvas(originalImage, cols, rows);
- if (!imgCanvas) {
- alert("No image to export.");
- return;
- }
- const imgData = imgCanvas.getContext("2d").getImageData(0,0,cols,rows).data;
- ctx.textBaseline = "top";
- ctx.font = `${Math.round(charH)}px ${selectedFont}, monospace`;
- for (let y=0;y<rows;y++){
- for (let x=0;x<cols;x++){
- const i = (y*cols + x)*4;
- const r = imgData[i], g = imgData[i+1], b = imgData[i+2], a = imgData[i+3];
- if (a === 0) continue;
- ctx.fillStyle = `rgb(${r},${g},${b})`;
- const lum = (0.299*r + 0.587*g + 0.114*b)/255;
- const bright = Math.min(Math.max((lum-0.5)*contrast+0.5,0),1);
- const ch = "█";
- ctx.fillText(ch, x*charW, y*charH);
- }
- }
- // download
- const a = document.createElement("a");
- a.href = canvas.toDataURL();
- a.download = "ascii_color.png";
- a.click();
- }
- // Plain ascii render
- function renderAsciiPlain() {
- if (!originalImage) return;
- latestAsciiText = imageToASCIIString(originalImage, asciiWidth, asciiHeight);
- asciiOutput.value = latestAsciiText;
- }
- // Full render manager (switches output by colorMode)
- function renderASCII() {
- if (!originalImage) return;
- if (colorMode === "none") {
- asciiHtmlOutput.style.display = "none";
- asciiOutput.style.display = "block";
- renderAsciiPlain();
- } else if (colorMode === "html") {
- asciiOutput.style.display = "none";
- asciiHtmlOutput.style.display = "block";
- // Use blocks mode? Use character mode
- asciiHtmlOutput.innerHTML = imageToHtmlColored(originalImage, asciiWidth, asciiHeight, false);
- } else if (colorMode === "blocks") {
- asciiOutput.style.display = "none";
- asciiHtmlOutput.style.display = "block";
- asciiHtmlOutput.innerHTML = imageToHtmlColored(originalImage, asciiWidth, asciiHeight, true);
- } else if (colorMode === "ansi") {
- // For ansi mode we still show plain ASCII in textarea but allow ANSI export
- asciiHtmlOutput.style.display = "none";
- asciiOutput.style.display = "block";
- latestAsciiText = imageToASCIIString(originalImage, asciiWidth, asciiHeight);
- asciiOutput.value = latestAsciiText;
- }
- }
- // Auto font scaling function (keeps ascii readable)
- function updateAsciiFontScaling() {
- const zoom = parseFloat(zoomSlider.value);
- const scale = Math.max(0.2, (asciiWidth + asciiHeight) / 160);
- asciiOutput.style.fontSize = `${8 * scale * zoom}px`;
- asciiOutput.style.lineHeight = `${6 * scale * zoom}px`;
- asciiHtmlOutput.style.fontSize = `${8 * scale * zoom}px`;
- asciiHtmlOutput.style.lineHeight = `${6 * scale * zoom}px`;
- }
- // BUTTONS
- useTextBtn.onclick = () => { rawText = textInput.value; if (rawText) generateTextImage(); };
- selectAllBtn.onclick = () => { asciiOutput.select(); };
- copyBtn.onclick = async () => {
- try {
- await navigator.clipboard.writeText(asciiOutput.value);
- } catch (e) {
- alert("Copy failed.");
- }
- };
- invertBtn.onclick = () => { inverted = !inverted; renderASCII(); };
- flipHBtn.onclick = () => { flipH = !flipH; renderASCII(); };
- flipVBtn.onclick = () => { flipV = !flipV; renderASCII(); };
- downloadBtn.onclick = () => { const blob = new Blob([latestAsciiText], {type:"text/plain"}); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "ascii_output.txt"; a.click(); URL.revokeObjectURL(a.href); };
- copyAnsiBtn.onclick = async () => {
- if (!originalImage) { alert("No image."); return; }
- const ansi = imageToAnsi(originalImage, asciiWidth, asciiHeight, false);
- try {
- await navigator.clipboard.writeText(ansi);
- alert("ANSI copied to clipboard.");
- } catch (e) { alert("Copy failed."); }
- };
- exportPngBtn.onclick = () => exportAsciiHtmlToPng();
- // SLIDERS: width, height, size, zoom, contrast, rotation
- widthSlider.addEventListener("input", () => {
- const val = parseInt(widthSlider.value);
- flipH = val < 0;
- asciiWidth = Math.abs(val) || 1;
- widthValue.innerText = val;
- updateAsciiFontScaling();
- renderASCII();
- });
- heightSlider.addEventListener("input", () => {
- const val = parseInt(heightSlider.value);
- flipV = val < 0;
- asciiHeight = Math.abs(val) || 1;
- heightValue.innerText = val;
- updateAsciiFontScaling();
- renderASCII();
- });
- sizeSlider.addEventListener("input", () => {
- let val = parseInt(sizeSlider.value);
- sizeValue.innerText = val;
- if (val < 0) { flipH = true; flipV = true; asciiWidth = asciiHeight = Math.abs(val) || 1; }
- else { flipH = false; flipV = false; asciiWidth = asciiHeight = val || 1; }
- widthSlider.value = asciiWidth; heightSlider.value = asciiHeight;
- widthValue.innerText = asciiWidth; heightValue.innerText = asciiHeight;
- updateAsciiFontScaling();
- renderASCII();
- });
- zoomSlider.addEventListener("input", () => {
- zoomValue.innerText = parseFloat(zoomSlider.value).toFixed(1) + "x";
- updateAsciiFontScaling();
- renderASCII();
- });
- contrastSlider.addEventListener("input", () => { contrast = parseFloat(contrastSlider.value); contrastValue.innerText = contrast.toFixed(1); renderASCII(); });
- rotationSlider.addEventListener("input", () => { rotation = parseFloat(rotationSlider.value); rotationValue.innerText = rotation.toFixed(1); renderASCII(); });
- colorModeSelect.addEventListener("change", () => {
- colorMode = colorModeSelect.value;
- // show/hide ANSI button and export
- copyAnsiBtn.style.display = (colorMode === "ansi") ? "inline-block" : "inline-block";
- if (colorMode === "none") {
- asciiOutput.style.display = "block";
- asciiHtmlOutput.style.display = "none";
- }
- renderASCII();
- });
- // IMAGE INPUT
- imageInput.onchange = () => { const file=imageInput.files[0]; if(!file) return; const img=new Image(); img.onload=()=>{ originalImage=img; renderASCII(); }; img.src=URL.createObjectURL(file); };
- // CLIPBOARD IMAGE
- fromClipboardBtn.onclick = async () => {
- try {
- const items = await navigator.clipboard.read();
- for (const item of items) {
- if(!item.types.some(t=>t.startsWith('image/'))) continue;
- const blob = await item.getType(item.types[0]);
- const img = new Image();
- img.onload=()=>{ originalImage=img; renderASCII(); };
- img.src=URL.createObjectURL(blob);
- break;
- }
- } catch(e) { alert("Clipboard image paste not supported or permission denied."); console.error(e); }
- };
- // TEXT->IMAGE
- function generateTextImage() {
- if (!rawText) return;
- const canvas = document.createElement("canvas");
- const ctx = canvas.getContext("2d");
- const fontSize = 36;
- const lines = rawText.split("\n");
- ctx.font = `${fontSize}px "${selectedFont}", monospace`;
- const width = Math.max(...lines.map(l => ctx.measureText(l).width)) + 10;
- const height = lines.length * fontSize;
- canvas.width = width;
- canvas.height = height;
- ctx.fillStyle = "white";
- ctx.fillRect(0,0,width,height);
- ctx.fillStyle = "black";
- ctx.textBaseline = "top";
- ctx.font = `${fontSize}px "${selectedFont}", monospace`;
- lines.forEach((line,i)=>ctx.fillText(line,5,i*fontSize));
- const img = new Image();
- img.onload = () => { originalImage = img; renderASCII(); };
- img.src = canvas.toDataURL();
- }
- // WEBCAM
- let webcamStream = null, webcamVideo = null, lastFrameTime = 0;
- webcamBtn.onclick = async () => {
- if (!webcamStream) {
- try {
- webcamVideo = document.createElement("video");
- webcamVideo.autoplay = true;
- webcamVideo.playsInline = true;
- webcamStream = await navigator.mediaDevices.getUserMedia({video:true});
- webcamVideo.srcObject = webcamStream;
- webcamPreview.srcObject = webcamStream;
- webcamPreview.style.display = "block";
- webcamVideo.onloadeddata = () => { requestAnimationFrame(updateWebcamASCII); };
- } catch (e) { alert("Cannot access webcam."); console.error(e); }
- } else {
- webcamStream.getTracks().forEach(t=>t.stop());
- webcamStream=null; webcamVideo=null; originalImage=null;
- webcamPreview.srcObject=null; webcamPreview.style.display="none";
- }
- };
- function updateWebcamASCII(ts) {
- if (!webcamStream || !webcamVideo) return;
- // limit to ~12 FPS for performance
- if (ts - lastFrameTime < 80) { requestAnimationFrame(updateWebcamASCII); return; }
- lastFrameTime = ts;
- const tempCanvas = document.createElement("canvas");
- tempCanvas.width = webcamVideo.videoWidth || 320;
- tempCanvas.height = webcamVideo.videoHeight || 240;
- const tempCtx = tempCanvas.getContext("2d");
- tempCtx.drawImage(webcamVideo,0,0,tempCanvas.width,tempCanvas.height);
- originalImage = tempCanvas;
- renderASCII();
- requestAnimationFrame(updateWebcamASCII);
- }
- // DRAG & RESIZE
- let isDragging=false, offsetX, offsetY;
- header.addEventListener("mousedown", e=>{ isDragging=true; offsetX=e.clientX-hud.offsetLeft; offsetY=e.clientY-hud.offsetTop; });
- document.addEventListener("mouseup", ()=>{ isDragging=false; isResizing=false; });
- document.addEventListener("mousemove", e=>{
- if(!isDragging) return;
- const maxX=window.innerWidth-hud.offsetWidth;
- const maxY=window.innerHeight-hud.offsetHeight;
- hud.style.left=Math.min(Math.max(0,e.clientX-offsetX),maxX)+"px";
- hud.style.top=Math.min(Math.max(0,e.clientY-offsetY),maxY)+"px";
- });
- const resizeHandle=document.createElement("div");
- resizeHandle.style.width="14px";
- resizeHandle.style.height="14px";
- resizeHandle.style.position="absolute";
- resizeHandle.style.right="4px";
- resizeHandle.style.bottom="4px";
- resizeHandle.style.cursor="nwse-resize";
- resizeHandle.style.background="transparent";
- hud.appendChild(resizeHandle);
- let isResizing=false, startX, startY, startWidth, startHeight;
- resizeHandle.addEventListener("mousedown", e=>{
- e.stopPropagation();
- isResizing=true;
- startX=e.clientX; startY=e.clientY;
- startWidth=hud.offsetWidth; startHeight=hud.offsetHeight;
- });
- document.addEventListener("mousemove", e=>{
- if(!isResizing) return;
- const dx=e.clientX-startX;
- const dy=e.clientY-startY;
- const maxWidth=window.innerWidth-hud.offsetLeft;
- const maxHeight=window.innerHeight-hud.offsetTop;
- hud.style.width=Math.min(Math.max(360,startWidth+dx),maxWidth)+"px";
- hud.style.height=Math.min(Math.max(320,startHeight+dy),maxHeight)+"px";
- });
- // initial scaling
- updateAsciiFontScaling();
- // initial render (if image already set)
- if (originalImage) renderASCII();
- })();
Advertisement