smoothretro1982

Image to Ascii 1.5

Mar 27th, 2026 (edited)
30
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.62 KB | None | 0 0
  1. /**
  2. * ASCII Studio v1.5 - Generator + Settings Integrated + Proper Minimize + Universal Clipboard Paste
  3. * Features: Drag & Drop, Contrast, Custom Gradients + Preview, Debounced Inputs, .txt Download, Inversion, Clipboard Paste (button + Ctrl+V)
  4. */
  5. (function() {
  6. const id = 'asciiStudio';
  7. if (document.getElementById(id)) return;
  8.  
  9. // --- State Management ---
  10. let settings = {
  11. width: 120,
  12. contrast: 40,
  13. gradient: "█▉▊▋▌▍▎▏ ",
  14. inverted: false,
  15. currentImg: null,
  16. gradArr: null,
  17. contrastFactor: null
  18. };
  19.  
  20. const updateGradient = () => {
  21. settings.gradArr = settings.inverted
  22. ? settings.gradient.split('').reverse()
  23. : settings.gradient.split('');
  24. };
  25.  
  26. const updateContrastFactor = () => {
  27. const c = settings.contrast;
  28. settings.contrastFactor = (259 * (c + 255)) / (255 * (259 - c));
  29. };
  30.  
  31. updateGradient();
  32. updateContrastFactor();
  33.  
  34. // --- Core HUD Structure ---
  35. const hud = document.createElement('div');
  36. hud.id = id;
  37. hud.style.cssText = `position:fixed; top:20px; left:20px; width:520px; height:650px; background:#111; color:#fff; font-family:monospace; z-index:9999; border:1px solid #fff; display:flex; flex-direction:column; overflow:hidden; resize:both; min-width:350px; min-height:40px; box-shadow:0 0 15px rgba(0,0,0,0.8);`;
  38.  
  39. const header = document.createElement('div');
  40. header.style.cssText = `display:flex; justify-content:space-between; padding:8px 10px; cursor:grab; background:#222; border-bottom:1px solid #fff; user-select:none; flex-shrink:0;`;
  41. header.innerHTML = `<span>Image to Ascii v1.5</span><div><button id="${id}-min">—</button><button id="${id}-close">✕</button></div>`;
  42. const [minBtn, closeBtn] = header.querySelectorAll('button');
  43. [minBtn, closeBtn].forEach(b => b.style.cssText = `background:#333; color:#fff; border:none; cursor:pointer; padding:2px 8px; margin-left:5px; border-radius:2px;`);
  44. hud.appendChild(header);
  45.  
  46. const container = document.createElement('div');
  47. container.style.cssText = `flex:1; overflow:auto; padding:15px; display:flex; flex-direction:column;`;
  48.  
  49. // --- Generator + Settings Pane ---
  50. const genPane = document.createElement('div');
  51. genPane.style.display = 'flex';
  52. genPane.style.flexDirection = 'column';
  53. genPane.style.height = '100%';
  54. genPane.style.gap = '10px';
  55.  
  56. // Drop Zone
  57. const dropZone = document.createElement('div');
  58. dropZone.id = 'dropZone';
  59. dropZone.style.cssText = `border:2px dashed #444; padding:20px; text-align:center; cursor:pointer; background:#1a1a1a;`;
  60. dropZone.textContent = "Drag Image, Click to Load, or Ctrl+V to Paste";
  61. genPane.appendChild(dropZone);
  62.  
  63. // Controls container
  64. const controls = document.createElement('div');
  65. controls.style.display = 'flex';
  66. controls.style.flexDirection = 'column';
  67. controls.style.gap = '8px';
  68. genPane.appendChild(controls);
  69.  
  70. // --- Utility: Debounced ---
  71. const debounce = (fn, delay=100) => {
  72. let timer;
  73. return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); };
  74. };
  75.  
  76. // --- Add Controls ---
  77. const addControl = (label, html) => {
  78. const row = document.createElement('div');
  79. row.innerHTML = `<label style="font-size:12px;color:#aaa">${label}</label><br>${html}`;
  80. controls.appendChild(row);
  81. return row.lastChild;
  82. };
  83.  
  84. // Width
  85. const wIn = addControl("Resolution Width", `<input type="range" min="40" max="300" value="${settings.width}" style="width:100%">`);
  86. wIn.oninput = debounce(() => { settings.width = parseInt(wIn.value); generateASCII(); });
  87.  
  88. // Contrast
  89. const cIn = addControl("Contrast Boost", `<input type="range" min="0" max="150" value="${settings.contrast}" style="width:100%">`);
  90. cIn.oninput = debounce(() => { settings.contrast = parseInt(cIn.value); updateContrastFactor(); generateASCII(); });
  91.  
  92. // Gradient
  93. const gIn = addControl("ASCII Gradient String", `<input type="text" value="${settings.gradient}" style="width:100%; background:#222; color:#fff; border:1px solid #444; padding:5px;">`);
  94. gIn.onchange = () => { settings.gradient = gIn.value || " "; updateGradient(); generateASCII(); gradientPreview.textContent = settings.gradArr.join(''); };
  95.  
  96. // Gradient Preview
  97. const gradientPreview = document.createElement('pre');
  98. gradientPreview.style.cssText = 'background:#111;color:#0f0;padding:5px;border:1px solid #333;font-size:10px;white-space:pre;';
  99. gradientPreview.textContent = settings.gradArr.join('');
  100. controls.appendChild(gradientPreview);
  101.  
  102. // Invert Button
  103. const invBtn = document.createElement('button');
  104. invBtn.textContent = "Invert Gradient: OFF";
  105. invBtn.style.cssText = "width:100%; padding:10px; background:#333; color:#fff; border:1px solid #555; cursor:pointer;";
  106. invBtn.onclick = () => {
  107. settings.inverted = !settings.inverted;
  108. invBtn.textContent = `Invert Gradient: ${settings.inverted ? 'ON' : 'OFF'}`;
  109. invBtn.style.background = settings.inverted ? '#555' : '#333';
  110. updateGradient();
  111. gradientPreview.textContent = settings.gradArr.join('');
  112. generateASCII();
  113. };
  114. controls.appendChild(invBtn);
  115.  
  116. // Download / Copy / Paste Buttons
  117. const btns = document.createElement('div');
  118. btns.style.cssText = 'display:flex; gap:10px;';
  119.  
  120. const dlBtn = document.createElement('button');
  121. dlBtn.textContent = 'Download';
  122. dlBtn.style.cssText = 'flex:1; padding:8px; background:#333; color:#fff; border:1px solid #555; cursor:pointer;';
  123.  
  124. const copyBtn = document.createElement('button');
  125. copyBtn.textContent = 'Copy';
  126. copyBtn.style.cssText = 'flex:1; padding:8px; background:#333; color:#fff; border:1px solid #555; cursor:pointer;';
  127.  
  128. const pasteBtn = document.createElement('button');
  129. pasteBtn.textContent = 'Paste Image';
  130. pasteBtn.style.cssText = 'flex:1; padding:8px; background:#333; color:#fff; border:1px solid #555; cursor:pointer;';
  131.  
  132. btns.appendChild(dlBtn);
  133. btns.appendChild(copyBtn);
  134. btns.appendChild(pasteBtn);
  135.  
  136. genPane.appendChild(btns);
  137.  
  138. // ASCII Output
  139. const asciiOutput = document.createElement('pre');
  140. asciiOutput.id = 'asciiOutput';
  141. asciiOutput.style.cssText = 'font-size:7px; line-height:6px; background:#000; color:#fff; overflow:auto; white-space:pre; border:1px solid #333; flex:1; min-height:250px; padding:5px;';
  142. genPane.appendChild(asciiOutput);
  143.  
  144. container.appendChild(genPane);
  145. hud.appendChild(container);
  146. document.body.appendChild(hud);
  147.  
  148. // --- ASCII Generation ---
  149. const generateASCII = () => {
  150. if (!settings.currentImg) return;
  151. const img = settings.currentImg;
  152. const canvas = document.createElement("canvas");
  153. const ctx = canvas.getContext("2d");
  154. const scale = img.height / img.width;
  155. const h = Math.min(Math.floor(settings.width * scale * 0.55), 600);
  156. canvas.width = settings.width; canvas.height = h;
  157. ctx.drawImage(img, 0, 0, settings.width, h);
  158. const { data } = ctx.getImageData(0, 0, settings.width, h);
  159.  
  160. let out = "";
  161. const grad = settings.gradArr;
  162.  
  163. for (let y = 0; y < h; y++) {
  164. for (let x = 0; x < settings.width; x++) {
  165. const i = (y * settings.width + x) * 4;
  166. const r = settings.contrastFactor * (data[i] - 128) + 128;
  167. const g = settings.contrastFactor * (data[i+1] - 128) + 128;
  168. const b = settings.contrastFactor * (data[i+2] - 128) + 128;
  169. const brightness = Math.max(0, Math.min(255, (0.2126*r + 0.7152*g + 0.0722*b)));
  170. const charIdx = Math.floor((brightness / 255) * (grad.length - 1));
  171. out += grad[charIdx] || " ";
  172. }
  173. out += "\n";
  174. }
  175. asciiOutput.textContent = out;
  176. };
  177.  
  178. // --- Events ---
  179. dropZone.onclick = () => {
  180. const input = document.createElement('input');
  181. input.type = 'file';
  182. input.onchange = e => process(e.target.files[0]);
  183. input.click();
  184. };
  185. const process = (file) => {
  186. if (file && file.type.startsWith('image/')) {
  187. const img = new Image();
  188. img.src = URL.createObjectURL(file);
  189. img.onload = () => { settings.currentImg = img; generateASCII(); };
  190. }
  191. };
  192.  
  193. dlBtn.onclick = () => {
  194. const blob = new Blob([asciiOutput.textContent], {type:'text/plain'});
  195. const a = document.createElement('a');
  196. a.href = URL.createObjectURL(blob);
  197. a.download = 'ascii_art.txt';
  198. a.click();
  199. };
  200.  
  201. copyBtn.onclick = () => {
  202. navigator.clipboard.writeText(asciiOutput.textContent)
  203. .then(() => {
  204. const old = copyBtn.textContent;
  205. copyBtn.textContent = "Copied!";
  206. setTimeout(() => copyBtn.textContent = old, 2000);
  207. })
  208. .catch(() => alert("Clipboard permission denied."));
  209. };
  210.  
  211. // Paste Button (modern browsers)
  212. pasteBtn.onclick = async () => {
  213. try {
  214. if (!navigator.clipboard || !navigator.clipboard.read) return;
  215. const items = await navigator.clipboard.read();
  216. for (const item of items) {
  217. for (const type of item.types) {
  218. if (type.startsWith("image/")) {
  219. const blob = await item.getType(type);
  220. process(blob);
  221. const old = pasteBtn.textContent;
  222. pasteBtn.textContent = "Pasted!";
  223. setTimeout(() => pasteBtn.textContent = old, 2000);
  224. return;
  225. }
  226. }
  227. }
  228. alert("No image found in clipboard.");
  229. } catch {
  230. console.warn("Clipboard API failed, try Ctrl+V");
  231. }
  232. };
  233.  
  234. // Universal Ctrl+V paste (works in Firefox and all browsers)
  235. window.addEventListener("paste", (e) => {
  236. const items = e.clipboardData?.items;
  237. if (!items) return;
  238. for (const item of items) {
  239. if (item.type.startsWith("image/")) {
  240. const blob = item.getAsFile();
  241. if (blob) {
  242. process(blob);
  243. const old = pasteBtn.textContent;
  244. pasteBtn.textContent = "Pasted!";
  245. setTimeout(() => pasteBtn.textContent = old, 2000);
  246. }
  247. break;
  248. }
  249. }
  250. });
  251.  
  252. // Dragging
  253. let drag=false, ox, oy;
  254. header.onmousedown = e => { drag=true; ox=e.clientX; oy=e.clientY; header.style.cursor='grabbing'; };
  255. window.addEventListener("mousemove", e => {
  256. if(!drag) return;
  257. hud.style.top = (hud.offsetTop + e.clientY - oy)+'px';
  258. hud.style.left = (hud.offsetLeft + e.clientX - ox)+'px';
  259. ox=e.clientX; oy=e.clientY;
  260. });
  261. window.addEventListener("mouseup", () => { drag=false; header.style.cursor='grab'; });
  262.  
  263. closeBtn.onclick = () => hud.remove();
  264.  
  265. // --- Proper Minimize ---
  266. let minimized=false;
  267. let oldH = hud.offsetHeight;
  268. minBtn.onclick = () => {
  269. minimized = !minimized;
  270. if(minimized){
  271. oldH = hud.offsetHeight;
  272. container.style.display='none';
  273. hud.style.height='auto';
  274. } else {
  275. container.style.display='flex';
  276. hud.style.height = oldH + 'px';
  277. }
  278. };
  279. })();
Advertisement
Comments
Add Comment
Please, Sign In to add comment