Spaces:
Running
Running
Update index.html
Browse files- index.html +699 -19
index.html
CHANGED
@@ -1,19 +1,699 @@
|
|
1 |
-
|
2 |
-
<
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<html><head><base href="https://www.example.com/advanced-image-preprocessing-ascii-converter/">
|
2 |
+
<meta charset="UTF-8">
|
3 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
4 |
+
<title>Advanced Image Preprocessing and ASCII Art Converter</title>
|
5 |
+
<style>
|
6 |
+
:root {
|
7 |
+
--fluid-5-20: clamp(0.3125rem, 0.2731rem + 1.2605vw, 1.25rem);
|
8 |
+
--fluid-8-20: clamp(0.5rem, 0.4685rem + 1.0084vw, 1.25rem);
|
9 |
+
--fluid-5-9: clamp(0.3125rem, 0.302rem + 0.3361vw, 0.5625rem);
|
10 |
+
--fluid-2-5: clamp(0.125rem, 0.1168rem + 0.2609vw, 0.3125rem);
|
11 |
+
}
|
12 |
+
|
13 |
+
@font-face {
|
14 |
+
font-family: 'jgs9';
|
15 |
+
src: url('https://websim.ai/fonts/jgs9.woff2') format('woff2');
|
16 |
+
font-weight: normal;
|
17 |
+
font-style: normal;
|
18 |
+
}
|
19 |
+
|
20 |
+
body {
|
21 |
+
font-family: 'Roboto', Arial, sans-serif;
|
22 |
+
margin: 0;
|
23 |
+
padding: 20px;
|
24 |
+
background-color: #040404;
|
25 |
+
color: #fdfdfd;
|
26 |
+
}
|
27 |
+
.container {
|
28 |
+
max-width: 1200px;
|
29 |
+
margin: 0 auto;
|
30 |
+
background-color: #111;
|
31 |
+
padding: 30px;
|
32 |
+
border-radius: 8px;
|
33 |
+
box-shadow: 0 4px 6px rgba(255,255,255,0.1);
|
34 |
+
}
|
35 |
+
h1, h2 {
|
36 |
+
text-align: center;
|
37 |
+
color: #fdfdfd;
|
38 |
+
font-family: "jgs9", sans-serif;
|
39 |
+
}
|
40 |
+
.upload-section {
|
41 |
+
text-align: center;
|
42 |
+
margin-bottom: 30px;
|
43 |
+
}
|
44 |
+
#imageUpload {
|
45 |
+
display: none;
|
46 |
+
}
|
47 |
+
.upload-btn {
|
48 |
+
background-color: #3498db;
|
49 |
+
color: white;
|
50 |
+
padding: 12px 24px;
|
51 |
+
border: none;
|
52 |
+
border-radius: 4px;
|
53 |
+
cursor: pointer;
|
54 |
+
font-size: 16px;
|
55 |
+
transition: background-color 0.3s ease;
|
56 |
+
}
|
57 |
+
.upload-btn:hover {
|
58 |
+
background-color: #2980b9;
|
59 |
+
}
|
60 |
+
.image-container {
|
61 |
+
display: flex;
|
62 |
+
justify-content: space-between;
|
63 |
+
flex-wrap: wrap;
|
64 |
+
margin-bottom: 30px;
|
65 |
+
}
|
66 |
+
.image-preview {
|
67 |
+
flex-basis: calc(33% - 20px);
|
68 |
+
margin-bottom: 20px;
|
69 |
+
background-color: #222;
|
70 |
+
border-radius: 8px;
|
71 |
+
overflow: hidden;
|
72 |
+
box-shadow: 0 2px 4px rgba(255,255,255,0.1);
|
73 |
+
}
|
74 |
+
.image-preview h3 {
|
75 |
+
background-color: #34495e;
|
76 |
+
color: #fff;
|
77 |
+
margin: 0;
|
78 |
+
padding: 10px;
|
79 |
+
font-size: 18px;
|
80 |
+
}
|
81 |
+
canvas {
|
82 |
+
max-width: 100%;
|
83 |
+
height: auto;
|
84 |
+
display: block;
|
85 |
+
}
|
86 |
+
.controls {
|
87 |
+
background-color: #222;
|
88 |
+
padding: 20px;
|
89 |
+
border-radius: 8px;
|
90 |
+
margin-bottom: 20px;
|
91 |
+
}
|
92 |
+
.slider-container {
|
93 |
+
margin-bottom: 15px;
|
94 |
+
}
|
95 |
+
.slider-container label {
|
96 |
+
display: block;
|
97 |
+
margin-bottom: 5px;
|
98 |
+
font-weight: bold;
|
99 |
+
color: #fdfdfd;
|
100 |
+
}
|
101 |
+
.slider {
|
102 |
+
-webkit-appearance: none;
|
103 |
+
width: 100%;
|
104 |
+
height: 10px;
|
105 |
+
border-radius: 5px;
|
106 |
+
background: #555;
|
107 |
+
outline: none;
|
108 |
+
opacity: 0.7;
|
109 |
+
transition: opacity .2s;
|
110 |
+
}
|
111 |
+
.slider:hover {
|
112 |
+
opacity: 1;
|
113 |
+
}
|
114 |
+
.slider::-webkit-slider-thumb {
|
115 |
+
-webkit-appearance: none;
|
116 |
+
appearance: none;
|
117 |
+
width: 20px;
|
118 |
+
height: 20px;
|
119 |
+
border-radius: 50%;
|
120 |
+
background: #3498db;
|
121 |
+
cursor: pointer;
|
122 |
+
}
|
123 |
+
.slider::-moz-range-thumb {
|
124 |
+
width: 20px;
|
125 |
+
height: 20px;
|
126 |
+
border-radius: 50%;
|
127 |
+
background: #3498db;
|
128 |
+
cursor: pointer;
|
129 |
+
}
|
130 |
+
.number-input {
|
131 |
+
width: 50px;
|
132 |
+
margin-left: 10px;
|
133 |
+
background-color: #333;
|
134 |
+
color: #fdfdfd;
|
135 |
+
border: 1px solid #555;
|
136 |
+
border-radius: 3px;
|
137 |
+
padding: 3px;
|
138 |
+
}
|
139 |
+
#processBtn {
|
140 |
+
display: block;
|
141 |
+
width: 100%;
|
142 |
+
padding: 12px;
|
143 |
+
background-color: #2ecc71;
|
144 |
+
color: white;
|
145 |
+
border: none;
|
146 |
+
border-radius: 4px;
|
147 |
+
font-size: 16px;
|
148 |
+
cursor: pointer;
|
149 |
+
transition: background-color 0.3s ease;
|
150 |
+
}
|
151 |
+
#processBtn:hover {
|
152 |
+
background-color: #27ae60;
|
153 |
+
}
|
154 |
+
.ascii-art {
|
155 |
+
font-family: "jgs9", monospace;
|
156 |
+
white-space: pre;
|
157 |
+
background-color: #222;
|
158 |
+
padding: 20px;
|
159 |
+
border-radius: 5px;
|
160 |
+
overflow: auto;
|
161 |
+
max-height: 600px;
|
162 |
+
color: #fdfdfd;
|
163 |
+
font-size: 2px;
|
164 |
+
line-height: 1;
|
165 |
+
text-align: center;
|
166 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
167 |
+
margin-top: 20px;
|
168 |
+
}
|
169 |
+
.download-buttons {
|
170 |
+
display: flex;
|
171 |
+
justify-content: center;
|
172 |
+
gap: 10px;
|
173 |
+
margin-top: 20px;
|
174 |
+
}
|
175 |
+
.download-btn {
|
176 |
+
background-color: #4CAF50;
|
177 |
+
border: none;
|
178 |
+
color: white;
|
179 |
+
padding: 10px 20px;
|
180 |
+
text-align: center;
|
181 |
+
text-decoration: none;
|
182 |
+
display: inline-block;
|
183 |
+
font-size: 16px;
|
184 |
+
margin: 4px 2px;
|
185 |
+
cursor: pointer;
|
186 |
+
border-radius: 5px;
|
187 |
+
}
|
188 |
+
</style>
|
189 |
+
</head>
|
190 |
+
<body>
|
191 |
+
<div id="loadingMessage" style="text-align: center; padding: 20px;">
|
192 |
+
Loading OpenCV.js, please wait...
|
193 |
+
</div>
|
194 |
+
<div class="container">
|
195 |
+
<h1>Advanced Image Preprocessing and ASCII Art Converter</h1>
|
196 |
+
<div class="upload-section">
|
197 |
+
<input type="file" id="imageUpload" accept="image/*">
|
198 |
+
<label for="imageUpload" class="upload-btn">Upload Image</label>
|
199 |
+
</div>
|
200 |
+
<div class="image-container">
|
201 |
+
<div class="image-preview">
|
202 |
+
<h3>Original Image</h3>
|
203 |
+
<canvas id="originalCanvas"></canvas>
|
204 |
+
</div>
|
205 |
+
<div class="image-preview">
|
206 |
+
<h3>Preprocessed Image</h3>
|
207 |
+
<canvas id="preprocessedCanvas"></canvas>
|
208 |
+
</div>
|
209 |
+
<div class="image-preview">
|
210 |
+
<h3>Structure Map</h3>
|
211 |
+
<canvas id="structureMapCanvas"></canvas>
|
212 |
+
</div>
|
213 |
+
</div>
|
214 |
+
<div class="controls">
|
215 |
+
<div class="slider-container">
|
216 |
+
<label for="edgeThresholdSlider">Edge Detection Threshold:</label>
|
217 |
+
<input type="range" id="edgeThresholdSlider" class="slider" min="0" max="255" value="100">
|
218 |
+
<span id="edgeThresholdValue">100</span>
|
219 |
+
</div>
|
220 |
+
<div class="slider-container">
|
221 |
+
<label for="gaussianBlurSlider">Gaussian Blur:</label>
|
222 |
+
<input type="range" id="gaussianBlurSlider" class="slider" min="0" max="10" value="1" step="0.1">
|
223 |
+
<span id="gaussianBlurValue">1</span>
|
224 |
+
</div>
|
225 |
+
<div class="slider-container">
|
226 |
+
<label for="thinningIterationsSlider">Thinning Iterations:</label>
|
227 |
+
<input type="range" id="thinningIterationsSlider" class="slider" min="1" max="10" value="3">
|
228 |
+
<span id="thinningIterationsValue">3</span>
|
229 |
+
</div>
|
230 |
+
<div class="slider-container">
|
231 |
+
<label for="scaleSlider">Scale (%):</label>
|
232 |
+
<input type="range" id="scaleSlider" class="slider" min="10" max="200" value="100">
|
233 |
+
<span id="scaleValue">100</span>
|
234 |
+
</div>
|
235 |
+
<div class="slider-container">
|
236 |
+
<label for="dogThresholdSlider">DoG Threshold:</label>
|
237 |
+
<input type="range" id="dogThresholdSlider" class="slider" min="0" max="255" value="100">
|
238 |
+
<span id="dogThresholdValue">100</span>
|
239 |
+
</div>
|
240 |
+
<div class="slider-container">
|
241 |
+
<label for="outputHeightSlider">Output Height (rows):</label>
|
242 |
+
<input type="range" id="outputHeightSlider" class="slider" min="10" max="200" value="50">
|
243 |
+
<input type="number" id="outputHeightValue" class="number-input" min="10" max="200" value="50">
|
244 |
+
</div>
|
245 |
+
<div class="slider-container">
|
246 |
+
<label for="scaleCountSlider">Number of Scales:</label>
|
247 |
+
<input type="range" id="scaleCountSlider" class="slider" min="1" max="8" value="4">
|
248 |
+
<input type="number" id="scaleCountValue" class="number-input" min="1" max="8" value="4">
|
249 |
+
</div>
|
250 |
+
<div class="slider-container">
|
251 |
+
<label for="ncrfRadiusSlider">Non-CRF Radius:</label>
|
252 |
+
<input type="range" id="ncrfRadiusSlider" class="slider" min="1" max="20" value="5">
|
253 |
+
<input type="number" id="ncrfRadiusValue" class="number-input" min="1" max="20" value="5">
|
254 |
+
</div>
|
255 |
+
<div class="slider-container">
|
256 |
+
<label for="modulationStrengthSlider">Modulation Strength:</label>
|
257 |
+
<input type="range" id="modulationStrengthSlider" class="slider" min="0" max="1" step="0.1" value="0.5">
|
258 |
+
<input type="number" id="modulationStrengthValue" class="number-input" min="0" max="1" step="0.1" value="0.5">
|
259 |
+
</div>
|
260 |
+
<div class="slider-container">
|
261 |
+
<label for="detailThresholdSlider">Detail Threshold:</label>
|
262 |
+
<input type="range" id="detailThresholdSlider" class="slider" min="0" max="1" step="0.05" value="0.1">
|
263 |
+
<input type="number" id="detailThresholdValue" class="number-input" min="0" max="1" step="0.05" value="0.1">
|
264 |
+
</div>
|
265 |
+
<div class="slider-container">
|
266 |
+
<label for="charSetSelect">Character Set:</label>
|
267 |
+
<select id="charSetSelect">
|
268 |
+
<option value="standard">Standard</option>
|
269 |
+
<option value="structure">Structure</option>
|
270 |
+
<option value="outline">Outline</option>
|
271 |
+
<option value="blocks">Blocks</option>
|
272 |
+
<option value="geometric">Geometric</option>
|
273 |
+
<option value="dots">Dots</option>
|
274 |
+
</select>
|
275 |
+
</div>
|
276 |
+
<div class="slider-container">
|
277 |
+
<label for="invertColorsCheckbox">
|
278 |
+
<input type="checkbox" id="invertColorsCheckbox"> Invert Colors
|
279 |
+
</label>
|
280 |
+
</div>
|
281 |
+
<button id="processBtn">Process Image</button>
|
282 |
+
</div>
|
283 |
+
<div class="ascii-output">
|
284 |
+
<h2>ASCII Output</h2>
|
285 |
+
<div id="asciiOutput" class="ascii-art"></div>
|
286 |
+
</div>
|
287 |
+
<div class="download-buttons">
|
288 |
+
<button id="downloadPreprocessed" class="download-btn">Download Preprocessed Image</button>
|
289 |
+
<button id="downloadStructureMap" class="download-btn">Download Structure Map</button>
|
290 |
+
<button id="downloadAscii" class="download-btn">Download ASCII Art</button>
|
291 |
+
</div>
|
292 |
+
</div>
|
293 |
+
|
294 |
+
<script>
|
295 |
+
let originalMat, preprocessedMat, structureMap;
|
296 |
+
let originalCanvas, preprocessedCanvas, structureMapCanvas;
|
297 |
+
|
298 |
+
function handleImageUpload(event) {
|
299 |
+
const file = event.target.files[0];
|
300 |
+
const reader = new FileReader();
|
301 |
+
reader.onload = function(e) {
|
302 |
+
const img = new Image();
|
303 |
+
img.onload = function() {
|
304 |
+
try {
|
305 |
+
originalMat = cv.imread(img);
|
306 |
+
cv.imshow('originalCanvas', originalMat);
|
307 |
+
processImage();
|
308 |
+
} catch (err) {
|
309 |
+
console.error('Error processing image:', err);
|
310 |
+
alert('Error processing image. Please try again.');
|
311 |
+
}
|
312 |
+
}
|
313 |
+
img.src = e.target.result;
|
314 |
+
}
|
315 |
+
reader.readAsDataURL(file);
|
316 |
+
}
|
317 |
+
|
318 |
+
function openCvReady() {
|
319 |
+
document.getElementById('loadingMessage').style.display = 'none';
|
320 |
+
document.getElementById('imageUpload').disabled = false;
|
321 |
+
document.getElementById('processBtn').disabled = false;
|
322 |
+
|
323 |
+
originalCanvas = document.getElementById('originalCanvas');
|
324 |
+
preprocessedCanvas = document.getElementById('preprocessedCanvas');
|
325 |
+
structureMapCanvas = document.getElementById('structureMapCanvas');
|
326 |
+
|
327 |
+
document.getElementById('imageUpload').addEventListener('change', handleImageUpload);
|
328 |
+
document.getElementById('processBtn').addEventListener('click', processImage);
|
329 |
+
document.getElementById('downloadPreprocessed').addEventListener('click', downloadPreprocessedImage);
|
330 |
+
document.getElementById('downloadStructureMap').addEventListener('click', downloadStructureMap);
|
331 |
+
document.getElementById('downloadAscii').addEventListener('click', downloadAsciiArt);
|
332 |
+
}
|
333 |
+
</script>
|
334 |
+
<script async src="https://docs.opencv.org/4.5.1/opencv.js" onload="openCvReady();" type="text/javascript"></script>
|
335 |
+
<script>
|
336 |
+
function structureLineExtraction(src) {
|
337 |
+
let dst = new cv.Mat();
|
338 |
+
cv.Canny(src, dst, 50, 150, 3, false);
|
339 |
+
return dst;
|
340 |
+
}
|
341 |
+
|
342 |
+
function edgeDetection(src) {
|
343 |
+
let dst = new cv.Mat();
|
344 |
+
let ksize = new cv.Size(3, 3);
|
345 |
+
let sobel_x = new cv.Mat();
|
346 |
+
let sobel_y = new cv.Mat();
|
347 |
+
cv.Sobel(src, sobel_x, cv.CV_64F, 1, 0, ksize);
|
348 |
+
cv.Sobel(src, sobel_y, cv.CV_64F, 0, 1, ksize);
|
349 |
+
cv.magnitude(sobel_x, sobel_y, dst);
|
350 |
+
cv.normalize(dst, dst, 0, 255, cv.NORM_MINMAX, cv.CV_8U);
|
351 |
+
return dst;
|
352 |
+
}
|
353 |
+
|
354 |
+
function preThinning(src) {
|
355 |
+
let dst = src.clone();
|
356 |
+
let rows = src.rows;
|
357 |
+
let cols = src.cols;
|
358 |
+
|
359 |
+
for (let i = 1; i < rows - 1; i++) {
|
360 |
+
for (let j = 1; j < cols - 1; j++) {
|
361 |
+
let p = src.ucharPtr(i, j)[0];
|
362 |
+
if (p === 0) continue;
|
363 |
+
|
364 |
+
let p1 = src.ucharPtr(i-1, j)[0];
|
365 |
+
let p3 = src.ucharPtr(i, j+1)[0];
|
366 |
+
let p5 = src.ucharPtr(i+1, j)[0];
|
367 |
+
let p7 = src.ucharPtr(i, j-1)[0];
|
368 |
+
|
369 |
+
let B_odd = p1 + p3 + p5 + p7;
|
370 |
+
|
371 |
+
if (B_odd < 2) {
|
372 |
+
dst.ucharPtr(i, j)[0] = 0;
|
373 |
+
} else if (B_odd > 2) {
|
374 |
+
dst.ucharPtr(i, j)[0] = 255;
|
375 |
+
}
|
376 |
+
}
|
377 |
+
}
|
378 |
+
return dst;
|
379 |
+
}
|
380 |
+
|
381 |
+
function improvedThinning(src, iterations) {
|
382 |
+
let dst = src.clone();
|
383 |
+
let changed;
|
384 |
+
|
385 |
+
for (let i = 0; i < iterations; i++) {
|
386 |
+
changed = false;
|
387 |
+
dst = preThinning(dst);
|
388 |
+
changed |= zhangSuenSubIteration(dst, 0);
|
389 |
+
changed |= zhangSuenSubIteration(dst, 1);
|
390 |
+
if (!changed) break;
|
391 |
+
}
|
392 |
+
|
393 |
+
return dst;
|
394 |
+
}
|
395 |
+
|
396 |
+
function processImage() {
|
397 |
+
if (!originalMat) {
|
398 |
+
console.error('No image loaded');
|
399 |
+
return;
|
400 |
+
}
|
401 |
+
|
402 |
+
try {
|
403 |
+
const edgeThreshold = parseInt(document.getElementById('edgeThresholdSlider').value);
|
404 |
+
const blurSize = parseFloat(document.getElementById('gaussianBlurSlider').value);
|
405 |
+
const iterations = parseInt(document.getElementById('thinningIterationsSlider').value);
|
406 |
+
const outputHeight = parseInt(document.getElementById('outputHeightSlider').value);
|
407 |
+
const scaleCount = parseInt(document.getElementById('scaleCountSlider').value);
|
408 |
+
const ncrfRadius = parseInt(document.getElementById('ncrfRadiusSlider').value);
|
409 |
+
const modulationStrength = parseFloat(document.getElementById('modulationStrengthSlider').value);
|
410 |
+
const detailThreshold = parseFloat(document.getElementById('detailThresholdSlider').value);
|
411 |
+
|
412 |
+
let blurred = new cv.Mat();
|
413 |
+
let gray = new cv.Mat();
|
414 |
+
let edges = new cv.Mat();
|
415 |
+
|
416 |
+
cv.GaussianBlur(originalMat, blurred, new cv.Size(0, 0), blurSize, blurSize, cv.BORDER_DEFAULT);
|
417 |
+
cv.cvtColor(blurred, gray, cv.COLOR_RGBA2GRAY);
|
418 |
+
cv.Canny(gray, edges, edgeThreshold, edgeThreshold * 2, 3, false);
|
419 |
+
|
420 |
+
preprocessedMat = improvedThinning(edges, iterations);
|
421 |
+
cv.imshow('preprocessedCanvas', preprocessedMat);
|
422 |
+
|
423 |
+
structureMap = extractStructure(gray, scaleCount, ncrfRadius, modulationStrength);
|
424 |
+
displayStructureMap(structureMap);
|
425 |
+
|
426 |
+
convertToASCII(structureMap, detailThreshold);
|
427 |
+
|
428 |
+
blurred.delete();
|
429 |
+
gray.delete();
|
430 |
+
edges.delete();
|
431 |
+
} catch (err) {
|
432 |
+
console.error('Error processing image:', err);
|
433 |
+
alert('Error processing image. Please try again.');
|
434 |
+
}
|
435 |
+
}
|
436 |
+
|
437 |
+
function zhangSuenThinning(src, iterations) {
|
438 |
+
let dst = src.clone();
|
439 |
+
let changed;
|
440 |
+
|
441 |
+
for (let i = 0; i < iterations; i++) {
|
442 |
+
changed = false;
|
443 |
+
changed |= zhangSuenSubIteration(dst, 0);
|
444 |
+
changed |= zhangSuenSubIteration(dst, 1);
|
445 |
+
if (!changed) break;
|
446 |
+
}
|
447 |
+
|
448 |
+
return dst;
|
449 |
+
}
|
450 |
+
|
451 |
+
function zhangSuenSubIteration(img, step) {
|
452 |
+
let changed = false;
|
453 |
+
let rows = img.rows;
|
454 |
+
let cols = img.cols;
|
455 |
+
let markers = new cv.Mat(rows, cols, cv.CV_8U, new cv.Scalar(0));
|
456 |
+
|
457 |
+
for (let i = 1; i < rows - 1; i++) {
|
458 |
+
for (let j = 1; j < cols - 1; j++) {
|
459 |
+
if (img.ucharPtr(i, j)[0] === 0) continue;
|
460 |
+
|
461 |
+
let p2 = img.ucharPtr(i-1, j)[0];
|
462 |
+
let p3 = img.ucharPtr(i-1, j+1)[0];
|
463 |
+
let p4 = img.ucharPtr(i, j+1)[0];
|
464 |
+
let p5 = img.ucharPtr(i+1, j+1)[0];
|
465 |
+
let p6 = img.ucharPtr(i+1, j)[0];
|
466 |
+
let p7 = img.ucharPtr(i+1, j-1)[0];
|
467 |
+
let p8 = img.ucharPtr(i, j-1)[0];
|
468 |
+
let p9 = img.ucharPtr(i-1, j-1)[0];
|
469 |
+
|
470 |
+
let A = ((p2 === 0 && p3 === 255) + (p3 === 0 && p4 === 255) +
|
471 |
+
(p4 === 0 && p5 === 255) + (p5 === 0 && p6 === 255) +
|
472 |
+
(p6 === 0 && p7 === 255) + (p7 === 0 && p8 === 255) +
|
473 |
+
(p8 === 0 && p9 === 255) + (p9 === 0 && p2 === 255));
|
474 |
+
let B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
|
475 |
+
|
476 |
+
let m1 = step === 0 ? (p2 * p4 * p6) : (p2 * p4 * p8);
|
477 |
+
let m2 = step === 0 ? (p4 * p6 * p8) : (p2 * p6 * p8);
|
478 |
+
|
479 |
+
if (A === 1 && (B >= 2 * 255 && B <= 6 * 255) && m1 === 0 && m2 === 0) {
|
480 |
+
markers.ucharPtr(i, j)[0] = 255;
|
481 |
+
changed = true;
|
482 |
+
}
|
483 |
+
}
|
484 |
+
}
|
485 |
+
|
486 |
+
for (let i = 0; i < rows; i++) {
|
487 |
+
for (let j = 0; j < cols; j++) {
|
488 |
+
if (markers.ucharPtr(i, j)[0] === 255) {
|
489 |
+
img.ucharPtr(i, j)[0] = 0;
|
490 |
+
}
|
491 |
+
}
|
492 |
+
}
|
493 |
+
|
494 |
+
markers.delete();
|
495 |
+
return changed;
|
496 |
+
}
|
497 |
+
|
498 |
+
function extractStructure(grayMat, scaleCount, ncrfRadius, modulationStrength) {
|
499 |
+
const width = grayMat.cols;
|
500 |
+
const height = grayMat.rows;
|
501 |
+
const data = grayMat.data;
|
502 |
+
|
503 |
+
const scales = [];
|
504 |
+
for (let i = 0; i < scaleCount; i++) {
|
505 |
+
scales.push(Math.pow(2, i));
|
506 |
+
}
|
507 |
+
let structureMap = new Array(width * height).fill(0);
|
508 |
+
|
509 |
+
for (let scale of scales) {
|
510 |
+
const response = simulateCRFResponse(data, width, height, scale);
|
511 |
+
for (let i = 0; i < structureMap.length; i++) {
|
512 |
+
structureMap[i] = Math.max(structureMap[i], response[i]);
|
513 |
+
}
|
514 |
+
}
|
515 |
+
|
516 |
+
structureMap = applyNonCRFModulation(structureMap, width, height, ncrfRadius, modulationStrength);
|
517 |
+
return structureMap;
|
518 |
+
}
|
519 |
+
|
520 |
+
function simulateCRFResponse(data, width, height, scale) {
|
521 |
+
const response = new Array(width * height).fill(0);
|
522 |
+
|
523 |
+
for (let y = 0; y < height; y++) {
|
524 |
+
for (let x = 0; x < width; x++) {
|
525 |
+
const i = y * width + x;
|
526 |
+
const grayValue = data[i];
|
527 |
+
|
528 |
+
if (x > scale && x < width - scale && y > scale && y < height - scale) {
|
529 |
+
const diff = Math.abs(grayValue - data[y * width + x - scale]) +
|
530 |
+
Math.abs(grayValue - data[y * width + x + scale]) +
|
531 |
+
Math.abs(grayValue - data[(y - scale) * width + x]) +
|
532 |
+
Math.abs(grayValue - data[(y + scale) * width + x]);
|
533 |
+
response[i] = diff / (4 * 255);
|
534 |
+
}
|
535 |
+
}
|
536 |
+
}
|
537 |
+
|
538 |
+
return response;
|
539 |
+
}
|
540 |
+
|
541 |
+
function applyNonCRFModulation(structureMap, width, height, radius, strength) {
|
542 |
+
const modulated = new Array(width * height).fill(0);
|
543 |
+
|
544 |
+
for (let y = 0; y < height; y++) {
|
545 |
+
for (let x = 0; x < width; x++) {
|
546 |
+
const i = y * width + x;
|
547 |
+
let sum = 0;
|
548 |
+
let count = 0;
|
549 |
+
|
550 |
+
for (let dy = -radius; dy <= radius; dy++) {
|
551 |
+
for (let dx = -radius; dx <= radius; dx++) {
|
552 |
+
const nx = x + dx;
|
553 |
+
const ny = y + dy;
|
554 |
+
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
555 |
+
sum += structureMap[ny * width + nx];
|
556 |
+
count++;
|
557 |
+
}
|
558 |
+
}
|
559 |
+
}
|
560 |
+
|
561 |
+
const avgResponse = sum / count;
|
562 |
+
modulated[i] = structureMap[i] * (1 - strength * avgResponse);
|
563 |
+
}
|
564 |
+
}
|
565 |
+
|
566 |
+
return modulated;
|
567 |
+
}
|
568 |
+
|
569 |
+
function displayStructureMap(structureMap) {
|
570 |
+
const width = preprocessedCanvas.width;
|
571 |
+
const height = preprocessedCanvas.height;
|
572 |
+
const ctx = structureMapCanvas.getContext('2d');
|
573 |
+
const imageData = ctx.createImageData(width, height);
|
574 |
+
|
575 |
+
for (let i = 0; i < structureMap.length; i++) {
|
576 |
+
const value = Math.floor(structureMap[i] * 255);
|
577 |
+
imageData.data[i * 4] = value;
|
578 |
+
imageData.data[i * 4 + 1] = value;
|
579 |
+
imageData.data[i * 4 + 2] = value;
|
580 |
+
imageData.data[i * 4 + 3] = 255;
|
581 |
+
}
|
582 |
+
|
583 |
+
structureMapCanvas.width = width;
|
584 |
+
structureMapCanvas.height = height;
|
585 |
+
ctx.putImageData(imageData, 0, 0);
|
586 |
+
}
|
587 |
+
|
588 |
+
function convertToASCII(structureMap, detailThreshold) {
|
589 |
+
const width = preprocessedCanvas.width;
|
590 |
+
const height = preprocessedCanvas.height;
|
591 |
+
const outputHeight = parseInt(document.getElementById('outputHeightSlider').value);
|
592 |
+
const outputWidth = Math.floor(outputHeight * width / height);
|
593 |
+
const charSetType = document.getElementById('charSetSelect').value;
|
594 |
+
const invert = document.getElementById('invertColorsCheckbox').checked;
|
595 |
+
|
596 |
+
const charSets = {
|
597 |
+
standard: '@%#*+=-:. ',
|
598 |
+
structure: '.,/\\_|~\'*+^][}{ ',
|
599 |
+
outline: 'βββ ',
|
600 |
+
blocks: 'ββββ ',
|
601 |
+
geometric: 'β β‘β’β£β€β₯β¦β§β¨β© ',
|
602 |
+
dots: 'β’Β·ββββββ '
|
603 |
+
};
|
604 |
+
const characters = charSets[charSetType];
|
605 |
+
|
606 |
+
let asciiArt = '';
|
607 |
+
for (let y = 0; y < outputHeight; y++) {
|
608 |
+
for (let x = 0; x < outputWidth; x++) {
|
609 |
+
const srcX = Math.floor(x * width / outputWidth);
|
610 |
+
const srcY = Math.floor(y * height / outputHeight);
|
611 |
+
const value = structureMap[srcY * width + srcX];
|
612 |
+
|
613 |
+
let charIndex = Math.floor(value * (characters.length - 1));
|
614 |
+
if (invert) {
|
615 |
+
charIndex = characters.length - 1 - charIndex;
|
616 |
+
}
|
617 |
+
asciiArt += value > detailThreshold ? characters[charIndex] : ' ';
|
618 |
+
}
|
619 |
+
asciiArt += '\n';
|
620 |
+
}
|
621 |
+
|
622 |
+
document.getElementById('asciiOutput').textContent = asciiArt;
|
623 |
+
}
|
624 |
+
|
625 |
+
function downloadPreprocessedImage() {
|
626 |
+
if (!preprocessedCanvas.toDataURL) {
|
627 |
+
alert('No preprocessed image available.');
|
628 |
+
return;
|
629 |
+
}
|
630 |
+
|
631 |
+
const link = document.createElement('a');
|
632 |
+
link.download = 'preprocessed_image.png';
|
633 |
+
link.href = preprocessedCanvas.toDataURL();
|
634 |
+
link.click();
|
635 |
+
}
|
636 |
+
|
637 |
+
function downloadStructureMap() {
|
638 |
+
if (!structureMapCanvas.toDataURL) {
|
639 |
+
alert('No structure map available.');
|
640 |
+
return;
|
641 |
+
}
|
642 |
+
|
643 |
+
const link = document.createElement('a');
|
644 |
+
link.download = 'structure_map.png';
|
645 |
+
link.href = structureMapCanvas.toDataURL();
|
646 |
+
link.click();
|
647 |
+
}
|
648 |
+
|
649 |
+
function downloadAsciiArt() {
|
650 |
+
const asciiArt = document.getElementById('asciiOutput').textContent;
|
651 |
+
if (!asciiArt.trim()) {
|
652 |
+
alert('No ASCII art available.');
|
653 |
+
return;
|
654 |
+
}
|
655 |
+
|
656 |
+
const blob = new Blob([asciiArt], {type: 'text/plain'});
|
657 |
+
const link = document.createElement('a');
|
658 |
+
link.download = 'ascii_art.txt';
|
659 |
+
link.href = URL.createObjectURL(blob);
|
660 |
+
link.click();
|
661 |
+
}
|
662 |
+
|
663 |
+
document.getElementById('edgeThresholdSlider').addEventListener('input', function(e) {
|
664 |
+
document.getElementById('edgeThresholdValue').textContent = e.target.value;
|
665 |
+
});
|
666 |
+
document.getElementById('gaussianBlurSlider').addEventListener('input', function(e) {
|
667 |
+
document.getElementById('gaussianBlurValue').textContent = e.target.value;
|
668 |
+
});
|
669 |
+
document.getElementById('thinningIterationsSlider').addEventListener('input', function(e) {
|
670 |
+
document.getElementById('thinningIterationsValue').textContent = e.target.value;
|
671 |
+
});
|
672 |
+
document.getElementById('scaleSlider').addEventListener('input', function(e) {
|
673 |
+
document.getElementById('scaleValue').textContent = e.target.value;
|
674 |
+
});
|
675 |
+
document.getElementById('dogThresholdSlider').addEventListener('input', function(e) {
|
676 |
+
document.getElementById('dogThresholdValue').textContent = e.target.value;
|
677 |
+
});
|
678 |
+
|
679 |
+
function syncInputs(slider, value) {
|
680 |
+
slider.addEventListener('input', () => value.value = slider.value);
|
681 |
+
value.addEventListener('input', () => slider.value = value.value);
|
682 |
+
}
|
683 |
+
|
684 |
+
syncInputs(document.getElementById('outputHeightSlider'), document.getElementById('outputHeightValue'));
|
685 |
+
syncInputs(document.getElementById('scaleCountSlider'), document.getElementById('scaleCountValue'));
|
686 |
+
syncInputs(document.getElementById('ncrfRadiusSlider'), document.getElementById('ncrfRadiusValue'));
|
687 |
+
syncInputs(document.getElementById('modulationStrengthSlider'), document.getElementById('modulationStrengthValue'));
|
688 |
+
syncInputs(document.getElementById('detailThresholdSlider'), document.getElementById('detailThresholdValue'));
|
689 |
+
|
690 |
+
const sliders = document.querySelectorAll('.slider');
|
691 |
+
sliders.forEach(slider => {
|
692 |
+
slider.addEventListener('input', processImage);
|
693 |
+
});
|
694 |
+
|
695 |
+
document.getElementById('imageUpload').disabled = true;
|
696 |
+
document.getElementById('processBtn').disabled = true;
|
697 |
+
</script>
|
698 |
+
</body>
|
699 |
+
</html>
|