<html><head><base href="https://www.example.com/advanced-image-preprocessing-ascii-converter/"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Advanced Image Preprocessing and ASCII Art Converter</title> <style> :root { --fluid-5-20: clamp(0.3125rem, 0.2731rem + 1.2605vw, 1.25rem); --fluid-8-20: clamp(0.5rem, 0.4685rem + 1.0084vw, 1.25rem); --fluid-5-9: clamp(0.3125rem, 0.302rem + 0.3361vw, 0.5625rem); --fluid-2-5: clamp(0.125rem, 0.1168rem + 0.2609vw, 0.3125rem); } @font-face { font-family: 'jgs9'; src: url('https://websim.ai/fonts/jgs9.woff2') format('woff2'); font-weight: normal; font-style: normal; } body { font-family: 'Roboto', Arial, sans-serif; margin: 0; padding: 20px; background-color: #040404; color: #fdfdfd; } .container { max-width: 1200px; margin: 0 auto; background-color: #111; padding: 30px; border-radius: 8px; box-shadow: 0 4px 6px rgba(255,255,255,0.1); } h1, h2 { text-align: center; color: #fdfdfd; font-family: "jgs9", sans-serif; } .upload-section { text-align: center; margin-bottom: 30px; } #imageUpload { display: none; } .upload-btn { background-color: #3498db; color: white; padding: 12px 24px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s ease; } .upload-btn:hover { background-color: #2980b9; } .image-container { display: flex; justify-content: space-between; flex-wrap: wrap; margin-bottom: 30px; } .image-preview { flex-basis: calc(33% - 20px); margin-bottom: 20px; background-color: #222; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(255,255,255,0.1); } .image-preview h3 { background-color: #34495e; color: #fff; margin: 0; padding: 10px; font-size: 18px; } canvas { max-width: 100%; height: auto; display: block; } .controls { background-color: #222; padding: 20px; border-radius: 8px; margin-bottom: 20px; } .slider-container { margin-bottom: 15px; } .slider-container label { display: block; margin-bottom: 5px; font-weight: bold; color: #fdfdfd; } .slider { -webkit-appearance: none; width: 100%; height: 10px; border-radius: 5px; background: #555; outline: none; opacity: 0.7; transition: opacity .2s; } .slider:hover { opacity: 1; } .slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; border-radius: 50%; background: #3498db; cursor: pointer; } .slider::-moz-range-thumb { width: 20px; height: 20px; border-radius: 50%; background: #3498db; cursor: pointer; } .number-input { width: 50px; margin-left: 10px; background-color: #333; color: #fdfdfd; border: 1px solid #555; border-radius: 3px; padding: 3px; } #processBtn { display: block; width: 100%; padding: 12px; background-color: #2ecc71; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; transition: background-color 0.3s ease; } #processBtn:hover { background-color: #27ae60; } .ascii-art { font-family: "jgs9", monospace; white-space: pre; background-color: #222; padding: 20px; border-radius: 5px; overflow: auto; max-height: 600px; color: #fdfdfd; font-size: 2px; line-height: 1; text-align: center; box-shadow: 0 0 10px rgba(0,0,0,0.5); margin-top: 20px; } .download-buttons { display: flex; justify-content: center; gap: 10px; margin-top: 20px; } .download-btn { background-color: #4CAF50; border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 5px; } </style> </head> <body> <div id="loadingMessage" style="text-align: center; padding: 20px;"> Loading OpenCV.js, please wait... </div> <div class="container"> <h1>Advanced Image Preprocessing and ASCII Art Converter</h1> <div class="upload-section"> <input type="file" id="imageUpload" accept="image/*"> <label for="imageUpload" class="upload-btn">Upload Image</label> </div> <div class="image-container"> <div class="image-preview"> <h3>Original Image</h3> <canvas id="originalCanvas"></canvas> </div> <div class="image-preview"> <h3>Preprocessed Image</h3> <canvas id="preprocessedCanvas"></canvas> </div> <div class="image-preview"> <h3>Structure Map</h3> <canvas id="structureMapCanvas"></canvas> </div> </div> <div class="controls"> <div class="slider-container"> <label for="edgeThresholdSlider">Edge Detection Threshold:</label> <input type="range" id="edgeThresholdSlider" class="slider" min="0" max="255" value="100"> <span id="edgeThresholdValue">100</span> </div> <div class="slider-container"> <label for="gaussianBlurSlider">Gaussian Blur:</label> <input type="range" id="gaussianBlurSlider" class="slider" min="0" max="10" value="1" step="0.1"> <span id="gaussianBlurValue">1</span> </div> <div class="slider-container"> <label for="thinningIterationsSlider">Thinning Iterations:</label> <input type="range" id="thinningIterationsSlider" class="slider" min="1" max="10" value="3"> <span id="thinningIterationsValue">3</span> </div> <div class="slider-container"> <label for="scaleSlider">Scale (%):</label> <input type="range" id="scaleSlider" class="slider" min="10" max="200" value="100"> <span id="scaleValue">100</span> </div> <div class="slider-container"> <label for="dogThresholdSlider">DoG Threshold:</label> <input type="range" id="dogThresholdSlider" class="slider" min="0" max="255" value="100"> <span id="dogThresholdValue">100</span> </div> <div class="slider-container"> <label for="outputHeightSlider">Output Height (rows):</label> <input type="range" id="outputHeightSlider" class="slider" min="10" max="200" value="50"> <input type="number" id="outputHeightValue" class="number-input" min="10" max="200" value="50"> </div> <div class="slider-container"> <label for="scaleCountSlider">Number of Scales:</label> <input type="range" id="scaleCountSlider" class="slider" min="1" max="8" value="4"> <input type="number" id="scaleCountValue" class="number-input" min="1" max="8" value="4"> </div> <div class="slider-container"> <label for="ncrfRadiusSlider">Non-CRF Radius:</label> <input type="range" id="ncrfRadiusSlider" class="slider" min="1" max="20" value="5"> <input type="number" id="ncrfRadiusValue" class="number-input" min="1" max="20" value="5"> </div> <div class="slider-container"> <label for="modulationStrengthSlider">Modulation Strength:</label> <input type="range" id="modulationStrengthSlider" class="slider" min="0" max="1" step="0.1" value="0.5"> <input type="number" id="modulationStrengthValue" class="number-input" min="0" max="1" step="0.1" value="0.5"> </div> <div class="slider-container"> <label for="detailThresholdSlider">Detail Threshold:</label> <input type="range" id="detailThresholdSlider" class="slider" min="0" max="1" step="0.05" value="0.1"> <input type="number" id="detailThresholdValue" class="number-input" min="0" max="1" step="0.05" value="0.1"> </div> <div class="slider-container"> <label for="charSetSelect">Character Set:</label> <select id="charSetSelect"> <option value="standard">Standard</option> <option value="structure">Structure</option> <option value="outline">Outline</option> <option value="blocks">Blocks</option> <option value="geometric">Geometric</option> <option value="dots">Dots</option> </select> </div> <div class="slider-container"> <label for="invertColorsCheckbox"> <input type="checkbox" id="invertColorsCheckbox"> Invert Colors </label> </div> <button id="processBtn">Process Image</button> </div> <div class="ascii-output"> <h2>ASCII Output</h2> <div id="asciiOutput" class="ascii-art"></div> </div> <div class="download-buttons"> <button id="downloadPreprocessed" class="download-btn">Download Preprocessed Image</button> <button id="downloadStructureMap" class="download-btn">Download Structure Map</button> <button id="downloadAscii" class="download-btn">Download ASCII Art</button> </div> </div> <script> let originalMat, preprocessedMat, structureMap; let originalCanvas, preprocessedCanvas, structureMapCanvas; function handleImageUpload(event) { const file = event.target.files[0]; const reader = new FileReader(); reader.onload = function(e) { const img = new Image(); img.onload = function() { try { originalMat = cv.imread(img); cv.imshow('originalCanvas', originalMat); processImage(); } catch (err) { console.error('Error processing image:', err); alert('Error processing image. Please try again.'); } } img.src = e.target.result; } reader.readAsDataURL(file); } function openCvReady() { document.getElementById('loadingMessage').style.display = 'none'; document.getElementById('imageUpload').disabled = false; document.getElementById('processBtn').disabled = false; originalCanvas = document.getElementById('originalCanvas'); preprocessedCanvas = document.getElementById('preprocessedCanvas'); structureMapCanvas = document.getElementById('structureMapCanvas'); document.getElementById('imageUpload').addEventListener('change', handleImageUpload); document.getElementById('processBtn').addEventListener('click', processImage); document.getElementById('downloadPreprocessed').addEventListener('click', downloadPreprocessedImage); document.getElementById('downloadStructureMap').addEventListener('click', downloadStructureMap); document.getElementById('downloadAscii').addEventListener('click', downloadAsciiArt); } </script> <script async src="https://docs.opencv.org/4.5.1/opencv.js" onload="openCvReady();" type="text/javascript"></script> <script> function structureLineExtraction(src) { let dst = new cv.Mat(); cv.Canny(src, dst, 50, 150, 3, false); return dst; } function edgeDetection(src) { let dst = new cv.Mat(); let ksize = new cv.Size(3, 3); let sobel_x = new cv.Mat(); let sobel_y = new cv.Mat(); cv.Sobel(src, sobel_x, cv.CV_64F, 1, 0, ksize); cv.Sobel(src, sobel_y, cv.CV_64F, 0, 1, ksize); cv.magnitude(sobel_x, sobel_y, dst); cv.normalize(dst, dst, 0, 255, cv.NORM_MINMAX, cv.CV_8U); return dst; } function preThinning(src) { let dst = src.clone(); let rows = src.rows; let cols = src.cols; for (let i = 1; i < rows - 1; i++) { for (let j = 1; j < cols - 1; j++) { let p = src.ucharPtr(i, j)[0]; if (p === 0) continue; let p1 = src.ucharPtr(i-1, j)[0]; let p3 = src.ucharPtr(i, j+1)[0]; let p5 = src.ucharPtr(i+1, j)[0]; let p7 = src.ucharPtr(i, j-1)[0]; let B_odd = p1 + p3 + p5 + p7; if (B_odd < 2) { dst.ucharPtr(i, j)[0] = 0; } else if (B_odd > 2) { dst.ucharPtr(i, j)[0] = 255; } } } return dst; } function improvedThinning(src, iterations) { let dst = src.clone(); let changed; for (let i = 0; i < iterations; i++) { changed = false; dst = preThinning(dst); changed |= zhangSuenSubIteration(dst, 0); changed |= zhangSuenSubIteration(dst, 1); if (!changed) break; } return dst; } function processImage() { if (!originalMat) { console.error('No image loaded'); return; } try { const edgeThreshold = parseInt(document.getElementById('edgeThresholdSlider').value); const blurSize = parseFloat(document.getElementById('gaussianBlurSlider').value); const iterations = parseInt(document.getElementById('thinningIterationsSlider').value); const outputHeight = parseInt(document.getElementById('outputHeightSlider').value); const scaleCount = parseInt(document.getElementById('scaleCountSlider').value); const ncrfRadius = parseInt(document.getElementById('ncrfRadiusSlider').value); const modulationStrength = parseFloat(document.getElementById('modulationStrengthSlider').value); const detailThreshold = parseFloat(document.getElementById('detailThresholdSlider').value); let blurred = new cv.Mat(); let gray = new cv.Mat(); let edges = new cv.Mat(); cv.GaussianBlur(originalMat, blurred, new cv.Size(0, 0), blurSize, blurSize, cv.BORDER_DEFAULT); cv.cvtColor(blurred, gray, cv.COLOR_RGBA2GRAY); cv.Canny(gray, edges, edgeThreshold, edgeThreshold * 2, 3, false); preprocessedMat = improvedThinning(edges, iterations); cv.imshow('preprocessedCanvas', preprocessedMat); structureMap = extractStructure(gray, scaleCount, ncrfRadius, modulationStrength); displayStructureMap(structureMap); convertToASCII(structureMap, detailThreshold); blurred.delete(); gray.delete(); edges.delete(); } catch (err) { console.error('Error processing image:', err); alert('Error processing image. Please try again.'); } } function zhangSuenThinning(src, iterations) { let dst = src.clone(); let changed; for (let i = 0; i < iterations; i++) { changed = false; changed |= zhangSuenSubIteration(dst, 0); changed |= zhangSuenSubIteration(dst, 1); if (!changed) break; } return dst; } function zhangSuenSubIteration(img, step) { let changed = false; let rows = img.rows; let cols = img.cols; let markers = new cv.Mat(rows, cols, cv.CV_8U, new cv.Scalar(0)); for (let i = 1; i < rows - 1; i++) { for (let j = 1; j < cols - 1; j++) { if (img.ucharPtr(i, j)[0] === 0) continue; let p2 = img.ucharPtr(i-1, j)[0]; let p3 = img.ucharPtr(i-1, j+1)[0]; let p4 = img.ucharPtr(i, j+1)[0]; let p5 = img.ucharPtr(i+1, j+1)[0]; let p6 = img.ucharPtr(i+1, j)[0]; let p7 = img.ucharPtr(i+1, j-1)[0]; let p8 = img.ucharPtr(i, j-1)[0]; let p9 = img.ucharPtr(i-1, j-1)[0]; let A = ((p2 === 0 && p3 === 255) + (p3 === 0 && p4 === 255) + (p4 === 0 && p5 === 255) + (p5 === 0 && p6 === 255) + (p6 === 0 && p7 === 255) + (p7 === 0 && p8 === 255) + (p8 === 0 && p9 === 255) + (p9 === 0 && p2 === 255)); let B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; let m1 = step === 0 ? (p2 * p4 * p6) : (p2 * p4 * p8); let m2 = step === 0 ? (p4 * p6 * p8) : (p2 * p6 * p8); if (A === 1 && (B >= 2 * 255 && B <= 6 * 255) && m1 === 0 && m2 === 0) { markers.ucharPtr(i, j)[0] = 255; changed = true; } } } for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { if (markers.ucharPtr(i, j)[0] === 255) { img.ucharPtr(i, j)[0] = 0; } } } markers.delete(); return changed; } function extractStructure(grayMat, scaleCount, ncrfRadius, modulationStrength) { const width = grayMat.cols; const height = grayMat.rows; const data = grayMat.data; const scales = []; for (let i = 0; i < scaleCount; i++) { scales.push(Math.pow(2, i)); } let structureMap = new Array(width * height).fill(0); for (let scale of scales) { const response = simulateCRFResponse(data, width, height, scale); for (let i = 0; i < structureMap.length; i++) { structureMap[i] = Math.max(structureMap[i], response[i]); } } structureMap = applyNonCRFModulation(structureMap, width, height, ncrfRadius, modulationStrength); return structureMap; } function simulateCRFResponse(data, width, height, scale) { const response = new Array(width * height).fill(0); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const i = y * width + x; const grayValue = data[i]; if (x > scale && x < width - scale && y > scale && y < height - scale) { const diff = Math.abs(grayValue - data[y * width + x - scale]) + Math.abs(grayValue - data[y * width + x + scale]) + Math.abs(grayValue - data[(y - scale) * width + x]) + Math.abs(grayValue - data[(y + scale) * width + x]); response[i] = diff / (4 * 255); } } } return response; } function applyNonCRFModulation(structureMap, width, height, radius, strength) { const modulated = new Array(width * height).fill(0); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const i = y * width + x; let sum = 0; let count = 0; for (let dy = -radius; dy <= radius; dy++) { for (let dx = -radius; dx <= radius; dx++) { const nx = x + dx; const ny = y + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height) { sum += structureMap[ny * width + nx]; count++; } } } const avgResponse = sum / count; modulated[i] = structureMap[i] * (1 - strength * avgResponse); } } return modulated; } function displayStructureMap(structureMap) { const width = preprocessedCanvas.width; const height = preprocessedCanvas.height; const ctx = structureMapCanvas.getContext('2d'); const imageData = ctx.createImageData(width, height); for (let i = 0; i < structureMap.length; i++) { const value = Math.floor(structureMap[i] * 255); imageData.data[i * 4] = value; imageData.data[i * 4 + 1] = value; imageData.data[i * 4 + 2] = value; imageData.data[i * 4 + 3] = 255; } structureMapCanvas.width = width; structureMapCanvas.height = height; ctx.putImageData(imageData, 0, 0); } function convertToASCII(structureMap, detailThreshold) { const width = preprocessedCanvas.width; const height = preprocessedCanvas.height; const outputHeight = parseInt(document.getElementById('outputHeightSlider').value); const outputWidth = Math.floor(outputHeight * width / height); const charSetType = document.getElementById('charSetSelect').value; const invert = document.getElementById('invertColorsCheckbox').checked; const charSets = { standard: '@%#*+=-:. ', structure: '.,/\\_|~\'*+^][}{ ', outline: '▓▒░ ', blocks: '█▓▒░ ', geometric: '■□▢▣▤▥▦▧▨▩ ', dots: '•·○●◌◍◎◉ ' }; const characters = charSets[charSetType]; let asciiArt = ''; for (let y = 0; y < outputHeight; y++) { for (let x = 0; x < outputWidth; x++) { const srcX = Math.floor(x * width / outputWidth); const srcY = Math.floor(y * height / outputHeight); const value = structureMap[srcY * width + srcX]; let charIndex = Math.floor(value * (characters.length - 1)); if (invert) { charIndex = characters.length - 1 - charIndex; } asciiArt += value > detailThreshold ? characters[charIndex] : ' '; } asciiArt += '\n'; } document.getElementById('asciiOutput').textContent = asciiArt; } function downloadPreprocessedImage() { if (!preprocessedCanvas.toDataURL) { alert('No preprocessed image available.'); return; } const link = document.createElement('a'); link.download = 'preprocessed_image.png'; link.href = preprocessedCanvas.toDataURL(); link.click(); } function downloadStructureMap() { if (!structureMapCanvas.toDataURL) { alert('No structure map available.'); return; } const link = document.createElement('a'); link.download = 'structure_map.png'; link.href = structureMapCanvas.toDataURL(); link.click(); } function downloadAsciiArt() { const asciiArt = document.getElementById('asciiOutput').textContent; if (!asciiArt.trim()) { alert('No ASCII art available.'); return; } const blob = new Blob([asciiArt], {type: 'text/plain'}); const link = document.createElement('a'); link.download = 'ascii_art.txt'; link.href = URL.createObjectURL(blob); link.click(); } document.getElementById('edgeThresholdSlider').addEventListener('input', function(e) { document.getElementById('edgeThresholdValue').textContent = e.target.value; }); document.getElementById('gaussianBlurSlider').addEventListener('input', function(e) { document.getElementById('gaussianBlurValue').textContent = e.target.value; }); document.getElementById('thinningIterationsSlider').addEventListener('input', function(e) { document.getElementById('thinningIterationsValue').textContent = e.target.value; }); document.getElementById('scaleSlider').addEventListener('input', function(e) { document.getElementById('scaleValue').textContent = e.target.value; }); document.getElementById('dogThresholdSlider').addEventListener('input', function(e) { document.getElementById('dogThresholdValue').textContent = e.target.value; }); function syncInputs(slider, value) { slider.addEventListener('input', () => value.value = slider.value); value.addEventListener('input', () => slider.value = value.value); } syncInputs(document.getElementById('outputHeightSlider'), document.getElementById('outputHeightValue')); syncInputs(document.getElementById('scaleCountSlider'), document.getElementById('scaleCountValue')); syncInputs(document.getElementById('ncrfRadiusSlider'), document.getElementById('ncrfRadiusValue')); syncInputs(document.getElementById('modulationStrengthSlider'), document.getElementById('modulationStrengthValue')); syncInputs(document.getElementById('detailThresholdSlider'), document.getElementById('detailThresholdValue')); const sliders = document.querySelectorAll('.slider'); sliders.forEach(slider => { slider.addEventListener('input', processImage); }); document.getElementById('imageUpload').disabled = true; document.getElementById('processBtn').disabled = true; </script> </body> </html>