@pallasoft/ps-file-components 1.5.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/dist/components/ps-document-scanner-loader.d.ts +1 -1
  2. package/dist/components/ps-document-scanner-sheet.d.ts +1 -1
  3. package/dist/components/ps-document-scanner-sheet.js +40 -110
  4. package/dist/components/ps-document-scanner.d.ts +1 -1
  5. package/dist/components/ps-document-scanner.js +2 -3
  6. package/dist/components/ps-file-delete-confirm.js +1 -1
  7. package/dist/components/ps-scan-camera-view.d.ts +2 -9
  8. package/dist/components/ps-scan-camera-view.js +3 -12
  9. package/dist/components/ps-scan-crop-editor.d.ts +4 -4
  10. package/dist/components/ps-scan-crop-editor.js +9 -9
  11. package/dist/components/ps-scan-permission-view.d.ts +2 -3
  12. package/dist/components/ps-scan-permission-view.js +2 -2
  13. package/dist/components/ps-scan-photo-button.d.ts +2 -3
  14. package/dist/components/ps-scan-photo-button.js +5 -5
  15. package/dist/components/ps-scan-preview.d.ts +12 -0
  16. package/dist/components/ps-scan-preview.js +10 -0
  17. package/dist/components/ps-scan-shutter.d.ts +1 -2
  18. package/dist/components/ps-scan-shutter.js +1 -1
  19. package/dist/components/ps-upload-modal.js +1 -1
  20. package/dist/constants/ps-document-scanner-labels.d.ts +5 -34
  21. package/dist/constants/ps-document-scanner-labels.js +5 -34
  22. package/dist/constants/ps-scan-chrome-classes.d.ts +0 -2
  23. package/dist/constants/ps-scan-chrome-classes.js +0 -2
  24. package/dist/constants/ps-scan-config.d.ts +24 -31
  25. package/dist/constants/ps-scan-config.js +24 -31
  26. package/dist/constants/ps-scan-step.d.ts +1 -1
  27. package/dist/constants/ps-scan-step.js +1 -1
  28. package/dist/esm/components/ps-document-scanner-sheet.js +41 -111
  29. package/dist/esm/components/ps-document-scanner.js +2 -3
  30. package/dist/esm/components/ps-file-delete-confirm.js +2 -2
  31. package/dist/esm/components/ps-scan-camera-view.js +3 -12
  32. package/dist/esm/components/ps-scan-crop-editor.js +9 -9
  33. package/dist/esm/components/ps-scan-permission-view.js +2 -2
  34. package/dist/esm/components/ps-scan-photo-button.js +5 -5
  35. package/dist/esm/components/ps-scan-preview.js +6 -0
  36. package/dist/esm/components/ps-scan-shutter.js +1 -1
  37. package/dist/esm/components/ps-upload-modal.js +2 -2
  38. package/dist/esm/constants/ps-document-scanner-labels.js +5 -34
  39. package/dist/esm/constants/ps-scan-chrome-classes.js +0 -2
  40. package/dist/esm/constants/ps-scan-config.js +24 -31
  41. package/dist/esm/constants/ps-scan-step.js +1 -1
  42. package/dist/esm/hooks/use-ps-scan-live-detection.js +17 -21
  43. package/dist/esm/hooks/use-ps-scan-photo.js +42 -0
  44. package/dist/esm/index.js +0 -2
  45. package/dist/esm/services/ps-scan-camera-service.js +2 -2
  46. package/dist/esm/services/ps-scan-canny-service.js +93 -0
  47. package/dist/esm/services/ps-scan-canvas-service.js +7 -1
  48. package/dist/esm/services/ps-scan-edge-service.js +73 -137
  49. package/dist/esm/services/ps-scan-file-name-service.js +2 -11
  50. package/dist/esm/services/ps-scan-geometry-service.js +36 -0
  51. package/dist/esm/services/ps-scan-hull-service.js +0 -39
  52. package/dist/esm/services/ps-scan-image-filter-service.js +0 -21
  53. package/dist/esm/services/ps-scan-morphology-service.js +44 -0
  54. package/dist/esm/services/ps-scan-pipeline-service.js +9 -19
  55. package/dist/esm/services/ps-scan-polygon-service.js +75 -0
  56. package/dist/esm/services/ps-scan-region-service.js +52 -0
  57. package/dist/esm/services/ps-scan-stability-service.js +9 -22
  58. package/dist/esm/services/ps-scan-warp-service.js +8 -2
  59. package/dist/esm/services/ps-scan-yield-service.js +5 -0
  60. package/dist/hooks/use-ps-scan-live-detection.d.ts +1 -1
  61. package/dist/hooks/use-ps-scan-live-detection.js +16 -20
  62. package/dist/hooks/use-ps-scan-photo.d.ts +9 -0
  63. package/dist/hooks/use-ps-scan-photo.js +46 -0
  64. package/dist/index.d.ts +0 -2
  65. package/dist/index.js +0 -2
  66. package/dist/models/ps-document-scanner-props.d.ts +0 -4
  67. package/dist/models/ps-document-scanner-sheet-props.d.ts +0 -4
  68. package/dist/models/ps-scan-gradient.d.ts +4 -0
  69. package/dist/models/ps-scan-live-detection.d.ts +0 -1
  70. package/dist/models/ps-scan-located.d.ts +5 -0
  71. package/dist/models/ps-scan-photo.d.ts +9 -0
  72. package/dist/models/ps-scan-region.d.ts +5 -0
  73. package/dist/models/ps-scan-result.d.ts +6 -0
  74. package/dist/models/ps-scan-stability.d.ts +0 -3
  75. package/dist/services/ps-scan-camera-service.js +2 -2
  76. package/dist/services/ps-scan-canny-service.d.ts +7 -0
  77. package/dist/services/ps-scan-canny-service.js +97 -0
  78. package/dist/services/ps-scan-canvas-service.js +7 -1
  79. package/dist/services/ps-scan-edge-service.d.ts +5 -5
  80. package/dist/services/ps-scan-edge-service.js +73 -137
  81. package/dist/services/ps-scan-file-name-service.d.ts +1 -3
  82. package/dist/services/ps-scan-file-name-service.js +2 -11
  83. package/dist/services/ps-scan-geometry-service.d.ts +2 -0
  84. package/dist/services/ps-scan-geometry-service.js +36 -0
  85. package/dist/services/ps-scan-hull-service.d.ts +0 -2
  86. package/dist/services/ps-scan-hull-service.js +0 -39
  87. package/dist/services/ps-scan-image-filter-service.d.ts +0 -1
  88. package/dist/services/ps-scan-image-filter-service.js +0 -21
  89. package/dist/services/ps-scan-morphology-service.d.ts +7 -0
  90. package/dist/services/ps-scan-morphology-service.js +48 -0
  91. package/dist/services/ps-scan-pipeline-service.d.ts +2 -5
  92. package/dist/services/ps-scan-pipeline-service.js +9 -19
  93. package/dist/services/ps-scan-polygon-service.d.ts +8 -0
  94. package/dist/services/ps-scan-polygon-service.js +79 -0
  95. package/dist/services/ps-scan-region-service.d.ts +4 -0
  96. package/dist/services/ps-scan-region-service.js +56 -0
  97. package/dist/services/ps-scan-stability-service.d.ts +1 -3
  98. package/dist/services/ps-scan-stability-service.js +9 -22
  99. package/dist/services/ps-scan-warp-service.d.ts +1 -1
  100. package/dist/services/ps-scan-warp-service.js +8 -2
  101. package/dist/services/ps-scan-yield-service.d.ts +3 -0
  102. package/dist/services/ps-scan-yield-service.js +9 -0
  103. package/package.json +3 -5
  104. package/dist/components/ps-scan-page-list.d.ts +0 -14
  105. package/dist/components/ps-scan-page-list.js +0 -22
  106. package/dist/components/ps-scan-page-stack.d.ts +0 -7
  107. package/dist/components/ps-scan-page-stack.js +0 -6
  108. package/dist/components/ps-scan-review.d.ts +0 -24
  109. package/dist/components/ps-scan-review.js +0 -37
  110. package/dist/constants/ps-scan-filter.d.ts +0 -7
  111. package/dist/constants/ps-scan-filter.js +0 -9
  112. package/dist/constants/ps-scan-output-format.d.ts +0 -5
  113. package/dist/constants/ps-scan-output-format.js +0 -7
  114. package/dist/esm/components/ps-scan-page-list.js +0 -18
  115. package/dist/esm/components/ps-scan-page-stack.js +0 -2
  116. package/dist/esm/components/ps-scan-review.js +0 -33
  117. package/dist/esm/constants/ps-scan-filter.js +0 -6
  118. package/dist/esm/constants/ps-scan-output-format.js +0 -4
  119. package/dist/esm/hooks/use-ps-scan-pages.js +0 -62
  120. package/dist/esm/hooks/use-ps-scan-previews.js +0 -57
  121. package/dist/esm/services/ps-scan-enhance-service.js +0 -97
  122. package/dist/esm/services/ps-scan-order-service.js +0 -13
  123. package/dist/esm/services/ps-scan-output-service.js +0 -24
  124. package/dist/esm/services/ps-scan-pdf-service.js +0 -56
  125. package/dist/hooks/use-ps-scan-pages.d.ts +0 -12
  126. package/dist/hooks/use-ps-scan-pages.js +0 -66
  127. package/dist/hooks/use-ps-scan-previews.d.ts +0 -2
  128. package/dist/hooks/use-ps-scan-previews.js +0 -61
  129. package/dist/models/ps-scan-added-page.d.ts +0 -5
  130. package/dist/models/ps-scan-levels.d.ts +0 -4
  131. package/dist/models/ps-scan-output-options.d.ts +0 -7
  132. package/dist/models/ps-scan-page.d.ts +0 -12
  133. package/dist/models/ps-scan-pdf-page.d.ts +0 -5
  134. package/dist/services/ps-scan-enhance-service.d.ts +0 -9
  135. package/dist/services/ps-scan-enhance-service.js +0 -101
  136. package/dist/services/ps-scan-order-service.d.ts +0 -4
  137. package/dist/services/ps-scan-order-service.js +0 -17
  138. package/dist/services/ps-scan-output-service.d.ts +0 -5
  139. package/dist/services/ps-scan-output-service.js +0 -28
  140. package/dist/services/ps-scan-pdf-service.d.ts +0 -5
  141. package/dist/services/ps-scan-pdf-service.js +0 -60
  142. /package/dist/esm/models/{ps-scan-added-page.js → ps-scan-gradient.js} +0 -0
  143. /package/dist/esm/models/{ps-scan-levels.js → ps-scan-located.js} +0 -0
  144. /package/dist/esm/models/{ps-scan-output-options.js → ps-scan-photo.js} +0 -0
  145. /package/dist/esm/models/{ps-scan-page.js → ps-scan-region.js} +0 -0
  146. /package/dist/esm/models/{ps-scan-pdf-page.js → ps-scan-result.js} +0 -0
  147. /package/dist/models/{ps-scan-added-page.js → ps-scan-gradient.js} +0 -0
  148. /package/dist/models/{ps-scan-levels.js → ps-scan-located.js} +0 -0
  149. /package/dist/models/{ps-scan-output-options.js → ps-scan-photo.js} +0 -0
  150. /package/dist/models/{ps-scan-page.js → ps-scan-region.js} +0 -0
  151. /package/dist/models/{ps-scan-pdf-page.js → ps-scan-result.js} +0 -0
