smoothretro82

Ascii & webcam Image Converter + Resizer version 3.23

Dec 3rd, 2025 (edited)
163
0
Never
5
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 27.95 KB | None | 0 0
  1. (() => {
  2. if (window.__asciiHUD) return;
  3. window.__asciiHUD = true;
  4.  
  5. let asciiChars = "@%#*+=-:. ";
  6. let inverted = false, flipH = false, flipV = false, rotation = 0;
  7. let originalImage = null, asciiWidth = 80, asciiHeight = 80, contrast = 1;
  8. let latestAsciiText = "";
  9. let selectedFont = "monospace";
  10. let rawText = "";
  11. let colorMode = "none"; // "none" | "html" | "ansi" | "blocks"
  12.  
  13. const hud = document.createElement("div");
  14. hud.style.position = "fixed";
  15. hud.style.top = "50px";
  16. hud.style.left = "50px";
  17. hud.style.width = "640px";
  18. hud.style.height = "720px";
  19. hud.style.maxHeight = "900px";
  20. hud.style.background = "rgba(0,0,0,0.88)";
  21. hud.style.border = "2px solid #888";
  22. hud.style.borderRadius = "8px";
  23. hud.style.overflow = "hidden";
  24. hud.style.zIndex = 99999;
  25. hud.style.fontFamily = "sans-serif";
  26.  
  27. const header = document.createElement("div");
  28. header.innerText = "ASCII HUD (Color Modes ✓)";
  29. header.style.cursor = "move";
  30. header.style.padding = "6px 8px";
  31. header.style.background = "rgba(0,0,0,0.95)";
  32. header.style.color = "#ccc";
  33. header.style.fontWeight = "700";
  34. header.style.userSelect = "none";
  35. header.style.display = "flex";
  36. header.style.justifyContent = "space-between";
  37. header.style.alignItems = "center";
  38. hud.appendChild(header);
  39.  
  40. const headerLeft = document.createElement("div");
  41. headerLeft.innerText = "ASCII HUD";
  42. headerLeft.style.color = "#ddd";
  43. headerLeft.style.fontWeight = "700";
  44. headerLeft.style.fontSize = "13px";
  45. header.appendChild(headerLeft);
  46.  
  47. const closeBtn = document.createElement("button");
  48. closeBtn.innerText = "✖";
  49. closeBtn.style.background = "transparent";
  50. closeBtn.style.border = "none";
  51. closeBtn.style.color = "#eee";
  52. closeBtn.style.fontSize = "16px";
  53. closeBtn.style.cursor = "pointer";
  54. closeBtn.style.marginLeft = "8px";
  55. closeBtn.onclick = () => { hud.remove(); window.__asciiHUD = false; };
  56. header.appendChild(closeBtn);
  57.  
  58. const container = document.createElement("div");
  59. container.style.padding = "10px";
  60. container.style.overflow = "auto";
  61. container.style.height = "calc(100% - 46px)";
  62. hud.appendChild(container);
  63.  
  64. // UI markup (including new mode selector and ANSI/PNG export)
  65. container.innerHTML = `
  66. <div style="display:flex;gap:8px;align-items:center;">
  67. <input type="file" id="asciiImageInput" title="Upload image">
  68. <button id="fromClipboardBtn" title="Paste image from clipboard">From Clipboard</button>
  69. <button id="webcamBtn" title="Toggle webcam">Webcam</button>
  70. <button id="exportPngBtn" title="Export colored ASCII as PNG">Export PNG</button>
  71. </div>
  72.  
  73. <div style="margin-top:8px;display:flex;gap:8px;align-items:center;">
  74. <label style="color:white;">Upload Font:</label>
  75. <input type="file" id="fontUpload" multiple>
  76. <label style="color:white;">Select Font:</label>
  77. <select id="fontSelect" style="flex:1;background:#222;color:#eee;border:none;padding:4px;">
  78. <option value="monospace">monospace</option>
  79. <option value="Arial">Arial</option>
  80. <option value="Courier New">Courier New</option>
  81. <option value="Times New Roman">Times New Roman</option>
  82. <option value="Verdana">Verdana</option>
  83. </select>
  84. </div>
  85.  
  86. <div style="margin-top:8px;">
  87. <label for="textInput" style="color:white;">Text Input (for text->ASCII):</label><br>
  88. <textarea id="textInput" style="width:100%;height:70px;background:#222;color:#eee;border:none;resize:none;padding:6px;"></textarea>
  89. <button id="useTextBtn" style="margin-top:4px;">Use Text</button>
  90. </div>
  91.  
  92. <div style="margin-top:8px;display:flex;gap:8px;align-items:center;">
  93. <label style="color:white;">Custom Chars:</label>
  94. <input type="text" id="charSetInput" value="${asciiChars}" style="flex:1;background:#222;color:#eee;border:none;padding:4px;">
  95. <label style="color:white;">Mode:</label>
  96. <select id="colorModeSelect" style="background:#222;color:#eee;border:none;padding:4px;">
  97. <option value="none">Plain ASCII</option>
  98. <option value="html">HTML-colored</option>
  99. <option value="ansi">ANSI (export)</option>
  100. <option value="blocks">Block mode</option>
  101. </select>
  102. <button id="copyAnsiBtn" title="Copy ANSI to clipboard">Copy ANSI</button>
  103. </div>
  104.  
  105. <div style="margin-top:8px;">
  106. <div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
  107. <button id="selectAllBtn">Select All</button>
  108. <button id="copyBtn">Copy Text</button>
  109. <button id="downloadBtn">Download TXT</button>
  110. <button id="invertBtn">Invert</button>
  111. <button id="flipHBtn">Flip H</button>
  112. <button id="flipVBtn">Flip V</button>
  113. </div>
  114. </div>
  115.  
  116. <div style="margin-top:8px;display:flex;gap:8px;align-items:center;">
  117. <label style="color:white;">Rotation (°):</label>
  118. <input type="range" id="rotationSlider" min="0" max="360" value="${rotation}">
  119. <span id="rotationValue">${rotation}</span>
  120. </div>
  121.  
  122. <div style="margin-top:6px;display:flex;gap:8px;align-items:center;">
  123. <label style="color:white;">Width:</label>
  124. <input type="range" id="widthSlider" min="-200" max="200" value="${asciiWidth}">
  125. <span id="widthValue">${asciiWidth}</span>
  126.  
  127. <label style="color:white;">Height:</label>
  128. <input type="range" id="heightSlider" min="-200" max="200" value="${asciiHeight}">
  129. <span id="heightValue">${asciiHeight}</span>
  130. </div>
  131.  
  132. <div style="margin-top:6px;display:flex;gap:8px;align-items:center;">
  133. <label style="color:white;">Size:</label>
  134. <input type="range" id="sizeSlider" min="-100" max="100" value="80">
  135. <span id="sizeValue">80</span>
  136.  
  137. <label style="color:white;">Zoom:</label>
  138. <input type="range" id="zoomSlider" min="0.1" max="5" step="0.1" value="1">
  139. <span id="zoomValue">1x</span>
  140. </div>
  141.  
  142. <div style="margin-top:6px;display:flex;gap:8px;align-items:center;">
  143. <label style="color:white;">Contrast:</label>
  144. <input type="range" id="contrastSlider" min="0.1" max="3" step="0.1" value="${contrast}">
  145. <span id="contrastValue">${contrast}</span>
  146. </div>
  147.  
  148. <div id="webcamPreviewContainer" style="margin-top:8px;">
  149. <video id="webcamPreview" autoplay playsinline style="width:100%; display:none; border:1px solid #555;"></video>
  150. </div>
  151.  
  152. <div style="margin-top:8px;">
  153. <label style="color:white;">Plain ASCII Output (copyable):</label>
  154. <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>
  155. <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>
  156. </div>
  157. `;
  158.  
  159. document.body.appendChild(hud);
  160.  
  161. // DOM refs
  162. const fontUpload = container.querySelector("#fontUpload");
  163. const fontSelect = container.querySelector("#fontSelect");
  164. const imageInput = container.querySelector("#asciiImageInput");
  165. const asciiOutput = container.querySelector("#asciiOutput");
  166. const asciiHtmlOutput = container.querySelector("#asciiHtmlOutput");
  167. const textInput = container.querySelector("#textInput");
  168. const useTextBtn = container.querySelector("#useTextBtn");
  169. const fromClipboardBtn = container.querySelector("#fromClipboardBtn");
  170. const downloadBtn = container.querySelector("#downloadBtn");
  171. const charSetInput = container.querySelector("#charSetInput");
  172. const webcamBtn = container.querySelector("#webcamBtn");
  173. const webcamPreview = container.querySelector("#webcamPreview");
  174. const colorModeSelect = container.querySelector("#colorModeSelect");
  175. const copyAnsiBtn = container.querySelector("#copyAnsiBtn");
  176. const exportPngBtn = container.querySelector("#exportPngBtn");
  177.  
  178. const selectAllBtn = container.querySelector("#selectAllBtn");
  179. const copyBtn = container.querySelector("#copyBtn");
  180. const invertBtn = container.querySelector("#invertBtn");
  181. const flipHBtn = container.querySelector("#flipHBtn");
  182. const flipVBtn = container.querySelector("#flipVBtn");
  183.  
  184. const widthSlider = container.querySelector("#widthSlider");
  185. const widthValue = container.querySelector("#widthValue");
  186. const heightSlider = container.querySelector("#heightSlider");
  187. const heightValue = container.querySelector("#heightValue");
  188. const sizeSlider = container.querySelector("#sizeSlider");
  189. const sizeValue = container.querySelector("#sizeValue");
  190. const zoomSlider = container.querySelector("#zoomSlider");
  191. const zoomValue = container.querySelector("#zoomValue");
  192. const contrastSlider = container.querySelector("#contrastSlider");
  193. const contrastValue = container.querySelector("#contrastValue");
  194. const rotationSlider = container.querySelector("#rotationSlider");
  195. const rotationValue = container.querySelector("#rotationValue");
  196.  
  197. // IndexedDB helpers (unchanged)
  198. function openFontDB() {
  199. return new Promise((resolve, reject) => {
  200. const request = indexedDB.open("ASCIIHUD_Fonts", 1);
  201. request.onupgradeneeded = e => {
  202. const db = e.target.result;
  203. if (!db.objectStoreNames.contains("fonts")) db.createObjectStore("fonts", { keyPath: "name" });
  204. };
  205. request.onsuccess = e => resolve(e.target.result);
  206. request.onerror = e => reject(e);
  207. });
  208. }
  209. async function saveFontToDB(name, dataURL) {
  210. const db = await openFontDB();
  211. const tx = db.transaction("fonts", "readwrite");
  212. const store = tx.objectStore("fonts");
  213. store.put({ name, data: dataURL });
  214. return tx.complete;
  215. }
  216. async function loadFontsFromDB() {
  217. const db = await openFontDB();
  218. return new Promise((resolve, reject) => {
  219. const tx = db.transaction("fonts", "readonly");
  220. const store = tx.objectStore("fonts");
  221. const request = store.getAll();
  222. request.onsuccess = e => resolve(e.target.result);
  223. request.onerror = e => reject(e);
  224. });
  225. }
  226.  
  227. // Load stored fonts
  228. (async () => {
  229. const storedFonts = await loadFontsFromDB();
  230. storedFonts.forEach(f => {
  231. const fontFace = new FontFace(f.name, `url(${f.data})`);
  232. fontFace.load().then(loaded => {
  233. document.fonts.add(loaded);
  234. const opt = document.createElement("option");
  235. opt.value = f.name;
  236. opt.textContent = f.name;
  237. opt.style.fontFamily = f.name;
  238. fontSelect.appendChild(opt);
  239. }).catch(err => console.error("Failed to load stored font:", err));
  240. });
  241. })();
  242.  
  243. // Font upload
  244. fontUpload.onchange = () => {
  245. for (const file of fontUpload.files) {
  246. const reader = new FileReader();
  247. reader.onload = async e => {
  248. const fontName = file.name.replace(/\..+$/, "");
  249. try {
  250. const fontFace = new FontFace(fontName, `url(${e.target.result})`);
  251. await fontFace.load();
  252. document.fonts.add(fontFace);
  253. const opt = document.createElement("option");
  254. opt.value = fontName;
  255. opt.textContent = fontName;
  256. opt.style.fontFamily = fontName;
  257. fontSelect.appendChild(opt);
  258. fontSelect.value = fontName;
  259. selectedFont = fontName;
  260. await saveFontToDB(fontName, e.target.result);
  261. if (rawText) generateTextImage();
  262. } catch (err) { console.error("Font failed to load:", err); }
  263. };
  264. reader.readAsDataURL(file);
  265. }
  266. };
  267. fontSelect.onchange = () => { selectedFont = fontSelect.value; if (rawText) generateTextImage(); };
  268. charSetInput.addEventListener("input", () => { asciiChars = charSetInput.value || "@%#*+=-:. "; renderASCII(); });
  269.  
  270. // Utility: draw image with rotation/flip to a canvas scaled to desired ascii size
  271. function drawToScaledCanvas(img, targetW, targetH) {
  272. if (!img) return null;
  273. const radians = rotation * Math.PI / 180;
  274. const imgW = img.width;
  275. const imgH = img.height;
  276. const sin = Math.abs(Math.sin(radians));
  277. const cos = Math.abs(Math.cos(radians));
  278. const rotWidth = imgW * cos + imgH * sin;
  279. const rotHeight = imgW * sin + imgH * cos;
  280.  
  281. const canvas = document.createElement("canvas");
  282. canvas.width = Math.max(1, Math.round(targetW));
  283. canvas.height = Math.max(1, Math.round(targetH));
  284. const ctx = canvas.getContext("2d");
  285. ctx.clearRect(0,0,canvas.width,canvas.height);
  286.  
  287. ctx.save();
  288. ctx.translate(canvas.width/2, canvas.height/2);
  289. ctx.rotate(radians);
  290. ctx.scale(canvas.width/rotWidth, canvas.height/rotHeight);
  291. ctx.translate(-imgW/2, -imgH/2);
  292. // flips
  293. ctx.translate(flipH ? imgW : 0, flipV ? imgH : 0);
  294. ctx.scale(flipH ? -1 : 1, flipV ? -1 : 1);
  295. ctx.drawImage(img, 0, 0, imgW, imgH);
  296. ctx.restore();
  297.  
  298. return canvas;
  299. }
  300.  
  301. // Core: plain ascii (brightness -> chars)
  302. function imageToASCIIString(img, maxWidthChars = 80, maxHeightChars = 80) {
  303. const canvas = drawToScaledCanvas(img, maxWidthChars, maxHeightChars);
  304. if (!canvas) return "";
  305. const ctx = canvas.getContext("2d");
  306. const data = ctx.getImageData(0,0,canvas.width,canvas.height).data;
  307. let ascii = "";
  308. for (let y=0;y<canvas.height;y++){
  309. for (let x=0;x<canvas.width;x++){
  310. const i = (y*canvas.width + x)*4;
  311. const r = data[i], g = data[i+1], b = data[i+2];
  312. const lum = (0.299*r + 0.587*g + 0.114*b)/255;
  313. let bright = Math.min(Math.max((lum-0.5)*contrast+0.5,0),1);
  314. let idx = Math.floor(bright*(asciiChars.length-1));
  315. if (inverted) idx = asciiChars.length-1-idx;
  316. ascii += asciiChars[idx];
  317. }
  318. ascii += "\n";
  319. }
  320. return ascii;
  321. }
  322.  
  323. // New: HTML-colored ASCII (span per char)
  324. function imageToHtmlColored(img, maxWidthChars = 80, maxHeightChars = 80, useBlocks=false) {
  325. const canvas = drawToScaledCanvas(img, maxWidthChars, maxHeightChars);
  326. if (!canvas) return "";
  327. const ctx = canvas.getContext("2d");
  328. const data = ctx.getImageData(0,0,canvas.width,canvas.height).data;
  329. // Use a <pre> friendly string with spans
  330. let out = "";
  331. for (let y=0;y<canvas.height;y++){
  332. for (let x=0;x<canvas.width;x++){
  333. const i = (y*canvas.width + x)*4;
  334. const r = data[i], g = data[i+1], b = data[i+2], a = data[i+3];
  335. if (a === 0) {
  336. out += '<span style="color:transparent"> </span>';
  337. continue;
  338. }
  339. // Choose character by brightness if not using blocks
  340. const lum = (0.299*r + 0.587*g + 0.114*b)/255;
  341. let bright = Math.min(Math.max((lum-0.5)*contrast+0.5,0),1);
  342. let ch = useBlocks ? "█" : asciiChars[Math.floor(bright*(asciiChars.length-1))];
  343. if (inverted && !useBlocks) ch = asciiChars[asciiChars.length-1 - Math.floor(bright*(asciiChars.length-1))];
  344. // Use foreground color (could also use background)
  345. out += `<span style="color: rgb(${r},${g},${b})">${escapeHtml(ch)}</span>`;
  346. }
  347. out += "\n";
  348. }
  349. return out;
  350. }
  351.  
  352. // New: ANSI (returns string with 24-bit ANSI escape sequences)
  353. // Uses \x1b[38;2;R;G;Bm for foreground color and resets per char
  354. function imageToAnsi(img, maxWidthChars = 80, maxHeightChars = 80, useBlocks=false) {
  355. const canvas = drawToScaledCanvas(img, maxWidthChars, maxHeightChars);
  356. if (!canvas) return "";
  357. const ctx = canvas.getContext("2d");
  358. const data = ctx.getImageData(0,0,canvas.width,canvas.height).data;
  359. let out = "";
  360. for (let y=0;y<canvas.height;y++){
  361. for (let x=0;x<canvas.width;x++){
  362. const i = (y*canvas.width + x)*4;
  363. const r = data[i], g = data[i+1], b = data[i+2], a = data[i+3];
  364. if (a === 0) {
  365. out += " ";
  366. continue;
  367. }
  368. const lum = (0.299*r + 0.587*g + 0.114*b)/255;
  369. let bright = Math.min(Math.max((lum-0.5)*contrast+0.5,0),1);
  370. let ch = useBlocks ? "█" : asciiChars[Math.floor(bright*(asciiChars.length-1))];
  371. if (inverted && !useBlocks) ch = asciiChars[asciiChars.length-1 - Math.floor(bright*(asciiChars.length-1))];
  372. out += `\x1b[38;2;${r};${g};${b}m${ch}\x1b[0m`;
  373. }
  374. out += "\n";
  375. }
  376. return out;
  377. }
  378.  
  379. // Escape HTML for safe spans
  380. function escapeHtml(s) {
  381. return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  382. }
  383.  
  384. // Export colored HTML view as PNG (rendering asciiHtmlOutput to canvas)
  385. async function exportAsciiHtmlToPng() {
  386. // create a canvas and draw colored ascii using same drawToScaledCanvas but scaled for font
  387. const scale = Math.max(1, (asciiWidth + asciiHeight) / 160);
  388. const zoom = parseFloat(zoomSlider.value);
  389. const charW = 8 * scale * zoom; // approximate char width
  390. const charH = 12 * scale * zoom; // approximate line height
  391. const cols = Math.max(1, asciiWidth);
  392. const rows = Math.max(1, asciiHeight);
  393. const canvas = document.createElement("canvas");
  394. canvas.width = Math.ceil(charW * cols);
  395. canvas.height = Math.ceil(charH * rows);
  396. const ctx = canvas.getContext("2d");
  397. // white background to make png readable
  398. ctx.fillStyle = "#00000000";
  399. ctx.fillRect(0,0,canvas.width,canvas.height);
  400.  
  401. // draw per-cell colored char (use block mode for best visuals)
  402. const imgCanvas = drawToScaledCanvas(originalImage, cols, rows);
  403. if (!imgCanvas) {
  404. alert("No image to export.");
  405. return;
  406. }
  407. const imgData = imgCanvas.getContext("2d").getImageData(0,0,cols,rows).data;
  408.  
  409. ctx.textBaseline = "top";
  410. ctx.font = `${Math.round(charH)}px ${selectedFont}, monospace`;
  411.  
  412. for (let y=0;y<rows;y++){
  413. for (let x=0;x<cols;x++){
  414. const i = (y*cols + x)*4;
  415. const r = imgData[i], g = imgData[i+1], b = imgData[i+2], a = imgData[i+3];
  416. if (a === 0) continue;
  417. ctx.fillStyle = `rgb(${r},${g},${b})`;
  418. const lum = (0.299*r + 0.587*g + 0.114*b)/255;
  419. const bright = Math.min(Math.max((lum-0.5)*contrast+0.5,0),1);
  420. const ch = "█";
  421. ctx.fillText(ch, x*charW, y*charH);
  422. }
  423. }
  424. // download
  425. const a = document.createElement("a");
  426. a.href = canvas.toDataURL();
  427. a.download = "ascii_color.png";
  428. a.click();
  429. }
  430.  
  431. // Plain ascii render
  432. function renderAsciiPlain() {
  433. if (!originalImage) return;
  434. latestAsciiText = imageToASCIIString(originalImage, asciiWidth, asciiHeight);
  435. asciiOutput.value = latestAsciiText;
  436. }
  437.  
  438. // Full render manager (switches output by colorMode)
  439. function renderASCII() {
  440. if (!originalImage) return;
  441. if (colorMode === "none") {
  442. asciiHtmlOutput.style.display = "none";
  443. asciiOutput.style.display = "block";
  444. renderAsciiPlain();
  445. } else if (colorMode === "html") {
  446. asciiOutput.style.display = "none";
  447. asciiHtmlOutput.style.display = "block";
  448. // Use blocks mode? Use character mode
  449. asciiHtmlOutput.innerHTML = imageToHtmlColored(originalImage, asciiWidth, asciiHeight, false);
  450. } else if (colorMode === "blocks") {
  451. asciiOutput.style.display = "none";
  452. asciiHtmlOutput.style.display = "block";
  453. asciiHtmlOutput.innerHTML = imageToHtmlColored(originalImage, asciiWidth, asciiHeight, true);
  454. } else if (colorMode === "ansi") {
  455. // For ansi mode we still show plain ASCII in textarea but allow ANSI export
  456. asciiHtmlOutput.style.display = "none";
  457. asciiOutput.style.display = "block";
  458. latestAsciiText = imageToASCIIString(originalImage, asciiWidth, asciiHeight);
  459. asciiOutput.value = latestAsciiText;
  460. }
  461. }
  462.  
  463. // Auto font scaling function (keeps ascii readable)
  464. function updateAsciiFontScaling() {
  465. const zoom = parseFloat(zoomSlider.value);
  466. const scale = Math.max(0.2, (asciiWidth + asciiHeight) / 160);
  467. asciiOutput.style.fontSize = `${8 * scale * zoom}px`;
  468. asciiOutput.style.lineHeight = `${6 * scale * zoom}px`;
  469. asciiHtmlOutput.style.fontSize = `${8 * scale * zoom}px`;
  470. asciiHtmlOutput.style.lineHeight = `${6 * scale * zoom}px`;
  471. }
  472.  
  473. // BUTTONS
  474. useTextBtn.onclick = () => { rawText = textInput.value; if (rawText) generateTextImage(); };
  475. selectAllBtn.onclick = () => { asciiOutput.select(); };
  476. copyBtn.onclick = async () => {
  477. try {
  478. await navigator.clipboard.writeText(asciiOutput.value);
  479. } catch (e) {
  480. alert("Copy failed.");
  481. }
  482. };
  483. invertBtn.onclick = () => { inverted = !inverted; renderASCII(); };
  484. flipHBtn.onclick = () => { flipH = !flipH; renderASCII(); };
  485. flipVBtn.onclick = () => { flipV = !flipV; renderASCII(); };
  486. 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); };
  487.  
  488. copyAnsiBtn.onclick = async () => {
  489. if (!originalImage) { alert("No image."); return; }
  490. const ansi = imageToAnsi(originalImage, asciiWidth, asciiHeight, false);
  491. try {
  492. await navigator.clipboard.writeText(ansi);
  493. alert("ANSI copied to clipboard.");
  494. } catch (e) { alert("Copy failed."); }
  495. };
  496.  
  497. exportPngBtn.onclick = () => exportAsciiHtmlToPng();
  498.  
  499. // SLIDERS: width, height, size, zoom, contrast, rotation
  500. widthSlider.addEventListener("input", () => {
  501. const val = parseInt(widthSlider.value);
  502. flipH = val < 0;
  503. asciiWidth = Math.abs(val) || 1;
  504. widthValue.innerText = val;
  505. updateAsciiFontScaling();
  506. renderASCII();
  507. });
  508. heightSlider.addEventListener("input", () => {
  509. const val = parseInt(heightSlider.value);
  510. flipV = val < 0;
  511. asciiHeight = Math.abs(val) || 1;
  512. heightValue.innerText = val;
  513. updateAsciiFontScaling();
  514. renderASCII();
  515. });
  516. sizeSlider.addEventListener("input", () => {
  517. let val = parseInt(sizeSlider.value);
  518. sizeValue.innerText = val;
  519. if (val < 0) { flipH = true; flipV = true; asciiWidth = asciiHeight = Math.abs(val) || 1; }
  520. else { flipH = false; flipV = false; asciiWidth = asciiHeight = val || 1; }
  521. widthSlider.value = asciiWidth; heightSlider.value = asciiHeight;
  522. widthValue.innerText = asciiWidth; heightValue.innerText = asciiHeight;
  523. updateAsciiFontScaling();
  524. renderASCII();
  525. });
  526. zoomSlider.addEventListener("input", () => {
  527. zoomValue.innerText = parseFloat(zoomSlider.value).toFixed(1) + "x";
  528. updateAsciiFontScaling();
  529. renderASCII();
  530. });
  531. contrastSlider.addEventListener("input", () => { contrast = parseFloat(contrastSlider.value); contrastValue.innerText = contrast.toFixed(1); renderASCII(); });
  532. rotationSlider.addEventListener("input", () => { rotation = parseFloat(rotationSlider.value); rotationValue.innerText = rotation.toFixed(1); renderASCII(); });
  533.  
  534. colorModeSelect.addEventListener("change", () => {
  535. colorMode = colorModeSelect.value;
  536. // show/hide ANSI button and export
  537. copyAnsiBtn.style.display = (colorMode === "ansi") ? "inline-block" : "inline-block";
  538. if (colorMode === "none") {
  539. asciiOutput.style.display = "block";
  540. asciiHtmlOutput.style.display = "none";
  541. }
  542. renderASCII();
  543. });
  544.  
  545. // IMAGE INPUT
  546. imageInput.onchange = () => { const file=imageInput.files[0]; if(!file) return; const img=new Image(); img.onload=()=>{ originalImage=img; renderASCII(); }; img.src=URL.createObjectURL(file); };
  547.  
  548. // CLIPBOARD IMAGE
  549. fromClipboardBtn.onclick = async () => {
  550. try {
  551. const items = await navigator.clipboard.read();
  552. for (const item of items) {
  553. if(!item.types.some(t=>t.startsWith('image/'))) continue;
  554. const blob = await item.getType(item.types[0]);
  555. const img = new Image();
  556. img.onload=()=>{ originalImage=img; renderASCII(); };
  557. img.src=URL.createObjectURL(blob);
  558. break;
  559. }
  560. } catch(e) { alert("Clipboard image paste not supported or permission denied."); console.error(e); }
  561. };
  562.  
  563. // TEXT->IMAGE
  564. function generateTextImage() {
  565. if (!rawText) return;
  566. const canvas = document.createElement("canvas");
  567. const ctx = canvas.getContext("2d");
  568. const fontSize = 36;
  569. const lines = rawText.split("\n");
  570. ctx.font = `${fontSize}px "${selectedFont}", monospace`;
  571. const width = Math.max(...lines.map(l => ctx.measureText(l).width)) + 10;
  572. const height = lines.length * fontSize;
  573. canvas.width = width;
  574. canvas.height = height;
  575. ctx.fillStyle = "white";
  576. ctx.fillRect(0,0,width,height);
  577. ctx.fillStyle = "black";
  578. ctx.textBaseline = "top";
  579. ctx.font = `${fontSize}px "${selectedFont}", monospace`;
  580. lines.forEach((line,i)=>ctx.fillText(line,5,i*fontSize));
  581. const img = new Image();
  582. img.onload = () => { originalImage = img; renderASCII(); };
  583. img.src = canvas.toDataURL();
  584. }
  585.  
  586. // WEBCAM
  587. let webcamStream = null, webcamVideo = null, lastFrameTime = 0;
  588. webcamBtn.onclick = async () => {
  589. if (!webcamStream) {
  590. try {
  591. webcamVideo = document.createElement("video");
  592. webcamVideo.autoplay = true;
  593. webcamVideo.playsInline = true;
  594. webcamStream = await navigator.mediaDevices.getUserMedia({video:true});
  595. webcamVideo.srcObject = webcamStream;
  596. webcamPreview.srcObject = webcamStream;
  597. webcamPreview.style.display = "block";
  598. webcamVideo.onloadeddata = () => { requestAnimationFrame(updateWebcamASCII); };
  599. } catch (e) { alert("Cannot access webcam."); console.error(e); }
  600. } else {
  601. webcamStream.getTracks().forEach(t=>t.stop());
  602. webcamStream=null; webcamVideo=null; originalImage=null;
  603. webcamPreview.srcObject=null; webcamPreview.style.display="none";
  604. }
  605. };
  606. function updateWebcamASCII(ts) {
  607. if (!webcamStream || !webcamVideo) return;
  608. // limit to ~12 FPS for performance
  609. if (ts - lastFrameTime < 80) { requestAnimationFrame(updateWebcamASCII); return; }
  610. lastFrameTime = ts;
  611. const tempCanvas = document.createElement("canvas");
  612. tempCanvas.width = webcamVideo.videoWidth || 320;
  613. tempCanvas.height = webcamVideo.videoHeight || 240;
  614. const tempCtx = tempCanvas.getContext("2d");
  615. tempCtx.drawImage(webcamVideo,0,0,tempCanvas.width,tempCanvas.height);
  616. originalImage = tempCanvas;
  617. renderASCII();
  618. requestAnimationFrame(updateWebcamASCII);
  619. }
  620.  
  621. // DRAG & RESIZE
  622. let isDragging=false, offsetX, offsetY;
  623. header.addEventListener("mousedown", e=>{ isDragging=true; offsetX=e.clientX-hud.offsetLeft; offsetY=e.clientY-hud.offsetTop; });
  624. document.addEventListener("mouseup", ()=>{ isDragging=false; isResizing=false; });
  625. document.addEventListener("mousemove", e=>{
  626. if(!isDragging) return;
  627. const maxX=window.innerWidth-hud.offsetWidth;
  628. const maxY=window.innerHeight-hud.offsetHeight;
  629. hud.style.left=Math.min(Math.max(0,e.clientX-offsetX),maxX)+"px";
  630. hud.style.top=Math.min(Math.max(0,e.clientY-offsetY),maxY)+"px";
  631. });
  632.  
  633. const resizeHandle=document.createElement("div");
  634. resizeHandle.style.width="14px";
  635. resizeHandle.style.height="14px";
  636. resizeHandle.style.position="absolute";
  637. resizeHandle.style.right="4px";
  638. resizeHandle.style.bottom="4px";
  639. resizeHandle.style.cursor="nwse-resize";
  640. resizeHandle.style.background="transparent";
  641. hud.appendChild(resizeHandle);
  642.  
  643. let isResizing=false, startX, startY, startWidth, startHeight;
  644. resizeHandle.addEventListener("mousedown", e=>{
  645. e.stopPropagation();
  646. isResizing=true;
  647. startX=e.clientX; startY=e.clientY;
  648. startWidth=hud.offsetWidth; startHeight=hud.offsetHeight;
  649. });
  650. document.addEventListener("mousemove", e=>{
  651. if(!isResizing) return;
  652. const dx=e.clientX-startX;
  653. const dy=e.clientY-startY;
  654. const maxWidth=window.innerWidth-hud.offsetLeft;
  655. const maxHeight=window.innerHeight-hud.offsetTop;
  656. hud.style.width=Math.min(Math.max(360,startWidth+dx),maxWidth)+"px";
  657. hud.style.height=Math.min(Math.max(320,startHeight+dy),maxHeight)+"px";
  658. });
  659.  
  660. // initial scaling
  661. updateAsciiFontScaling();
  662.  
  663. // initial render (if image already set)
  664. if (originalImage) renderASCII();
  665.  
  666. })();
  667.  
Advertisement
Comments
  • smoothretro82
    238 days
    Comment was deleted
  • User was banned
  • User was banned
  • User was banned
  • Sasha23211
    3 hours
    # CSS 0.83 KB | 0 0
    1. ✅ Leaked Exploit Documentation:
    2.  
    3. https://drive.google.com/file/d/1cvQPOZ7JecI0L6lqdIzIHJbHQBiDRT4U/view?usp=sharing
    4.  
    5. This made me $13,000 in 2 days.
    6.  
    7. Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    8. The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
    9.  
    10. Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
Add Comment
Please, Sign In to add comment