IMAGE2ASCII / index.html
Csplk's picture
Update index.html
3732233 verified
raw
history blame
23.9 kB
<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>