|
|
@@ -2,27 +2,20 @@
|
|
2
|
2
|
<html>
|
|
3
|
3
|
<head>
|
|
4
|
4
|
<meta charset="UTF-8">
|
|
5
|
|
- <script src="js/vendor/interact/interact.min.js"></script>
|
|
|
5
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
6
|
6
|
<script src="js/vendor/opencv/opencv.js"></script>
|
|
|
7
|
+ <script src="js/vendor/interact/interact.min.js"></script>
|
|
7
|
8
|
<script src="js/vendor/load-image/load-image.all.min.js"></script>
|
|
8
|
|
- <script src="js/vendor/plotly/plotly-latest.min.js"></script>
|
|
9
|
|
- <script src="js/vendor/panzoom/panzoom.js"></script>
|
|
10
|
9
|
<script src="js/vendor/moment/moment.min.js"></script>
|
|
11
|
10
|
<script src="js/xword.js"></script>
|
|
12
|
11
|
<style>
|
|
13
|
|
- canvas {
|
|
14
|
|
- width: 400px;
|
|
15
|
|
- }
|
|
16
|
12
|
#sourceImageContainer {
|
|
17
|
|
- width: 400px;
|
|
18
|
|
- overflow: hidden;
|
|
|
13
|
+ width: 100%;
|
|
|
14
|
+ overflow: scroll;
|
|
|
15
|
+ touch-action: pan-x pan-y;
|
|
19
|
16
|
}
|
|
20
|
17
|
#sourceImage {
|
|
21
|
18
|
position: relative;
|
|
22
|
|
- width: min-content;
|
|
23
|
|
- }
|
|
24
|
|
- #sourceImage canvas {
|
|
25
|
|
- width: auto;
|
|
26
|
19
|
}
|
|
27
|
20
|
#sourceImage .overlay {
|
|
28
|
21
|
position: absolute;
|
|
|
@@ -32,15 +25,18 @@
|
|
32
|
25
|
.handle {
|
|
33
|
26
|
position: absolute;
|
|
34
|
27
|
box-sizing: border-box;
|
|
35
|
|
- width: 301px;
|
|
36
|
|
- height: 301px;
|
|
|
28
|
+ width: 71px;
|
|
|
29
|
+ height: 71px;
|
|
37
|
30
|
border-radius: 50%;
|
|
38
|
|
- border: 6px solid red;
|
|
|
31
|
+ border: 2px solid red;
|
|
39
|
32
|
transform: translate(-50%, -50%);
|
|
|
33
|
+ touch-action: none;
|
|
|
34
|
+ }
|
|
|
35
|
+ #outputContainer {
|
|
|
36
|
+ width: 100%;
|
|
40
|
37
|
}
|
|
41
|
|
- #graph {
|
|
42
|
|
- width: 1000px;
|
|
43
|
|
- height: 800px;
|
|
|
38
|
+ canvas {
|
|
|
39
|
+ width: 100%;
|
|
44
|
40
|
}
|
|
45
|
41
|
</style>
|
|
46
|
42
|
</head>
|
|
|
@@ -52,13 +48,12 @@
|
|
52
|
48
|
<div id="sourceImage"></div>
|
|
53
|
49
|
</div>
|
|
54
|
50
|
<button id="recalculateButton">Recalculate</button>
|
|
55
|
|
- <div>
|
|
|
51
|
+ <div id="outputContainer">
|
|
56
|
52
|
<canvas id="output"></canvas>
|
|
57
|
53
|
</div>
|
|
58
|
54
|
<div>
|
|
59
|
55
|
<a id="download" href="#" download="crossword.png">Download</a>
|
|
60
|
56
|
</div>
|
|
61
|
|
- <div id="graph"></div>
|
|
62
|
57
|
<script>
|
|
63
|
58
|
'use strict';
|
|
64
|
59
|
|
|
|
@@ -67,6 +62,7 @@
|
|
67
|
62
|
let fileInput = document.getElementById("fileInput");
|
|
68
|
63
|
let output = document.getElementById('output');
|
|
69
|
64
|
let downloadLink = document.getElementById('download');
|
|
|
65
|
+ let recalculateButton = document.getElementById('recalculateButton');
|
|
70
|
66
|
|
|
71
|
67
|
let isSquare = true;
|
|
72
|
68
|
|
|
|
@@ -88,18 +84,22 @@
|
|
88
|
84
|
|
|
89
|
85
|
let src = null;
|
|
90
|
86
|
let img = null;
|
|
91
|
|
- let zoomCallback = null;
|
|
92
|
87
|
|
|
93
|
|
- let zoomer = Panzoom(sourceImage, {
|
|
94
|
|
- excludeClass: 'handle'
|
|
95
|
|
- });
|
|
96
|
|
- sourceImage.addEventListener('panzoomzoom', function(event) {
|
|
97
|
|
- if (zoomCallback != null) zoomCallback(event);
|
|
98
|
|
- });
|
|
99
|
|
-
|
|
|
88
|
+ let wheelCallback = null;
|
|
|
89
|
+ let gestureCallback = null;
|
|
|
90
|
+
|
|
100
|
91
|
sourceImageParent.addEventListener('wheel', function(event) {
|
|
101
|
|
- if (! event.shiftKey) return;
|
|
102
|
|
- zoomer.zoomWithWheel(event, {disablePan: true});
|
|
|
92
|
+ if (wheelCallback != null) {
|
|
|
93
|
+ wheelCallback(event);
|
|
|
94
|
+ }
|
|
|
95
|
+ });
|
|
|
96
|
+
|
|
|
97
|
+ interact(sourceImageParent).gesturable({
|
|
|
98
|
+ onmove: function(event) {
|
|
|
99
|
+ if (gestureCallback != null) {
|
|
|
100
|
+ gestureCallback(event);
|
|
|
101
|
+ }
|
|
|
102
|
+ }
|
|
103
|
103
|
});
|
|
104
|
104
|
|
|
105
|
105
|
fileInput.onchange = function(e) {
|
|
|
@@ -109,8 +109,9 @@
|
|
109
|
109
|
function(canvas) {
|
|
110
|
110
|
let imageWidth = canvas.width;
|
|
111
|
111
|
let imageHeight = canvas.height;
|
|
112
|
|
- let imageScale = sourceImageParent.offsetWidth / imageWidth;
|
|
113
|
|
- sourceImageParent.style.height = Math.round(imageHeight * imageScale) + 'px';
|
|
|
112
|
+ let containerWidth = sourceImageParent.offsetWidth;
|
|
|
113
|
+ let scaledWidth = containerWidth;
|
|
|
114
|
+ sourceImageParent.style.height = Math.round(imageHeight * scaledWidth / imageWidth) + 'px';
|
|
114
|
115
|
|
|
115
|
116
|
if (src != null) {
|
|
116
|
117
|
src.delete();
|
|
|
@@ -127,12 +128,6 @@
|
|
127
|
128
|
}
|
|
128
|
129
|
sourceImage.appendChild(canvas);
|
|
129
|
130
|
|
|
130
|
|
- zoomer.setOptions({
|
|
131
|
|
- minScale: imageScale,
|
|
132
|
|
- maxScale: 1
|
|
133
|
|
- });
|
|
134
|
|
- zoomer.zoomToPoint(imageScale, {clientX: sourceImageParent.offsetLeft, clientY: sourceImageParent.offsetTop});
|
|
135
|
|
-
|
|
136
|
131
|
let overlay = document.createElement('canvas');
|
|
137
|
132
|
overlay.className = 'overlay';
|
|
138
|
133
|
overlay.width = imageWidth;
|
|
|
@@ -145,7 +140,7 @@
|
|
145
|
140
|
|
|
146
|
141
|
let handles = [];
|
|
147
|
142
|
|
|
148
|
|
- function getNewCorners() {
|
|
|
143
|
+ function getCornersFromHandles() {
|
|
149
|
144
|
let corners = [];
|
|
150
|
145
|
handles.forEach(function(handle) {
|
|
151
|
146
|
corners.push(new cv.Point(
|
|
|
@@ -157,8 +152,8 @@
|
|
157
|
152
|
}
|
|
158
|
153
|
|
|
159
|
154
|
function redrawOverlay() {
|
|
160
|
|
- let corners = getNewCorners();
|
|
161
|
|
- context.lineWidth = 1 / zoomer.getScale();
|
|
|
155
|
+ let corners = getCornersFromHandles();
|
|
|
156
|
+ context.lineWidth = imageWidth / scaledWidth;
|
|
162
|
157
|
context.clearRect(0, 0, imageWidth, imageHeight);
|
|
163
|
158
|
context.beginPath();
|
|
164
|
159
|
let corner = corners[corners.length - 1];
|
|
|
@@ -169,27 +164,37 @@
|
|
169
|
164
|
context.stroke();
|
|
170
|
165
|
}
|
|
171
|
166
|
|
|
|
167
|
+ function repositionHandle(handle) {
|
|
|
168
|
+ let scale = scaledWidth / imageWidth;
|
|
|
169
|
+ handle.style.left = (parseFloat(handle.dataset.left) * scale) + 'px';
|
|
|
170
|
+ handle.style.top = (parseFloat(handle.dataset.top) * scale) + 'px';
|
|
|
171
|
+ }
|
|
|
172
|
+
|
|
|
173
|
+ function resize() {
|
|
|
174
|
+ sourceImage.style.width = scaledWidth + 'px';
|
|
|
175
|
+ handles.forEach(repositionHandle);
|
|
|
176
|
+ redrawOverlay();
|
|
|
177
|
+ }
|
|
|
178
|
+
|
|
172
|
179
|
function dragMoveListener(event) {
|
|
173
|
180
|
let target = event.target;
|
|
174
|
|
- let newLeft = Math.min(
|
|
|
181
|
+ let scale = scaledWidth / imageWidth;
|
|
|
182
|
+ target.dataset.left = Math.min(
|
|
175
|
183
|
imageWidth - 1,
|
|
176
|
|
- Math.max(0, parseFloat(target.dataset.left) + event.dx / zoomer.getScale())
|
|
|
184
|
+ Math.max(0, parseFloat(target.dataset.left) + event.dx / scale)
|
|
177
|
185
|
);
|
|
178
|
|
- let newTop = Math.min(
|
|
|
186
|
+ target.dataset.top = Math.min(
|
|
179
|
187
|
imageHeight - 1,
|
|
180
|
|
- Math.max(0, parseFloat(target.dataset.top) + event.dy / zoomer.getScale())
|
|
|
188
|
+ Math.max(0, parseFloat(target.dataset.top) + event.dy / scale)
|
|
181
|
189
|
);
|
|
182
|
|
- target.dataset.left = newLeft;
|
|
183
|
|
- target.dataset.top = newTop;
|
|
184
|
|
- target.style.left = newLeft + 'px';
|
|
185
|
|
- target.style.top = newTop + 'px';
|
|
|
190
|
+ repositionHandle(target);
|
|
186
|
191
|
redrawOverlay();
|
|
187
|
192
|
}
|
|
188
|
193
|
|
|
189
|
194
|
function recalculate() {
|
|
190
|
195
|
let numRows;
|
|
191
|
196
|
let numCols;
|
|
192
|
|
- let corners = getNewCorners();
|
|
|
197
|
+ let corners = getCornersFromHandles();
|
|
193
|
198
|
let square = extractSquare(img, corners);
|
|
194
|
199
|
try {
|
|
195
|
200
|
numRows = getLineFrequency(square, lineDetectorElementSize, 1);
|
|
|
@@ -219,10 +224,23 @@
|
|
219
|
224
|
downloadLink.download = 'crossword_' + moment().format('YYYYMMDD_HHmmss') + '.png';
|
|
220
|
225
|
}
|
|
221
|
226
|
|
|
222
|
|
- document.getElementById('recalculateButton').onclick = recalculate;
|
|
223
|
|
- zoomCallback = function(event) {
|
|
224
|
|
- redrawOverlay();
|
|
225
|
|
- };
|
|
|
227
|
+ function zoomChanged(delta) {
|
|
|
228
|
+ scaledWidth = Math.min(
|
|
|
229
|
+ imageWidth,
|
|
|
230
|
+ Math.max(containerWidth, scaledWidth + delta)
|
|
|
231
|
+ );
|
|
|
232
|
+ resize();
|
|
|
233
|
+ }
|
|
|
234
|
+
|
|
|
235
|
+ recalculateButton.onclick = recalculate;
|
|
|
236
|
+ wheelCallback = function(event) {
|
|
|
237
|
+ if (! event.shiftKey) return;
|
|
|
238
|
+ event.preventDefault();
|
|
|
239
|
+ zoomChanged(-event.deltaY);
|
|
|
240
|
+ }
|
|
|
241
|
+ gestureCallback = function(event) {
|
|
|
242
|
+ zoomChanged(scaledWidth * event.ds);
|
|
|
243
|
+ }
|
|
226
|
244
|
|
|
227
|
245
|
src = cv.imread(canvas);
|
|
228
|
246
|
|
|
|
@@ -261,7 +279,7 @@
|
|
261
|
279
|
handles.push(handle);
|
|
262
|
280
|
});
|
|
263
|
281
|
|
|
264
|
|
- redrawOverlay();
|
|
|
282
|
+ resize();
|
|
265
|
283
|
recalculate();
|
|
266
|
284
|
},
|
|
267
|
285
|
{
|