smoothretro82

Webcam Ascii HUD

Dec 2nd, 2025 (edited)
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.32 KB | None | 0 0
  1. (() => {
  2. // Remove existing HUD if any
  3. const existing = document.getElementById('webcamASCIIHUD');
  4. if (existing) existing.remove();
  5.  
  6. // Create HUD container
  7. const hud = document.createElement('div');
  8. hud.id = 'webcamASCIIHUD';
  9. Object.assign(hud.style, {
  10. position: 'fixed',
  11. top: '20px',
  12. left: '20px',
  13. width: '90%',
  14. maxWidth: '480px',
  15. height: 'auto',
  16. background: 'rgba(34,34,34,0.95)',
  17. borderRadius: '12px',
  18. padding: '15px',
  19. zIndex: 99999,
  20. fontFamily: 'Arial, sans-serif',
  21. fontSize: '12px',
  22. color: '#fff',
  23. boxShadow: '0 8px 25px rgba(0,0,0,0.4)',
  24. cursor: 'move',
  25. backdropFilter: 'blur(10px)',
  26. border: '2px solid #666',
  27. boxSizing: 'border-box',
  28. overflow: 'hidden',
  29. minWidth: '200px',
  30. minHeight: '150px'
  31. });
  32.  
  33. // Close button
  34. const closeBtn = document.createElement('button');
  35. closeBtn.innerHTML = '×';
  36. Object.assign(closeBtn.style, {
  37. position: 'absolute',
  38. top: '8px',
  39. right: '8px',
  40. background: 'rgba(220,53,69,0.8)',
  41. color: '#fff',
  42. border: 'none',
  43. borderRadius: '50%',
  44. width: '20px',
  45. height: '20px',
  46. cursor: 'pointer',
  47. fontSize: '14px',
  48. display: 'flex',
  49. alignItems: 'center',
  50. justifyContent: 'center',
  51. });
  52. closeBtn.onclick = () => hud.remove();
  53. hud.appendChild(closeBtn);
  54.  
  55. // Title
  56. const title = document.createElement('div');
  57. title.innerText = '🎥 Webcam ASCII Controller';
  58. Object.assign(title.style, { fontWeight: 'bold', fontSize: '14px', marginBottom: '12px' });
  59. hud.appendChild(title);
  60.  
  61. // Controls container
  62. const controlsContainer = document.createElement('div');
  63. Object.assign(controlsContainer.style, { display: 'flex', flexDirection: 'column', gap: '10px', boxSizing: 'border-box' });
  64. hud.appendChild(controlsContainer);
  65.  
  66. // Camera selection
  67. const camSelectDiv = document.createElement('div');
  68. camSelectDiv.innerHTML = '<strong>Camera:</strong> ';
  69. const camSelect = document.createElement('select');
  70. Object.assign(camSelect.style, { background: '#333', color: '#fff', border: '1px solid #555', padding: '3px', borderRadius: '4px', marginLeft: '5px', width: '120px' });
  71. camSelectDiv.appendChild(camSelect);
  72. controlsContainer.appendChild(camSelectDiv);
  73.  
  74. // Populate cameras
  75. async function populateCameras() {
  76. try {
  77. const devices = await navigator.mediaDevices.enumerateDevices();
  78. const videoDevices = devices.filter(d => d.kind === 'videoinput');
  79. camSelect.innerHTML = '<option value="">Select Camera</option>';
  80. videoDevices.forEach((device, i) => {
  81. const option = document.createElement('option');
  82. option.value = device.deviceId;
  83. option.textContent = device.label || `Camera ${i + 1}`;
  84. camSelect.appendChild(option);
  85. });
  86. } catch (err) {
  87. console.log('Cannot enumerate devices:', err);
  88. }
  89. }
  90.  
  91. // Resolution
  92. const resolutionDiv = document.createElement('div');
  93. resolutionDiv.innerHTML = '<strong>Resolution:</strong> ';
  94. const resolutionSelect = document.createElement('select');
  95. Object.assign(resolutionSelect.style, { background: '#333', color: '#fff', border: '1px solid #555', padding: '3px', borderRadius: '4px', marginLeft: '5px', width: '120px' });
  96. const resolutions = [
  97. { width: 160, height: 120, name: 'Low (160x120)' },
  98. { width: 320, height: 240, name: 'Medium (320x240)' },
  99. { width: 640, height: 480, name: 'High (640x480)' },
  100. { width: 1280, height: 720, name: 'HD (1280x720)' },
  101. { width: 1920, height: 1080, name: 'Full HD (1920x1080)' }
  102. ];
  103. resolutions.forEach(res => {
  104. const option = document.createElement('option');
  105. option.value = JSON.stringify(res);
  106. option.textContent = res.name;
  107. resolutionSelect.appendChild(option);
  108. });
  109. resolutionDiv.appendChild(resolutionSelect);
  110. controlsContainer.appendChild(resolutionDiv);
  111.  
  112. // Quality
  113. const qualityDiv = document.createElement('div');
  114. qualityDiv.innerHTML = '<strong>Quality:</strong> ';
  115. const qualitySelect = document.createElement('select');
  116. Object.assign(qualitySelect.style, { background: '#333', color: '#fff', border: '1px solid #555', padding: '3px', borderRadius: '4px', marginLeft: '5px', width: '120px' });
  117. ['low','medium','high'].forEach(q => {
  118. const option = document.createElement('option');
  119. option.value = q;
  120. option.textContent = q.charAt(0).toUpperCase() + q.slice(1);
  121. qualitySelect.appendChild(option);
  122. });
  123. qualityDiv.appendChild(qualitySelect);
  124. controlsContainer.appendChild(qualityDiv);
  125.  
  126. // Frame rate
  127. const frameRateDiv = document.createElement('div');
  128. frameRateDiv.innerHTML = '<strong>Frame Rate:</strong> <input type="range" id="frameRateSlider" min="1" max="30" value="10" style="width:100px"> <span id="frameRateValue">10</span> FPS';
  129. controlsContainer.appendChild(frameRateDiv);
  130.  
  131. // Brightness, Contrast, Saturation
  132. const processingDiv = document.createElement('div');
  133. Object.assign(processingDiv.style, { display: 'flex', gap: '10px', flexWrap: 'wrap' });
  134. ['Brightness','Contrast','Saturation'].forEach(label=>{
  135. const div = document.createElement('div');
  136. const id = label.toLowerCase()+'Slider';
  137. div.innerHTML = `<div style="font-size:10px; opacity:0.8;">${label}</div><input type="range" id="${id}" min="-100" max="100" value="0" style="width:100px"> <span id="${id}Value">0</span>`;
  138. processingDiv.appendChild(div);
  139. });
  140. controlsContainer.appendChild(processingDiv);
  141.  
  142. // Action buttons
  143. const actionButtonsDiv = document.createElement('div');
  144. Object.assign(actionButtonsDiv.style, { display: 'flex', gap: '8px', flexWrap:'wrap', marginTop:'8px' });
  145.  
  146. const startBtn = document.createElement('button');
  147. startBtn.innerText = '▶ Start Webcam';
  148. Object.assign(startBtn.style, { flex: '1', padding:'6px', background: 'linear-gradient(45deg,#28a745,#20c997)', color:'#fff', border:'none', borderRadius:'6px', cursor:'pointer', fontSize:'11px' });
  149.  
  150. const stopBtn = document.createElement('button');
  151. stopBtn.innerText = '⏹ Stop';
  152. Object.assign(stopBtn.style, { flex: '1', padding:'6px', background: 'linear-gradient(45deg,#dc3545,#e83e8c)', color:'#fff', border:'none', borderRadius:'6px', cursor:'pointer', fontSize:'11px' });
  153.  
  154. const snapshotBtn = document.createElement('button');
  155. snapshotBtn.innerText = '📸 Snapshot';
  156. Object.assign(snapshotBtn.style, { flex: '1', padding:'6px', background: 'linear-gradient(45deg,#fd7e14,#ffc107)', color:'#fff', border:'none', borderRadius:'6px', cursor:'pointer', fontSize:'11px' });
  157.  
  158. const invertBtn = document.createElement('button');
  159. invertBtn.innerText = '🎨 Invert: Off';
  160. Object.assign(invertBtn.style, { flex: '1', padding:'6px', background: 'linear-gradient(45deg,#6f42c1,#6610f2)', color:'#fff', border:'none', borderRadius:'6px', cursor:'pointer', fontSize:'11px' });
  161.  
  162. const mirrorBtn = document.createElement('button');
  163. mirrorBtn.innerText = 'Flip: None';
  164. Object.assign(mirrorBtn.style, { flex: '1', padding:'6px', background: 'linear-gradient(45deg,#17a2b8,#20c997)', color:'#fff', border:'none', borderRadius:'6px', cursor:'pointer', fontSize:'11px' });
  165.  
  166. const brailleBtn = document.createElement('button');
  167. brailleBtn.innerText = 'Braille: Off';
  168. Object.assign(brailleBtn.style, { flex: '1', padding:'6px', background: 'linear-gradient(45deg,#ff6f61,#ff9a76)', color:'#fff', border:'none', borderRadius:'6px', cursor:'pointer', fontSize:'11px' });
  169.  
  170. // === New Select All button ===
  171. const selectAllBtn = document.createElement('button');
  172. selectAllBtn.innerText = '🔲 Select All';
  173. Object.assign(selectAllBtn.style, { flex: '1', padding:'6px', background: 'linear-gradient(45deg,#0d6efd,#6610f2)', color:'#fff', border:'none', borderRadius:'6px', cursor:'pointer', fontSize:'11px' });
  174.  
  175. actionButtonsDiv.append(startBtn, stopBtn, snapshotBtn, invertBtn, mirrorBtn, brailleBtn, selectAllBtn);
  176. controlsContainer.appendChild(actionButtonsDiv);
  177.  
  178. // Status display
  179. const statusDiv = document.createElement('div');
  180. statusDiv.id = 'webcamStatus';
  181. statusDiv.innerText = 'Ready - Select camera and resolution';
  182. Object.assign(statusDiv.style, { marginTop:'10px', padding:'6px', background:'rgba(50,50,80,0.3)', borderRadius:'4px', fontSize:'10px', color:'#4CAF50' });
  183. controlsContainer.appendChild(statusDiv);
  184.  
  185. // Preview
  186. const pre = document.createElement('pre');
  187. Object.assign(pre.style, {
  188. background:'#111',
  189. color:'#ccc',
  190. fontSize:'8px',
  191. lineHeight:'7px',
  192. padding:'8px',
  193. borderRadius:'6px',
  194. marginTop:'12px',
  195. overflow:'auto',
  196. maxHeight:'300px',
  197. width:'100%',
  198. border:'1px solid #333',
  199. whiteSpace:'pre-wrap',
  200. wordBreak:'break-word'
  201. });
  202. hud.appendChild(pre);
  203. document.body.appendChild(hud);
  204.  
  205. // === Select All functionality ===
  206. selectAllBtn.addEventListener('click', () => {
  207. const range = document.createRange();
  208. range.selectNodeContents(pre);
  209. const selection = window.getSelection();
  210. selection.removeAllRanges();
  211. selection.addRange(range);
  212. statusDiv.innerText = 'ASCII selected — press Ctrl+C to copy';
  213. statusDiv.style.color = '#4CAF50';
  214. });
  215.  
  216. // Resize handle
  217. const resizeHandle = document.createElement('div');
  218. Object.assign(resizeHandle.style, {
  219. width: '12px',
  220. height: '12px',
  221. position: 'absolute',
  222. bottom: '4px',
  223. right: '4px',
  224. cursor: 'nwse-resize',
  225. background: 'rgba(255,255,255,0.3)',
  226. borderRadius: '2px',
  227. });
  228. hud.appendChild(resizeHandle);
  229.  
  230. // State variables
  231. let currentStream = null;
  232. let currentVideo = null;
  233. let canvas = document.createElement('canvas');
  234. let selectedResolutionGlobal = { width: 320, height: 240 };
  235. let animationRunning = false;
  236. let invertASCII = false;
  237. let mirrorMode = 'none'; // none | horizontal | vertical
  238. let brailleMode = false;
  239.  
  240. // Invert toggle
  241. invertBtn.addEventListener('click', () => { invertASCII = !invertASCII; invertBtn.innerText = `🎨 Invert: ${invertASCII ? 'On':'Off'}`; });
  242.  
  243. // Mirror toggle
  244. mirrorBtn.addEventListener('click', () => {
  245. if(mirrorMode==='none') mirrorMode='horizontal';
  246. else if(mirrorMode==='horizontal') mirrorMode='vertical';
  247. else mirrorMode='none';
  248. mirrorBtn.innerText = `Flip: ${mirrorMode.charAt(0).toUpperCase()+mirrorMode.slice(1)}`;
  249. });
  250.  
  251. // Braille toggle
  252. brailleBtn.addEventListener('click', () => { brailleMode=!brailleMode; brailleBtn.innerText=`Braille: ${brailleMode?'On':'Off'}`; });
  253.  
  254. // Slider displays
  255. const updateSliderDisplay = (sliderId, valueId) => {
  256. const slider = document.getElementById(sliderId);
  257. const valueDisplay = document.getElementById(valueId);
  258. if(slider && valueDisplay){
  259. slider.addEventListener('input', ()=>{ valueDisplay.textContent=slider.value; });
  260. }
  261. };
  262. ['frameRateSlider','brightnessSlider','contrastSlider','saturationSlider'].forEach(id=>{
  263. const valueId = id==='frameRateSlider'?'frameRateValue':id+'Value';
  264. updateSliderDisplay(id,valueId);
  265. });
  266.  
  267. // Start webcam
  268. async function startWebcam(){
  269. try{
  270. statusDiv.innerText='Starting webcam...'; statusDiv.style.color='#FF9800';
  271. selectedResolutionGlobal = JSON.parse(resolutionSelect.value);
  272. const constraints = {
  273. video:{
  274. deviceId: camSelect.value ? { exact: camSelect.value } : true,
  275. width: { ideal: selectedResolutionGlobal.width },
  276. height: { ideal: selectedResolutionGlobal.height }
  277. }
  278. };
  279.  
  280. currentStream = await navigator.mediaDevices.getUserMedia(constraints);
  281. currentVideo = document.createElement('video');
  282. currentVideo.srcObject = currentStream;
  283. currentVideo.autoplay=true;
  284. currentVideo.playsInline=true;
  285. currentVideo.muted=true;
  286. currentVideo.style.display='none';
  287. document.body.appendChild(currentVideo);
  288.  
  289. currentVideo.addEventListener('loadeddata', async () => {
  290. await currentVideo.play();
  291. statusDiv.innerText = `Webcam active - ${selectedResolutionGlobal.width}x${selectedResolutionGlobal.height}`;
  292. statusDiv.style.color = '#4CAF50';
  293. if(!animationRunning){
  294. animationRunning = true;
  295. const loop = () => {
  296. if(currentVideo) {
  297. processFrame(currentVideo);
  298. const fps = parseInt(document.getElementById('frameRateSlider').value);
  299. setTimeout(()=>requestAnimationFrame(loop),1000/fps);
  300. } else { animationRunning=false; }
  301. };
  302. loop();
  303. }
  304. });
  305.  
  306. startBtn.disabled=true; stopBtn.disabled=false; snapshotBtn.disabled=false;
  307.  
  308. }catch(e){
  309. console.error('Webcam error:',e);
  310. statusDiv.innerText=`Error: ${e.message}`; statusDiv.style.color='#f44336';
  311. }
  312. }
  313.  
  314. // Stop webcam
  315. function stopWebcam(){
  316. if(currentStream) currentStream.getTracks().forEach(t=>t.stop());
  317. if(currentVideo){ currentVideo.pause(); currentVideo.srcObject=null; currentVideo.remove(); currentVideo=null; }
  318. startBtn.disabled=false; stopBtn.disabled=true; snapshotBtn.disabled=true;
  319. statusDiv.innerText='Webcam stopped'; statusDiv.style.color='#aaa';
  320. }
  321.  
  322. // Snapshot
  323. function takeSnapshot(){ if(currentVideo) processFrame(currentVideo); }
  324.  
  325. // Frame processing
  326. function processFrame(videoEl){
  327. const hudWidth = hud.clientWidth - 16;
  328. const hudHeight = hud.clientHeight - pre.offsetTop - 16;
  329. const charWidth = 6, charHeight = 7;
  330. let asciiCols = Math.floor(hudWidth / charWidth);
  331. let asciiRows = Math.floor(hudHeight / charHeight);
  332.  
  333. let width = asciiCols;
  334. let height = asciiRows;
  335. if(brailleMode){ width*=2; height*=4; }
  336.  
  337. canvas.width=width; canvas.height=height;
  338. const ctx=canvas.getContext('2d');
  339.  
  340. try{
  341. ctx.save();
  342. if(mirrorMode==='horizontal'){ ctx.translate(canvas.width,0); ctx.scale(-1,1); }
  343. else if(mirrorMode==='vertical'){ ctx.translate(0,canvas.height); ctx.scale(1,-1); }
  344. ctx.drawImage(videoEl,0,0,width,height);
  345. ctx.restore();
  346.  
  347. const data=ctx.getImageData(0,0,width,height).data;
  348. let output='';
  349.  
  350. const brightness=parseInt(document.getElementById('brightnessSlider').value);
  351. const contrast=parseInt(document.getElementById('contrastSlider').value);
  352. const saturation=parseInt(document.getElementById('saturationSlider').value);
  353.  
  354. if(brailleMode){
  355. for(let y=0;y<height;y+=4){
  356. for(let x=0;x<width;x+=2){
  357. let brailleChar=0x2800;
  358. const dotMap=[[0,3],[1,4],[2,5],[6,7]];
  359. for(let py=0;py<4;py++){
  360. for(let px=0;px<2;px++){
  361. const xi=x+px, yi=y+py;
  362. if(xi>=width||yi>=height) continue;
  363. const i=(yi*width+xi)*4;
  364. let r=data[i],g=data[i+1],b=data[i+2];
  365. if(brightness!==0){ const f=1+brightness/100; r=Math.min(255,Math.max(0,r*f)); g=Math.min(255,Math.max(0,g*f)); b=Math.min(255,Math.max(0,b*f)); }
  366. if(contrast!==0){ const f=(259*(contrast+255))/(255*(259-contrast)); r=Math.min(255,Math.max(0,f*(r-128)+128)); g=Math.min(255,Math.max(0,f*(g-128)+128)); b=Math.min(255,Math.max(0,f*(b-128)+128)); }
  367. if(saturation!==0){ const gray=0.299*r+0.587*g+0.114*b; const f=1+saturation/100; r=Math.min(255,Math.max(0,gray+(r-gray)*f)); g=Math.min(255,Math.max(0,gray+(g-gray)*f)); b=Math.min(255,Math.max(0,gray+(b-gray)*f)); }
  368. const lum=(0.299*r+0.587*g+0.114*b)/255;
  369. if(lum>0.5){ brailleChar|=1<<dotMap[py][px]; }
  370. }
  371. }
  372. output+=String.fromCharCode(brailleChar);
  373. }
  374. output+='\n';
  375. }
  376. } else {
  377. const chars = ['@','W','M','#','%','&','8','B','D','H','Q','O','0','9','5','3','2','1','|','!',';','^','~',':',',','.',' '];
  378. for(let y=0;y<height;y++){
  379. for(let x=0;x<width;x++){
  380. const i=(y*width+x)*4;
  381. let r=data[i],g=data[i+1],b=data[i+2];
  382. if(brightness!==0){ const f=1+brightness/100; r=Math.min(255,Math.max(0,r*f)); g=Math.min(255,Math.max(0,g*f)); b=Math.min(255,Math.max(0,b*f)); }
  383. if(contrast!==0){ const f=(259*(contrast+255))/(255*(259-contrast)); r=Math.min(255,Math.max(0,f*(r-128)+128)); g=Math.min(255,Math.max(0,f*(g-128)+128)); b=Math.min(255,Math.max(0,f*(b-128)+128)); }
  384. if(saturation!==0){ const gray=0.299*r+0.587*g+0.114*b; const f=1+saturation/100; r=Math.min(255,Math.max(0,gray+(r-gray)*f)); g=Math.min(255,Math.max(0,gray+(g-gray)*f)); b=Math.min(255,Math.max(0,gray+(b-gray)*f)); }
  385. const lum=(0.299*r+0.587*g+0.114*b)/255;
  386. const charIndex=Math.floor(lum*(chars.length-1));
  387. output+=invertASCII ? chars[chars.length-1-charIndex] : chars[charIndex];
  388. }
  389. output+='\n';
  390. }
  391. }
  392.  
  393. pre.innerText=output;
  394. }catch(e){ console.log('Frame processing error:',e); }
  395. }
  396.  
  397. // Event listeners
  398. startBtn.addEventListener('click', startWebcam);
  399. stopBtn.addEventListener('click', stopWebcam);
  400. snapshotBtn.addEventListener('click', takeSnapshot);
  401.  
  402. // Populate cameras
  403. populateCameras();
  404.  
  405. // Dragging
  406. let isDragging=false, offsetX=0, offsetY=0;
  407. hud.addEventListener('mousedown',e=>{
  408. if(['BUTTON','INPUT','SELECT'].includes(e.target.tagName)) return;
  409. isDragging=true;
  410. offsetX=e.clientX-hud.offsetLeft; offsetY=e.clientY-hud.offsetTop;
  411. hud.style.cursor='grabbing';
  412. });
  413. document.addEventListener('mousemove',e=>{ if(!isDragging) return; hud.style.left=(e.clientX-offsetX)+'px'; hud.style.top=(e.clientY-offsetY)+'px'; });
  414. document.addEventListener('mouseup',()=>{ isDragging=false; hud.style.cursor='move'; });
  415.  
  416. // Resizing
  417. let isResizing=false, resizeStartX=0, resizeStartY=0, startWidth=0, startHeight=0;
  418. resizeHandle.addEventListener('mousedown',e=>{
  419. e.stopPropagation(); isResizing=true; resizeStartX=e.clientX; resizeStartY=e.clientY;
  420. startWidth=hud.offsetWidth; startHeight=hud.offsetHeight;
  421. });
  422. document.addEventListener('mousemove',e=>{
  423. if(!isResizing) return;
  424. hud.style.width=startWidth+(e.clientX-resizeStartX)+'px';
  425. hud.style.height=startHeight+(e.clientY-resizeStartY)+'px';
  426. });
  427. document.addEventListener('mouseup',()=>{ isResizing=false; });
  428.  
  429. console.log('🎥 Webcam ASCII Controller loaded with Braille mode and Select All button!');
  430. })();
  431.  
Advertisement
Add Comment
Please, Sign In to add comment