浏览代码

Working version.

Andrew Klopper 6 年之前
父节点
当前提交
c1af469cba
共有 3 个文件被更改,包括 173 次插入41 次删除
  1. 66 41
      index.html
  2. 1 0
      js/vendor/moment/moment.min.js
  3. 106 0
      js/xword.js

+ 66 - 41
index.html

@@ -7,6 +7,7 @@
7 7
         <script src="js/vendor/load-image/load-image.all.min.js"></script>
8 8
         <script src="js/vendor/plotly/plotly-latest.min.js"></script>
9 9
         <script src="js/vendor/panzoom/panzoom.js"></script>
10
+        <script src="js/vendor/moment/moment.min.js"></script>
10 11
         <script src="js/xword.js"></script>
11 12
         <style>
12 13
             canvas {
@@ -54,6 +55,9 @@
54 55
         <div>
55 56
             <canvas id="output"></canvas>
56 57
         </div>
58
+        <div>
59
+            <a id="download" href="#" download="crossword.png">Download</a>
60
+        </div>
57 61
         <div id="graph"></div>
58 62
         <script>
59 63
             'use strict';
@@ -61,6 +65,8 @@
61 65
             let sourceImage = document.getElementById("sourceImage");
62 66
             let sourceImageParent = sourceImage.parentElement;
63 67
             let fileInput = document.getElementById("fileInput");
68
+            let output = document.getElementById('output');
69
+            let downloadLink = document.getElementById('download');
64 70
 
65 71
             let isSquare = true;
66 72
 
@@ -75,13 +81,12 @@
75 81
             let lineDetectorElementSize = 51;
76 82
 
77 83
             let samplingBlockSizeRatio = 0.25;
78
-            let samplingThresholdQuantile = 0.3;
79
-            let samplingThreshold = null;
80 84
 
81 85
             let gridLineThickness = 4;
82 86
             let gridSquareInternalSize = 64;
83 87
             let gridMarginSize = 20;
84 88
 
89
+            let src = null;
85 90
             let img = null;
86 91
             let zoomCallback = null;
87 92
 
@@ -107,6 +112,11 @@
107 112
                             let imageScale = sourceImageParent.offsetWidth / imageWidth;
108 113
                             sourceImageParent.style.height = Math.round(imageHeight * imageScale) + 'px';
109 114
 
115
+                            if (src != null) {
116
+                                src.delete();
117
+                                src = null;
118
+                            }
119
+
110 120
                             if (img != null) {
111 121
                                 img.delete();
112 122
                                 img = null;
@@ -177,17 +187,36 @@
177 187
                             }
178 188
 
179 189
                             function recalculate() {
180
-                                let square = extractSquare(img, getNewCorners());
190
+                                let numRows;
191
+                                let numCols;
192
+                                let corners = getNewCorners();
193
+                                let square = extractSquare(img, corners);
181 194
                                 try {
182
-                                    let numRows = getLineFrequency(square, lineDetectorElementSize, 1);
183
-                                    let numCols = getLineFrequency(square, lineDetectorElementSize, 0);
195
+                                    numRows = getLineFrequency(square, lineDetectorElementSize, 1);
196
+                                    numCols = getLineFrequency(square, lineDetectorElementSize, 0);
184 197
                                     if (isSquare && (numRows !== numCols)) {
185 198
                                         console.log("WARNING: crossword is not square");
186 199
                                     }
187
-                                    cv.imshow('output', square);
188 200
                                 } finally {
189 201
                                     square.delete();
190 202
                                 }
203
+
204
+                                let blockImg = extractSquare(src, corners);
205
+                                let grid;
206
+                                try {
207
+                                    let gridColours = extractGridColours(blockImg, numRows, numCols, samplingBlockSizeRatio);
208
+                                    let threshold = getGridColourThreshold(gridColours);
209
+                                    grid = gridColoursToBlocks(gridColours, numRows, numCols, threshold);
210
+                                } finally {
211
+                                    blockImg.delete();
212
+                                }
213
+
214
+                                if (grid.warning) {
215
+                                    console.log("WARNING: some blocks may be the wrong colour");
216
+                                }
217
+                                drawGrid(output, grid.blocks, numRows, numCols, gridLineThickness, gridSquareInternalSize, gridMarginSize);
218
+                                downloadLink.href = output.toDataURL("image/png");
219
+                                downloadLink.download = 'crossword_' + moment().format('YYYYMMDD_HHmmss') + '.png';
191 220
                             }
192 221
 
193 222
                             document.getElementById('recalculateButton').onclick = recalculate;
@@ -195,49 +224,45 @@
195 224
                                 redrawOverlay();
196 225
                             };