@@ -2,159 +2,95 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsScanEdgeService = void 0;
4
4
  const ps_scan_config_1 = require("../constants/ps-scan-config");
5
+ const ps_scan_canny_service_1 = require("./ps-scan-canny-service");
5
6
  const ps_scan_geometry_service_1 = require("./ps-scan-geometry-service");
6
7
  const ps_scan_hull_service_1 = require("./ps-scan-hull-service");
7
8
  const ps_scan_image_filter_service_1 = require("./ps-scan-image-filter-service");
9
+ const ps_scan_morphology_service_1 = require("./ps-scan-morphology-service");
10
+ const ps_scan_polygon_service_1 = require("./ps-scan-polygon-service");
8
11
  const ps_scan_raster_service_1 = require("./ps-scan-raster-service");
12
+ const ps_scan_region_service_1 = require("./ps-scan-region-service");
13
+ const ps_scan_yield_service_1 = require("./ps-scan-yield-service");
9
14
  class PsScanEdgeService {
10
- static detect(source) {
11
- const scale = Math.min(1, ps_scan_config_1.PsScanConfig.detectMaxDimension / Math.max(source.width, source.height));
12
- const width = Math.max(8, Math.round(source.width * scale));
13
- const height = Math.max(8, Math.round(source.height * scale));
15
+ static async detect(source, maxDimension) {
16
+ const scale = Math.min(1, maxDimension / Math.max(source.width, source.height));
17
+ const width = Math.max(16, Math.round(source.width * scale));
18
+ const height = Math.max(16, Math.round(source.height * scale));
14
19
  const raster = ps_scan_raster_service_1.PsScanRasterService.resize(source, width, height);
15
20
  const gray = ps_scan_image_filter_service_1.PsScanImageFilterService.blur(ps_scan_image_filter_service_1.PsScanImageFilterService.grayscale(raster), width, height);
16
- const edges = ps_scan_image_filter_service_1.PsScanImageFilterService.sobel(gray, width, height);
17
- let mask = PsScanEdgeService.threshold(edges);
18
- for (let step = 0; step < ps_scan_config_1.PsScanConfig.detectDilations; step += 1)
19
- mask = PsScanEdgeService.dilate(mask, width, height);
20
- const foreground = PsScanEdgeService.foreground(mask, width, height);
21
- const component = PsScanEdgeService.largestComponent(foreground, width, height);
22
- if (component.length < 8)
21
+ await ps_scan_yield_service_1.PsScanYieldService.next();
22
+ const edges = ps_scan_canny_service_1.PsScanCannyService.edges(gray, width, height);
23
+ for (let index = 0; index < width; index += 1) {
24
+ edges[index] = 1;
25
+ edges[(height - 1) * width + index] = 1;
26
+ }
27
+ for (let index = 0; index < height; index += 1) {
28
+ edges[index * width] = 1;
29
+ edges[index * width + width - 1] = 1;
30
+ }
31
+ let best = null;
32
+ let bestArea = 0;
33
+ for (const radius of ps_scan_config_1.PsScanConfig.closeRadii) {
34
+ await ps_scan_yield_service_1.PsScanYieldService.next();
35
+ const closed = ps_scan_morphology_service_1.PsScanMorphologyService.close(edges, width, height, radius);
36
+ const regions = ps_scan_region_service_1.PsScanRegionService.enclosed(closed, width, height, width * height * ps_scan_config_1.PsScanConfig.detectMinAreaRatio * 0.5);
37
+ for (const region of regions) {
38
+ const quad = PsScanEdgeService.candidate(region.boundary, width, height);
39
+ if (quad === null)
40
+ continue;
41
+ const area = ps_scan_geometry_service_1.PsScanGeometryService.area(quad);
42
+ if (area > bestArea) {
43
+ bestArea = area;
44
+ best = quad;
45
+ }
46
+ }
47
+ }
48
+ if (best === null)
23
49
  return null;
24
- const hull = ps_scan_hull_service_1.PsScanHullService.convexHull(component);
50
+ return PsScanEdgeService.normalise(best, width, height);
51
+ }
52
+ static async locate(source, maxDimension) {
53
+ const quad = await PsScanEdgeService.detect(source, maxDimension);
54
+ return quad === null
55
+ ? { isDetected: false, quad: ps_scan_geometry_service_1.PsScanGeometryService.defaultQuad() }
56
+ : { isDetected: true, quad };
57
+ }
58
+ static candidate(boundary, width, height) {
59
+ const points = boundary.map((point) => ({ x: point.x + 0.5, y: point.y + 0.5 }));
60
+ const hull = ps_scan_hull_service_1.PsScanHullService.convexHull(points);
25
61
  if (hull.length < 4)
26
62
  return null;
27
- const quad = ps_scan_geometry_service_1.PsScanGeometryService.order(ps_scan_hull_service_1.PsScanHullService.bestQuad(hull));
28
- const imageArea = width * height;
29
- const quadArea = ps_scan_geometry_service_1.PsScanGeometryService.area(quad);
30
- const hullArea = ps_scan_geometry_service_1.PsScanGeometryService.area(hull);
31
- if (quadArea / imageArea < ps_scan_config_1.PsScanConfig.detectMinAreaRatio)
63
+ const corners = ps_scan_polygon_service_1.PsScanPolygonService.toQuad(hull);
64
+ if (corners === null)
65
+ return null;
66
+ const ordered = ps_scan_geometry_service_1.PsScanGeometryService.order(corners);
67
+ const margin = ps_scan_config_1.PsScanConfig.detectBorderMargin;
68
+ const onBorder = ordered.filter((point) => point.x <= margin ||
69
+ point.y <= margin ||
70
+ point.x >= width - margin ||
71
+ point.y >= height - margin);
72
+ if (onBorder.length >= 2)
32
73
  return null;
33
- if (quadArea / imageArea > ps_scan_config_1.PsScanConfig.detectMaxAreaRatio)
74
+ const quad = ps_scan_geometry_service_1.PsScanGeometryService.expand(ordered, ps_scan_config_1.PsScanConfig.detectExpand);
75
+ const imageArea = width * height;
76
+ const area = ps_scan_geometry_service_1.PsScanGeometryService.area(quad);
77
+ const fill = ps_scan_geometry_service_1.PsScanGeometryService.area(ordered) / ps_scan_geometry_service_1.PsScanGeometryService.area(hull);
78
+ const ratio = area / imageArea;
79
+ if (ratio < ps_scan_config_1.PsScanConfig.detectMinAreaRatio || ratio > ps_scan_config_1.PsScanConfig.detectMaxAreaRatio)
34
80
  return null;
35
- if (hullArea > 0 && quadArea / hullArea < ps_scan_config_1.PsScanConfig.detectMinHullFill)
81
+ if (fill < ps_scan_config_1.PsScanConfig.detectMinQuadFill)
36
82
  return null;
37
83
  if (!ps_scan_geometry_service_1.PsScanGeometryService.isConvex(quad))
38
84
  return null;
39
- return ps_scan_geometry_service_1.PsScanGeometryService.inset(quad, ps_scan_config_1.PsScanConfig.detectInset).map((point) => ({
40
- x: Math.min(1, Math.max(0, point.x / (width - 1))),
41
- y: Math.min(1, Math.max(0, point.y / (height - 1))),
42
- }));
43
- }
44
- static dilate(mask, width, height) {
45
- const out = new Uint8Array(mask.length);
46
- for (let y = 0; y < height; y += 1)
47
- for (let x = 0; x < width; x += 1) {
48
- if (mask[y * width + x] === 0)
49
- continue;
50
- for (let dy = -1; dy <= 1; dy += 1)
51
- for (let dx = -1; dx <= 1; dx += 1) {
52
- const nx = x + dx;
53
- const ny = y + dy;
54
- if (nx >= 0 && ny >= 0 && nx < width && ny < height)
55
- out[ny * width + nx] = 1;
56
- }
57
- }
58
- return out;
59
- }
60
- static foreground(mask, width, height) {
61
- const background = new Uint8Array(mask.length);
62
- const stack = [];
63
- const seed = (index) => {
64
- if (mask[index] === 0 && background[index] === 0) {
65
- background[index] = 1;
66
- stack.push(index);
67
- }
68
- };
69
- for (let x = 0; x < width; x += 1) {
70
- seed(x);
71
- seed((height - 1) * width + x);
72
- }
73
- for (let y = 0; y < height; y += 1) {
74
- seed(y * width);
75
- seed(y * width + width - 1);
76
- }
77
- while (stack.length > 0) {
78
- const index = stack.pop();
79
- if (index === undefined)
80
- break;
81
- const x = index % width;
82
- const y = (index - x) / width;
83
- if (x > 0)
84
- seed(index - 1);
85
- if (x < width - 1)
86
- seed(index + 1);
87
- if (y > 0)
88
- seed(index - width);
89
- if (y < height - 1)
90
- seed(index + width);
91
- }
92
- return background.map((value) => 1 - value);
93
- }
94
- static largestComponent(mask, width, height) {
95
- const seen = new Uint8Array(mask.length);
96
- let best = [];
97
- let bestSize = 0;
98
- for (let start = 0; start < mask.length; start += 1) {
99
- if (mask[start] === 0 || seen[start] === 1)
100
- continue;
101
- const members = [];
102
- const stack = [start];
103
- let size = 0;
104
- seen[start] = 1;
105
- while (stack.length > 0) {
106
- const index = stack.pop();
107
- if (index === undefined)
108
- break;
109
- const x = index % width;
110
- const y = (index - x) / width;
111
- let isBoundary = false;
112
- size += 1;
113
- for (let dy = -1; dy <= 1; dy += 1)
114
- for (let dx = -1; dx <= 1; dx += 1) {
115
- if (dx === 0 && dy === 0)
116
- continue;
117
- const nx = x + dx;
118
- const ny = y + dy;
119
- if (nx < 0 || ny < 0 || nx >= width || ny >= height) {
120
- isBoundary = true;
121
- continue;
122
- }
123
- const next = ny * width + nx;
124
- if (mask[next] === 0)
125
- isBoundary = true;
126
- else if (seen[next] === 0) {
127
- seen[next] = 1;
128
- stack.push(next);
129
- }
130
- }
131
- if (isBoundary)
132
- members.push({ x, y });
133
- }
134
- if (size > bestSize) {
135
- bestSize = size;
136
- best = members;
137
- }
138
- }
139
- return best;
85
+ const shortSide = Math.min(width, height) * ps_scan_config_1.PsScanConfig.detectMinSideRatio;
86
+ const isLongEnough = quad.every((point, index) => ps_scan_geometry_service_1.PsScanGeometryService.distance(point, quad[(index + 1) % quad.length]) >= shortSide);
87
+ if (!isLongEnough)
88
+ return null;
89
+ const isSane = ps_scan_geometry_service_1.PsScanGeometryService.angles(quad).every((angle) => angle >= ps_scan_config_1.PsScanConfig.detectMinAngle && angle <= ps_scan_config_1.PsScanConfig.detectMaxAngle);
90
+ return isSane ? quad : null;
140
91
  }
141
- static threshold(edges) {
142
- const bins = new Uint32Array(512);
143
- edges.forEach((value) => {
144
- bins[Math.min(511, value)] += 1;
145
- });
146
- const target = edges.length * ps_scan_config_1.PsScanConfig.detectPercentile;
147
- let cumulative = 0;
148
- let high = 0;
149
- for (let bin = 0; bin < bins.length; bin += 1) {
150
- cumulative += bins[bin];
151
- if (cumulative >= target) {
152
- high = bin;
153
- break;
154
- }
155
- }
156
- const limit = Math.max(ps_scan_config_1.PsScanConfig.detectMinEdgeStrength, high * ps_scan_config_1.PsScanConfig.detectRelativeStrength);
157
- return Uint8Array.from(edges, (value) => (value >= limit ? 1 : 0));
92
+ static normalise(quad, width, height) {
93
+ return quad.map((point) => ps_scan_geometry_service_1.PsScanGeometryService.clamp({ x: point.x / width, y: point.y / height }));
158
94
  }
159
95
  }
160
96
  exports.PsScanEdgeService = PsScanEdgeService;
@@ -1,6 +1,4 @@
1
- import { PsScanOutputFormat } from '../constants/ps-scan-output-format';
2
1
  export declare class PsScanFileNameService {
3
- static build(base: string, format: PsScanOutputFormat, index: number, count: number): string;
4
- static mimeType(format: PsScanOutputFormat): string;
2
+ static build(base: string): string;
5
3
  static sanitize(base: string): string;
6
4
  }
@@ -2,18 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsScanFileNameService = void 0;
4
4
  const ps_scan_config_1 = require("../constants/ps-scan-config");
5
- const ps_scan_output_format_1 = require("../constants/ps-scan-output-format");
6
5
  class PsScanFileNameService {
7
- static build(base, format, index, count) {
8
- const stem = PsScanFileNameService.sanitize(base);
9
- if (format === ps_scan_output_format_1.PsScanOutputFormat.Pdf)
10
- return `${stem}.pdf`;
11
- if (count <= 1)
12
- return `${stem}.jpg`;
13
- return `${stem}-${String(index + 1).padStart(String(count).length, '0')}.jpg`;
14
- }
15
- static mimeType(format) {
16
- return format === ps_scan_output_format_1.PsScanOutputFormat.Pdf ? 'application/pdf' : 'image/jpeg';
6
+ static build(base) {
7
+ return `${PsScanFileNameService.sanitize(base)}.jpg`;
17
8
  }
18
9
  static sanitize(base) {
19
10
  const cleaned = Array.from(base)
@@ -2,11 +2,13 @@ import type { PsPageSize } from '../models/ps-page-size';
2
2
  import type { PsPoint } from '../models/ps-point';
3
3
  import type { PsScanQuad } from '../models/ps-scan-quad';
4
4
  export declare class PsScanGeometryService {
5
+ static angles(quad: PsPoint[]): number[];
5
6
  static area(points: PsPoint[]): number;
6
7
  static clamp(point: PsPoint): PsPoint;
7
8
  static cover(container: PsPageSize, content: PsPageSize): PsPageSize;
8
9
  static defaultQuad(): PsScanQuad;
9
10
  static distance(from: PsPoint, to: PsPoint): number;
11
+ static expand(quad: PsScanQuad, distance: number): PsScanQuad;
10
12
  static fit(container: PsPageSize, content: PsPageSize): PsPageSize;
11
13
  static fitWithin(size: PsPageSize, maxDimension: number): PsPageSize;
12
14
  static inset(quad: PsScanQuad, distance: number): PsScanQuad;
@@ -3,6 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsScanGeometryService = void 0;
4
4
  const ps_scan_config_1 = require("../constants/ps-scan-config");
5
5
  class PsScanGeometryService {
6
+ static angles(quad) {
7
+ return quad.map((point, index) => {
8
+ const before = quad[(index + quad.length - 1) % quad.length];
9
+ const after = quad[(index + 1) % quad.length];
10
+ const ax = before.x - point.x;
11
+ const ay = before.y - point.y;
12
+ const bx = after.x - point.x;
13
+ const by = after.y - point.y;
14
+ const cosine = (ax * bx + ay * by) / (Math.hypot(ax, ay) * Math.hypot(bx, by));
15
+ return (Math.acos(Math.min(1, Math.max(-1, cosine))) * 180) / Math.PI;
16
+ });
17
+ }
6
18
  static area(points) {
7
19
  let sum = 0;
8
20
  points.forEach((point, index) => {
@@ -32,6 +44,30 @@ class PsScanGeometryService {
32
44
  static distance(from, to) {
33
45
  return Math.hypot(to.x - from.x, to.y - from.y);
34
46
  }
47
+ static expand(quad, distance) {
48
+ const lines = quad.map((point, index) => {
49
+ const next = quad[(index + 1) % quad.length];
50
+ const length = PsScanGeometryService.distance(point, next);
51
+ const nx = (next.y - point.y) / length;
52
+ const ny = -(next.x - point.x) / length;
53
+ return {
54
+ dx: next.x - point.x,
55
+ dy: next.y - point.y,
56
+ x: point.x + nx * distance,
57
+ y: point.y + ny * distance,
58
+ };
59
+ });
60
+ return quad.map((point, index) => {
61
+ const previous = lines[(index + lines.length - 1) % lines.length];
62
+ const current = lines[index];
63
+ const denominator = previous.dx * current.dy - previous.dy * current.dx;
64
+ if (Math.abs(denominator) < 1e-9)
65
+ return point;
66
+ const t = ((current.x - previous.x) * current.dy - (current.y - previous.y) * current.dx) /
67
+ denominator;
68
+ return { x: previous.x + previous.dx * t, y: previous.y + previous.dy * t };
69
+ });
70
+ }
35
71
  static fit(container, content) {
36
72
  if (container.width <= 0 || container.height <= 0 || content.width <= 0 || content.height <= 0)
37
73
  return { height: 0, width: 0 };
@@ -1,6 +1,4 @@
1
1
  import type { PsPoint } from '../models/ps-point';
2
2
  export declare class PsScanHullService {
3
- static bestQuad(hull: PsPoint[]): PsPoint[];
4
3
  static convexHull(input: PsPoint[]): PsPoint[];
5
- private static reduce;
6
4
  }
@@ -1,28 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsScanHullService = void 0;
4
- const ps_scan_config_1 = require("../constants/ps-scan-config");
5
- const ps_scan_geometry_service_1 = require("./ps-scan-geometry-service");
6
4
  class PsScanHullService {
7
- static bestQuad(hull) {
8
- const points = PsScanHullService.reduce(hull, ps_scan_config_1.PsScanConfig.hullCandidateLimit);
9
- if (points.length <= 4)
10
- return points;
11
- let best = points.slice(0, 4);
12
- let bestArea = 0;
13
- for (let a = 0; a < points.length - 3; a += 1)
14
- for (let b = a + 1; b < points.length - 2; b += 1)
15
- for (let c = b + 1; c < points.length - 1; c += 1)
16
- for (let d = c + 1; d < points.length; d += 1) {
17
- const candidate = [points[a], points[b], points[c], points[d]];
18
- const area = ps_scan_geometry_service_1.PsScanGeometryService.area(candidate);
19
- if (area > bestArea) {
20
- bestArea = area;
21
- best = candidate;
22
- }
23
- }
24
- return best;
25
- }
26
5
  static convexHull(input) {
27
6
  const points = [...input].sort((a, b) => (a.x === b.x ? a.y - b.y : a.x - b.x));
28
7
  if (points.length < 3)
@@ -41,23 +20,5 @@ class PsScanHullService {
41
20
  };
42
21
  return [...build(points), ...build([...points].reverse())];
43
22
  }
44
- static reduce(hull, limit) {
45
- const points = [...hull];
46
- while (points.length > limit) {
47
- let weakest = 0;
48
- let weakestArea = Infinity;
49
- points.forEach((point, index) => {
50
- const previous = points[(index - 1 + points.length) % points.length];
51
- const next = points[(index + 1) % points.length];
52
- const area = ps_scan_geometry_service_1.PsScanGeometryService.area([previous, point, next]);
53
- if (area < weakestArea) {
54
- weakestArea = area;
55
- weakest = index;
56
- }
57
- });
58
- points.splice(weakest, 1);
59
- }
60
- return points;
61
- }
62
23
  }
63
24
  exports.PsScanHullService = PsScanHullService;
@@ -2,5 +2,4 @@ import type { PsScanRaster } from '../models/ps-scan-raster';
2
2
  export declare class PsScanImageFilterService {
3
3
  static blur(gray: Uint8ClampedArray, width: number, height: number): Uint8ClampedArray;
4
4
  static grayscale(raster: PsScanRaster): Uint8ClampedArray;
5
- static sobel(gray: Uint8ClampedArray, width: number, height: number): Uint16Array;
6
5
  }
@@ -31,26 +31,5 @@ class PsScanImageFilterService {
31
31
  }
32
32
  return out;
33
33
  }
34
- static sobel(gray, width, height) {
35
- const out = new Uint16Array(width * height);
36
- for (let y = 1; y < height - 1; y += 1)
37
- for (let x = 1; x < width - 1; x += 1) {
38
- const i = y * width + x;
39
- const gx = -gray[i - width - 1] +
40
- gray[i - width + 1] -
41
- 2 * gray[i - 1] +
42
- 2 * gray[i + 1] -
43
- gray[i + width - 1] +
44
- gray[i + width + 1];
45
- const gy = -gray[i - width - 1] -
46
- 2 * gray[i - width] -
47
- gray[i - width + 1] +
48
- gray[i + width - 1] +
49
- 2 * gray[i + width] +
50
- gray[i + width + 1];
51
- out[i] = Math.hypot(gx, gy) / 4;
52
- }
53
- return out;
54
- }
55
34
  }
56
35
  exports.PsScanImageFilterService = PsScanImageFilterService;
@@ -0,0 +1,7 @@
1
+ export declare class PsScanMorphologyService {
2
+ static close(mask: Uint8Array, width: number, height: number, radius: number): Uint8Array;
3
+ static dilate(mask: Uint8Array, width: number, height: number, radius: number): Uint8Array;
4
+ static erode(mask: Uint8Array, width: number, height: number, radius: number): Uint8Array;
5
+ private static horizontal;
6
+ private static vertical;
7
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsScanMorphologyService = void 0;
4
+ class PsScanMorphologyService {
5
+ static close(mask, width, height, radius) {
6
+ return PsScanMorphologyService.erode(PsScanMorphologyService.dilate(mask, width, height, radius), width, height, radius);
7
+ }
8
+ static dilate(mask, width, height, radius) {
9
+ return PsScanMorphologyService.vertical(PsScanMorphologyService.horizontal(mask, width, height, radius, 1), width, height, radius, 1);
10
+ }
11
+ static erode(mask, width, height, radius) {
12
+ return PsScanMorphologyService.vertical(PsScanMorphologyService.horizontal(mask, width, height, radius, 0), width, height, radius, 0);
13
+ }
14
+ static horizontal(mask, width, height, radius, target) {
15
+ const out = new Uint8Array(mask.length);
16
+ for (let y = 0; y < height; y += 1)
17
+ for (let x = 0; x < width; x += 1) {
18
+ const from = Math.max(0, x - radius);
19
+ const to = Math.min(width - 1, x + radius);
20
+ let hit = target === 1 ? 0 : 1;
21
+ for (let at = from; at <= to; at += 1)
22
+ if (mask[y * width + at] === target) {
23
+ hit = target;
24
+ break;
25
+ }
26
+ out[y * width + x] = hit;
27
+ }
28
+ return out;
29
+ }
30
+ static vertical(mask, width, height, radius, target) {
31
+ const out = new Uint8Array(mask.length);
32
+ for (let y = 0; y < height; y += 1) {
33
+ const from = Math.max(0, y - radius);
34
+ const to = Math.min(height - 1, y + radius);
35
+ for (let x = 0; x < width; x += 1) {
36
+ let hit = target === 1 ? 0 : 1;
37
+ for (let at = from; at <= to; at += 1)
38
+ if (mask[at * width + x] === target) {
39
+ hit = target;
40
+ break;
41
+ }
42
+ out[y * width + x] = hit;
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+ }
48
+ exports.PsScanMorphologyService = PsScanMorphologyService;
@@ -1,10 +1,7 @@
1
- import type { PsScanPage } from '../models/ps-scan-page';
2
1
  import type { PsScanPrepared } from '../models/ps-scan-prepared';
3
- import type { PsScanRaster } from '../models/ps-scan-raster';
2
+ import type { PsScanQuad } from '../models/ps-scan-quad';
4
3
  import type { PsScanRenderedPage } from '../models/ps-scan-rendered-page';
5
4
  export declare class PsScanPipelineService {
6
5
  static prepare(source: Blob): Promise<PsScanPrepared>;
7
- static measure(source: Blob): Promise<PsScanRaster>;
8
- static process(page: Pick<PsScanPage, 'corners' | 'filter' | 'rotation'>, raster: PsScanRaster, maxDimension: number): PsScanRaster;
9
- static render(page: PsScanPage, maxDimension: number, quality: number): Promise<PsScanRenderedPage>;
6
+ static render(source: Blob, corners: PsScanQuad, maxDimension: number, quality: number): Promise<PsScanRenderedPage>;
10
7
  }
@@ -4,36 +4,26 @@ exports.PsScanPipelineService = void 0;
4
4
  const ps_scan_config_1 = require("../constants/ps-scan-config");
5
5
  const ps_scan_canvas_service_1 = require("./ps-scan-canvas-service");
6
6
  const ps_scan_edge_service_1 = require("./ps-scan-edge-service");
7
- const ps_scan_enhance_service_1 = require("./ps-scan-enhance-service");
8
7
  const ps_scan_geometry_service_1 = require("./ps-scan-geometry-service");
9
- const ps_scan_raster_service_1 = require("./ps-scan-raster-service");
10
8
  const ps_scan_warp_service_1 = require("./ps-scan-warp-service");
11
9
  class PsScanPipelineService {
12
10
  static async prepare(source) {
13
11
  const decoded = await ps_scan_canvas_service_1.PsScanCanvasService.decode(source, ps_scan_config_1.PsScanConfig.detectMaxDimension * 2);
14
- const detected = ps_scan_edge_service_1.PsScanEdgeService.detect(decoded.raster);
12
+ const located = await ps_scan_edge_service_1.PsScanEdgeService.locate(decoded.raster, ps_scan_config_1.PsScanConfig.detectMaxDimension);
15
13
  return {
16
- corners: detected === null ? ps_scan_geometry_service_1.PsScanGeometryService.defaultQuad() : detected,
14
+ corners: located.quad,
17
15
  height: decoded.naturalHeight,
18
- isDetected: detected !== null,
16
+ isDetected: located.isDetected,
19
17
  width: decoded.naturalWidth,
20
18
  };
21
19
  }
22
- static async measure(source) {
23
- const decoded = await ps_scan_canvas_service_1.PsScanCanvasService.decode(source, ps_scan_config_1.PsScanConfig.previewMaxDimension);
24
- return decoded.raster;
25
- }
26
- static process(page, raster, maxDimension) {
27
- const size = ps_scan_geometry_service_1.PsScanGeometryService.fitWithin(ps_scan_geometry_service_1.PsScanGeometryService.outputSize(page.corners, raster), maxDimension);
28
- const warped = ps_scan_warp_service_1.PsScanWarpService.warp(raster, ps_scan_geometry_service_1.PsScanGeometryService.toPixels(page.corners, {
29
- height: raster.height - 1,
30
- width: raster.width - 1,
20
+ static async render(source, corners, maxDimension, quality) {
21
+ const decoded = await ps_scan_canvas_service_1.PsScanCanvasService.decode(source, Number.MAX_SAFE_INTEGER);
22
+ const size = ps_scan_geometry_service_1.PsScanGeometryService.fitWithin(ps_scan_geometry_service_1.PsScanGeometryService.outputSize(corners, decoded.raster), maxDimension);
23
+ const raster = await ps_scan_warp_service_1.PsScanWarpService.warp(decoded.raster, ps_scan_geometry_service_1.PsScanGeometryService.toPixels(corners, {
24
+ height: decoded.raster.height - 1,
25
+ width: decoded.raster.width - 1,
31
26
  }), size.width, size.height);
32
- return ps_scan_raster_service_1.PsScanRasterService.rotate(ps_scan_enhance_service_1.PsScanEnhanceService.apply(warped, page.filter), page.rotation);
33
- }
34
- static async render(page, maxDimension, quality) {
35
- const decoded = await ps_scan_canvas_service_1.PsScanCanvasService.decode(page.source, ps_scan_config_1.PsScanConfig.decodeMaxDimension);
36
- const raster = PsScanPipelineService.process(page, decoded.raster, maxDimension);
37
27
  return {
38
28
  blob: await ps_scan_canvas_service_1.PsScanCanvasService.encode(raster, quality),
39
29
  height: raster.height,
@@ -0,0 +1,8 @@
1
+ import type { PsPoint } from '../models/ps-point';
2
+ export declare class PsScanPolygonService {
3
+ static perimeter(points: PsPoint[]): number;
4
+ static simplify(points: PsPoint[], epsilon: number): PsPoint[];
5
+ static toQuad(hull: PsPoint[]): PsPoint[] | null;
6
+ private static chain;
7
+ private static lineDistance;
8
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsScanPolygonService = void 0;
4
+ const ps_scan_config_1 = require("../constants/ps-scan-config");
5
+ const ps_scan_geometry_service_1 = require("./ps-scan-geometry-service");
6
+ class PsScanPolygonService {
7
+ static perimeter(points) {
8
+ return points.reduce((sum, point, index) => sum + ps_scan_geometry_service_1.PsScanGeometryService.distance(point, points[(index + 1) % points.length]), 0);
9
+ }
10
+ static simplify(points, epsilon) {
11
+ if (points.length < 4)
12
+ return points;
13
+ let first = 0;
14
+ let second = 0;
15
+ let best = -1;
16
+ points.forEach((point, index) => {
17
+ const length = ps_scan_geometry_service_1.PsScanGeometryService.distance(points[0], point);
18
+ if (length > best) {
19
+ best = length;
20
+ second = index;
21
+ }
22
+ });
23
+ best = -1;
24
+ points.forEach((point, index) => {
25
+ const length = ps_scan_geometry_service_1.PsScanGeometryService.distance(points[second], point);
26
+ if (length > best) {
27
+ best = length;
28
+ first = index;
29
+ }
30
+ });
31
+ const low = Math.min(first, second);
32
+ const high = Math.max(first, second);
33
+ const forward = points.slice(low, high + 1);
34
+ const backward = [...points.slice(high), ...points.slice(0, low + 1)];
35
+ const left = PsScanPolygonService.chain(forward, epsilon);
36
+ const right = PsScanPolygonService.chain(backward, epsilon);
37
+ return [...left.slice(0, -1), ...right.slice(0, -1)];
38
+ }
39
+ static toQuad(hull) {
40
+ const perimeter = PsScanPolygonService.perimeter(hull);
41
+ for (const ratio of ps_scan_config_1.PsScanConfig.polygonEpsilons) {
42
+ const simple = PsScanPolygonService.simplify(hull, ratio * perimeter);
43
+ if (simple.length === 4)
44
+ return simple;
45
+ if (simple.length < 4)
46
+ return null;
47
+ }
48
+ return null;
49
+ }
50
+ static chain(points, epsilon) {
51
+ if (points.length < 3)
52
+ return points;
53
+ const start = points[0];
54
+ const end = points[points.length - 1];
55
+ let far = 0;
56
+ let farDistance = -1;
57
+ for (let index = 1; index < points.length - 1; index += 1) {
58
+ const length = PsScanPolygonService.lineDistance(points[index], start, end);
59
+ if (length > farDistance) {
60
+ farDistance = length;
61
+ far = index;
62
+ }
63
+ }
64
+ if (farDistance <= epsilon)
65
+ return [start, end];
66
+ const left = PsScanPolygonService.chain(points.slice(0, far + 1), epsilon);
67
+ const right = PsScanPolygonService.chain(points.slice(far), epsilon);
68
+ return [...left.slice(0, -1), ...right];
69
+ }
70
+ static lineDistance(point, from, to) {
71
+ const dx = to.x - from.x;
72
+ const dy = to.y - from.y;
73
+ const length = Math.hypot(dx, dy);
74
+ if (length === 0)
75
+ return ps_scan_geometry_service_1.PsScanGeometryService.distance(point, from);
76
+ return Math.abs(dy * (point.x - from.x) - dx * (point.y - from.y)) / length;
77
+ }
78
+ }
79
+ exports.PsScanPolygonService = PsScanPolygonService;
@@ -0,0 +1,4 @@
1
+ import type { PsScanRegion } from '../models/ps-scan-region';
2
+ export declare class PsScanRegionService {
3
+ static enclosed(mask: Uint8Array, width: number, height: number, minArea: number): PsScanRegion[];
4
+ }