197 226
 
198
-                            let src = cv.imread(canvas);
199
-                            try {
200
-                                //removeColours(src, src, 48);
201
-                                //cv.imshow('output', src);
227
+                            src = cv.imread(canvas);
202 228
 
203
-                                cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
229
+                            //removeColours(src, src, 48);
230
+                            //cv.imshow('output', src);
204 231
 
205
-                                img = new cv.Mat();
206
-                                preprocessImage(src, img, gaussianBlurSize, adaptiveThresholdBlockSize, adaptiveThresholdMeanAdjustment, numDilations);
207
-                                //cv.imshow('output', img);
208
-                                let corners;
209
-                                let biggest = findBiggestContour(img);
232
+                            cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
233
+
234
+                            img = new cv.Mat();
235
+                            preprocessImage(src, img, gaussianBlurSize, adaptiveThresholdBlockSize, adaptiveThresholdMeanAdjustment, numDilations);
236
+                            //cv.imshow('output', img);
237
+                            let corners;
238
+                            let biggest = findBiggestContour(img);
239
+                            try {
240
+                                let eroded = erodeContour(img.size(), biggest, contourErosionKernelSize, contourErosionIterations);
210 241
                                 try {
211
-                                    let eroded = erodeContour(img.size(), biggest, contourErosionKernelSize, contourErosionIterations);
212
-                                    try {
213
-                                        corners = getContourCorners(img.size(), eroded);
214
-                                    } finally {
215
-                                        eroded.delete();
216
-                                    }
242
+                                    corners = getContourCorners(img.size(), eroded);
217 243
                                 } finally {
218
-                                    biggest.delete();
244
+                                    eroded.delete();
219 245
                                 }
246
+                            } finally {
247
+                                biggest.delete();
248
+                            }
220 249
 
221
-                                corners.forEach(function(corner) {
222
-                                    let handle = document.createElement('div');
223
-                                    handle.className = 'handle';
224
-                                    handle.dataset.left = corner.x;
225
-                                    handle.dataset.top = corner.y;
226
-                                    handle.style.left = handle.dataset.left + 'px';
227
-                                    handle.style.top = handle.dataset.top + 'px';
228
-                                    sourceImage.appendChild(handle);
229
-                                    interact(handle).draggable({
230
-                                        onmove: dragMoveListener
231
-                                    });
232
-                                    handles.push(handle);
250
+                            corners.forEach(function(corner) {
251
+                                let handle = document.createElement('div');
252
+                                handle.className = 'handle';
253
+                                handle.dataset.left = corner.x;
254
+                                handle.dataset.top = corner.y;
255
+                                handle.style.left = handle.dataset.left + 'px';
256
+                                handle.style.top = handle.dataset.top + 'px';
257
+                                sourceImage.appendChild(handle);
258
+                                interact(handle).draggable({
259
+                                    onmove: dragMoveListener
233 260
                                 });
261
+                                handles.push(handle);
262
+                            });
234 263
 
235
-                                redrawOverlay();
236
-                                recalculate();
237
-
238
-                            } finally {
239
-                                src.delete();
240
-                            }
264
+                            redrawOverlay();
265
+                            recalculate();
241 266
                         },
242 267
                         {
243 268
                             orientation: true

文件差异内容过多而无法显示
+ 1 - 0
js/vendor/moment/moment.min.js


+ 106 - 0
js/xword.js

@@ -253,3 +253,109 @@ function getLineFrequency(img, lineDetectorElementSize, axis) {
253 253
         fft.delete();
254 254
     }
255 255
 }
256
+
257
+function extractGridColours(img, numRows, numCols, samplingBlockSizeRatio) {
258
+    let imageSize = img.size();
259
+    let rowOffset = Math.floor(imageSize.height * samplingBlockSizeRatio / numRows / 2);
260
+    let rowHeight = 2 * rowOffset + 1;
261
+    let colOffset = Math.floor(imageSize.width * samplingBlockSizeRatio / numCols / 2);
262
+    let colWidth = 2 * colOffset + 1;
263
+    let gridColours = [];
264
+    for (let row = 0; row < numRows; row++) {
265
+        let line = [];
266
+        let y = Math.floor((row + 0.5) / numRows * imageSize.height) - rowOffset;
267
+        for (let col = 0; col < numCols; col++) {
268
+            let x = Math.floor((col + 0.5) / numCols * imageSize.width) - colOffset;
269
+            let roi = img.roi(new cv.Rect(x, y, colWidth, rowHeight));
270
+            try {
271
+                line.push(cv.mean(roi)[0]);
272
+            } finally {
273
+                roi.delete();
274
+            }
275
+        }
276
+        gridColours.push(line);
277
+    }
278
+    return gridColours;
279
+}
280
+
281
+function getGridColourThreshold(gridColours) {
282
+    let colours = gridColours.reduce(function(acc, x) { return acc.concat(x); }, []).sort(function (a, b) { return a - b; });
283
+    let deltaMax = 0;
284
+    let iMax;
285
+    for (let i = 0; i < colours.length; i++) {
286
+        let delta = colours[i] - colours[i - 1];
287
+        if (delta > deltaMax) {
288
+            deltaMax = delta;
289
+            iMax = i;
290
+        }
291
+    }
292
+    return (colours[iMax] + colours[iMax - 1]) / 2;
293
+}
294
+
295
+function gridColoursToBlocks(gridColours, numRows, numCols, samplingThreshold) {
296
+    let blocks = JSON.parse(JSON.stringify(gridColours));
297
+    let warning = false;
298
+    let midpoint = Math.floor(numRows / 2) + (numRows % 2 > 0 ? 1 : 0);
299
+    for (let row = 0; row < midpoint; row++) {
300
+        for (let col = 0; col < numCols; col++) {
301
+            // If there is an odd number of rows then row and row2 will point to
302
+            // the same row when we reach the middle. Doesn't seem worth adding a
303
+            // special case.
304
+            let row2 = numRows - row - 1;
305
+            let col2 = numCols - col - 1;
306
+            let delta1 = gridColours[row][col] - samplingThreshold;
307
+            let delta2 = gridColours[row2][col2] - samplingThreshold;
308
+            let filled;
309
+            if ((delta1 > 0) && (delta2 > 0)) {
310
+                filled = false;
311
+            } else if ((delta1 < 0) && (delta2 < 0)) {
312
+                filled = true;
313
+            } else {
314
+                warning = true;
315
+                if (Math.abs(delta1) > Math.abs(delta2)) {
316
+                    filled = delta1 < 0;
317
+                } else {
318
+                    filled = delta2 < 0;
319
+                }
320
+            }
321
+            blocks[row][col] = {filled: filled}
322
+            blocks[row2][col2] = {filled: filled}
323
+        }
324
+    }
325
+
326
+    let number = 1
327
+    for (let row = 0; row < numRows; row++) {
328
+        for (let col = 0; col < numCols; col++) {
329
+            if (! blocks[row][col].filled && (
330
+                (((col == 0) || blocks[row][col - 1].filled) && (col < numCols - 1) && ! blocks[row][col + 1].filled) ||
331
+                (((row == 0) || blocks[row - 1][col].filled) && (row < numRows - 1) && ! blocks[row + 1][col].filled)
332
+            )) {
333
+                blocks[row][col].number = number
334
+                number += 1
335
+            }
336
+        }
337
+    }
338
+
339
+    return {warning: warning, blocks: blocks};
340
+}
341
+
342
+function drawGrid(canvas, blocks, numRows, numCols, gridLineThickness, gridSquareSize, gridBorderSize) {
343
+    let step = gridSquareSize + gridLineThickness;
344
+    let gridHeight = numRows * step + gridLineThickness;
345
+    let gridWidth = numCols * step + gridLineThickness;
346
+    canvas.width = 2 * gridBorderSize + gridWidth;
347
+    canvas.height = 2 * gridBorderSize + gridHeight;
348
+    let context = canvas.getContext('2d');
349
+    context.fillStyle = 'black';
350
+    context.fillRect(gridBorderSize, gridBorderSize, gridWidth, gridHeight);
351
+    context.fillStyle = 'white';
352
+    for (let row = 0; row < numRows; row++) {
353
+        let y = row * step + gridLineThickness + gridBorderSize;
354
+        for (let col = 0; col < numCols; col++) {
355
+            if (! blocks[row][col].filled) {
356
+                let x = col * step + gridLineThickness + gridBorderSize;
357
+                context.fillRect(x, y, gridSquareSize, gridSquareSize);
358
+            }
359
+        }
360
+    }
361
+}