@pallasoft/ps-file-components 1.4.0 → 2.0.0

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 (194) 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 +64 -86
  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-picker-actions.d.ts +10 -0
  7. package/dist/components/ps-file-picker-actions.js +21 -0
  8. package/dist/components/ps-file-picker.d.ts +7 -1
  9. package/dist/components/ps-file-picker.js +30 -6
  10. package/dist/components/ps-scan-button.d.ts +13 -0
  11. package/dist/components/ps-scan-button.js +9 -0
  12. package/dist/components/ps-scan-camera-view.d.ts +11 -8
  13. package/dist/components/ps-scan-camera-view.js +14 -16
  14. package/dist/components/ps-scan-crop-editor.d.ts +4 -4
  15. package/dist/components/ps-scan-crop-editor.js +24 -8
  16. package/dist/components/ps-scan-denied-steps.d.ts +6 -0
  17. package/dist/components/ps-scan-denied-steps.js +33 -0
  18. package/dist/components/ps-scan-icon-button.d.ts +4 -3
  19. package/dist/components/ps-scan-icon-button.js +3 -1
  20. package/dist/components/ps-scan-illustration.d.ts +1 -0
  21. package/dist/components/ps-scan-illustration.js +6 -0
  22. package/dist/components/ps-scan-loupe.d.ts +10 -0
  23. package/dist/components/ps-scan-loupe.js +31 -0
  24. package/dist/components/ps-scan-permission-view.d.ts +10 -0
  25. package/dist/components/ps-scan-permission-view.js +48 -0
  26. package/dist/components/ps-scan-photo-button.d.ts +7 -0
  27. package/dist/components/ps-scan-photo-button.js +20 -0
  28. package/dist/components/ps-scan-preview.d.ts +12 -0
  29. package/dist/components/ps-scan-preview.js +10 -0
  30. package/dist/components/ps-scan-shutter.d.ts +6 -0
  31. package/dist/components/ps-scan-shutter.js +6 -0
  32. package/dist/components/ps-scan-viewfinder-overlay.d.ts +8 -0
  33. package/dist/components/ps-scan-viewfinder-overlay.js +28 -0
  34. package/dist/components/ps-upload-row.js +1 -1
  35. package/dist/constants/ps-document-scanner-labels.d.ts +39 -38
  36. package/dist/constants/ps-document-scanner-labels.js +39 -38
  37. package/dist/constants/ps-file-picker-labels.d.ts +5 -3
  38. package/dist/constants/ps-file-picker-labels.js +5 -3
  39. package/dist/constants/ps-scan-camera-status.d.ts +4 -0
  40. package/dist/constants/ps-scan-camera-status.js +4 -0
  41. package/dist/constants/ps-scan-chrome-classes.d.ts +10 -0
  42. package/dist/constants/ps-scan-chrome-classes.js +13 -0
  43. package/dist/constants/ps-scan-config.d.ts +34 -24
  44. package/dist/constants/ps-scan-config.js +34 -24
  45. package/dist/constants/ps-scan-platform.d.ts +6 -0
  46. package/dist/constants/ps-scan-platform.js +8 -0
  47. package/dist/constants/ps-scan-step.d.ts +1 -1
  48. package/dist/constants/ps-scan-step.js +1 -1
  49. package/dist/esm/components/ps-document-scanner-sheet.js +67 -89
  50. package/dist/esm/components/ps-document-scanner.js +2 -3
  51. package/dist/esm/components/ps-file-picker-actions.js +17 -0
  52. package/dist/esm/components/ps-file-picker.js +31 -7
  53. package/dist/esm/components/ps-scan-button.js +5 -0
  54. package/dist/esm/components/ps-scan-camera-view.js +17 -19
  55. package/dist/esm/components/ps-scan-crop-editor.js +26 -10
  56. package/dist/esm/components/ps-scan-denied-steps.js +29 -0
  57. package/dist/esm/components/ps-scan-icon-button.js +3 -1
  58. package/dist/esm/components/ps-scan-illustration.js +2 -0
  59. package/dist/esm/components/ps-scan-loupe.js +27 -0
  60. package/dist/esm/components/ps-scan-permission-view.js +44 -0
  61. package/dist/esm/components/ps-scan-photo-button.js +16 -0
  62. package/dist/esm/components/ps-scan-preview.js +6 -0
  63. package/dist/esm/components/ps-scan-shutter.js +2 -0
  64. package/dist/esm/components/ps-scan-viewfinder-overlay.js +24 -0
  65. package/dist/esm/components/ps-upload-row.js +1 -1
  66. package/dist/esm/constants/ps-document-scanner-labels.js +39 -38
  67. package/dist/esm/constants/ps-file-picker-labels.js +5 -3
  68. package/dist/esm/constants/ps-scan-camera-status.js +4 -0
  69. package/dist/esm/constants/ps-scan-chrome-classes.js +10 -0
  70. package/dist/esm/constants/ps-scan-config.js +34 -24
  71. package/dist/esm/constants/ps-scan-platform.js +5 -0
  72. package/dist/esm/constants/ps-scan-step.js +1 -1
  73. package/dist/esm/hooks/use-ps-scan-camera.js +52 -15
  74. package/dist/esm/hooks/use-ps-scan-live-detection.js +41 -0
  75. package/dist/esm/hooks/use-ps-scan-photo.js +42 -0
  76. package/dist/esm/index.js +1 -2
  77. package/dist/esm/models/ps-scan-region.js +1 -0
  78. package/dist/esm/models/ps-scan-result.js +1 -0
  79. package/dist/esm/models/ps-scan-stability.js +1 -0
  80. package/dist/esm/services/ps-scan-camera-service.js +52 -7
  81. package/dist/esm/services/ps-scan-canny-service.js +93 -0
  82. package/dist/esm/services/ps-scan-canvas-service.js +13 -1
  83. package/dist/esm/services/ps-scan-edge-service.js +73 -137
  84. package/dist/esm/services/ps-scan-file-name-service.js +2 -11
  85. package/dist/esm/services/ps-scan-geometry-service.js +56 -0
  86. package/dist/esm/services/ps-scan-hull-service.js +0 -39
  87. package/dist/esm/services/ps-scan-image-filter-service.js +0 -21
  88. package/dist/esm/services/ps-scan-morphology-service.js +44 -0
  89. package/dist/esm/services/ps-scan-pipeline-service.js +9 -19
  90. package/dist/esm/services/ps-scan-platform-service.js +15 -0
  91. package/dist/esm/services/ps-scan-polygon-service.js +75 -0
  92. package/dist/esm/services/ps-scan-region-service.js +52 -0
  93. package/dist/esm/services/ps-scan-stability-service.js +28 -0
  94. package/dist/esm/services/ps-scan-warp-service.js +8 -2
  95. package/dist/esm/services/ps-scan-yield-service.js +5 -0
  96. package/dist/hooks/use-ps-scan-camera.d.ts +3 -1
  97. package/dist/hooks/use-ps-scan-camera.js +52 -15
  98. package/dist/hooks/use-ps-scan-live-detection.d.ts +3 -0
  99. package/dist/hooks/use-ps-scan-live-detection.js +45 -0
  100. package/dist/hooks/use-ps-scan-photo.d.ts +9 -0
  101. package/dist/hooks/use-ps-scan-photo.js +46 -0
  102. package/dist/index.d.ts +1 -2
  103. package/dist/index.js +1 -2
  104. package/dist/models/ps-document-scanner-props.d.ts +0 -3
  105. package/dist/models/ps-document-scanner-sheet-props.d.ts +0 -3
  106. package/dist/models/ps-file-picker-scan.d.ts +4 -0
  107. package/dist/models/ps-scan-gradient.d.ts +4 -0
  108. package/dist/models/ps-scan-live-detection.d.ts +4 -0
  109. package/dist/models/ps-scan-located.d.ts +5 -0
  110. package/dist/models/ps-scan-photo.d.ts +9 -0
  111. package/dist/models/ps-scan-region.d.ts +5 -0
  112. package/dist/models/ps-scan-region.js +2 -0
  113. package/dist/models/ps-scan-result.d.ts +6 -0
  114. package/dist/models/ps-scan-result.js +2 -0
  115. package/dist/models/ps-scan-stability.d.ts +5 -0
  116. package/dist/models/ps-scan-stability.js +2 -0
  117. package/dist/services/ps-scan-camera-service.d.ts +4 -0
  118. package/dist/services/ps-scan-camera-service.js +52 -7
  119. package/dist/services/ps-scan-canny-service.d.ts +7 -0
  120. package/dist/services/ps-scan-canny-service.js +97 -0
  121. package/dist/services/ps-scan-canvas-service.d.ts +1 -0
  122. package/dist/services/ps-scan-canvas-service.js +13 -1
  123. package/dist/services/ps-scan-edge-service.d.ts +5 -5
  124. package/dist/services/ps-scan-edge-service.js +73 -137
  125. package/dist/services/ps-scan-file-name-service.d.ts +1 -3
  126. package/dist/services/ps-scan-file-name-service.js +2 -11
  127. package/dist/services/ps-scan-geometry-service.d.ts +4 -0
  128. package/dist/services/ps-scan-geometry-service.js +56 -0
  129. package/dist/services/ps-scan-hull-service.d.ts +0 -2
  130. package/dist/services/ps-scan-hull-service.js +0 -39
  131. package/dist/services/ps-scan-image-filter-service.d.ts +0 -1
  132. package/dist/services/ps-scan-image-filter-service.js +0 -21
  133. package/dist/services/ps-scan-morphology-service.d.ts +7 -0
  134. package/dist/services/ps-scan-morphology-service.js +48 -0
  135. package/dist/services/ps-scan-pipeline-service.d.ts +2 -5
  136. package/dist/services/ps-scan-pipeline-service.js +9 -19
  137. package/dist/services/ps-scan-platform-service.d.ts +4 -0
  138. package/dist/services/ps-scan-platform-service.js +19 -0
  139. package/dist/services/ps-scan-polygon-service.d.ts +8 -0
  140. package/dist/services/ps-scan-polygon-service.js +79 -0
  141. package/dist/services/ps-scan-region-service.d.ts +4 -0
  142. package/dist/services/ps-scan-region-service.js +56 -0
  143. package/dist/services/ps-scan-stability-service.d.ts +7 -0
  144. package/dist/services/ps-scan-stability-service.js +32 -0
  145. package/dist/services/ps-scan-warp-service.d.ts +1 -1
  146. package/dist/services/ps-scan-warp-service.js +8 -2
  147. package/dist/services/ps-scan-yield-service.d.ts +3 -0
  148. package/dist/services/ps-scan-yield-service.js +9 -0
  149. package/package.json +3 -3
  150. package/dist/components/ps-scan-page-list.d.ts +0 -12
  151. package/dist/components/ps-scan-page-list.js +0 -23
  152. package/dist/components/ps-scan-review.d.ts +0 -23
  153. package/dist/components/ps-scan-review.js +0 -20
  154. package/dist/constants/ps-scan-filter.d.ts +0 -7
  155. package/dist/constants/ps-scan-filter.js +0 -9
  156. package/dist/constants/ps-scan-output-format.d.ts +0 -5
  157. package/dist/constants/ps-scan-output-format.js +0 -7
  158. package/dist/esm/components/ps-scan-page-list.js +0 -19
  159. package/dist/esm/components/ps-scan-review.js +0 -16
  160. package/dist/esm/constants/ps-scan-filter.js +0 -6
  161. package/dist/esm/constants/ps-scan-output-format.js +0 -4
  162. package/dist/esm/hooks/use-ps-scan-pages.js +0 -62
  163. package/dist/esm/hooks/use-ps-scan-previews.js +0 -57
  164. package/dist/esm/services/ps-scan-enhance-service.js +0 -97
  165. package/dist/esm/services/ps-scan-order-service.js +0 -13
  166. package/dist/esm/services/ps-scan-output-service.js +0 -24
  167. package/dist/esm/services/ps-scan-pdf-service.js +0 -56
  168. package/dist/hooks/use-ps-scan-pages.d.ts +0 -12
  169. package/dist/hooks/use-ps-scan-pages.js +0 -66
  170. package/dist/hooks/use-ps-scan-previews.d.ts +0 -2
  171. package/dist/hooks/use-ps-scan-previews.js +0 -61
  172. package/dist/models/ps-scan-added-page.d.ts +0 -5
  173. package/dist/models/ps-scan-levels.d.ts +0 -4
  174. package/dist/models/ps-scan-output-options.d.ts +0 -7
  175. package/dist/models/ps-scan-page.d.ts +0 -12
  176. package/dist/models/ps-scan-pdf-page.d.ts +0 -5
  177. package/dist/services/ps-scan-enhance-service.d.ts +0 -9
  178. package/dist/services/ps-scan-enhance-service.js +0 -101
  179. package/dist/services/ps-scan-order-service.d.ts +0 -4
  180. package/dist/services/ps-scan-order-service.js +0 -17
  181. package/dist/services/ps-scan-output-service.d.ts +0 -5
  182. package/dist/services/ps-scan-output-service.js +0 -28
  183. package/dist/services/ps-scan-pdf-service.d.ts +0 -5
  184. package/dist/services/ps-scan-pdf-service.js +0 -60
  185. /package/dist/esm/models/{ps-scan-added-page.js → ps-file-picker-scan.js} +0 -0
  186. /package/dist/esm/models/{ps-scan-levels.js → ps-scan-gradient.js} +0 -0
  187. /package/dist/esm/models/{ps-scan-output-options.js → ps-scan-live-detection.js} +0 -0
  188. /package/dist/esm/models/{ps-scan-page.js → ps-scan-located.js} +0 -0
  189. /package/dist/esm/models/{ps-scan-pdf-page.js → ps-scan-photo.js} +0 -0
  190. /package/dist/models/{ps-scan-added-page.js → ps-file-picker-scan.js} +0 -0
  191. /package/dist/models/{ps-scan-levels.js → ps-scan-gradient.js} +0 -0
  192. /package/dist/models/{ps-scan-output-options.js → ps-scan-live-detection.js} +0 -0
  193. /package/dist/models/{ps-scan-page.js → ps-scan-located.js} +0 -0
  194. /package/dist/models/{ps-scan-pdf-page.js → ps-scan-photo.js} +0 -0
@@ -0,0 +1,4 @@
1
+ import type { PsScanQuad } from './ps-scan-quad';
2
+ export interface PsScanLiveDetection {
3
+ quad: PsScanQuad | null;
4
+ }
@@ -0,0 +1,5 @@
1
+ import type { PsScanQuad } from './ps-scan-quad';
2
+ export interface PsScanLocated {
3
+ isDetected: boolean;
4
+ quad: PsScanQuad;
5
+ }
@@ -0,0 +1,9 @@
1
+ import type { PsScanQuad } from './ps-scan-quad';
2
+ export interface PsScanPhoto {
3
+ corners: PsScanQuad;
4
+ height: number;
5
+ isDetected: boolean;
6
+ source: Blob;
7
+ sourceUrl: string;
8
+ width: number;
9
+ }
@@ -0,0 +1,5 @@
1
+ import type { PsPoint } from './ps-point';
2
+ export interface PsScanRegion {
3
+ area: number;
4
+ boundary: PsPoint[];
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ export interface PsScanResult {
2
+ blob: Blob;
3
+ height: number;
4
+ url: string;
5
+ width: number;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ import type { PsScanQuad } from './ps-scan-quad';
2
+ export interface PsScanStability {
3
+ misses: number;
4
+ quad: PsScanQuad | null;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,9 +1,13 @@
1
1
  import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
2
2
  import type { PsScanCameraSession } from '../models/ps-scan-camera-session';
3
3
  export declare class PsScanCameraService {
4
+ static attach(video: HTMLVideoElement, session: PsScanCameraSession): Promise<void>;
5
+ static check(): Promise<PsScanCameraStatus>;
4
6
  static classify(error: unknown): PsScanCameraStatus;
7
+ static hasCamera(): Promise<boolean>;
5
8
  static isSupported(): boolean;
6
9
  static open(): Promise<PsScanCameraSession>;
10
+ static permission(): Promise<PermissionStatus | null>;
7
11
  static setTorch(track: MediaStreamTrack, isOn: boolean): Promise<boolean>;
8
12
  static stop(stream: MediaStream | null): void;
9
13
  static supportsTorch(track: MediaStreamTrack | undefined): boolean;
@@ -4,13 +4,45 @@ exports.PsScanCameraService = void 0;
4
4
  const ps_scan_camera_status_1 = require("../constants/ps-scan-camera-status");
5
5
  const ps_scan_config_1 = require("../constants/ps-scan-config");
6
6
  class PsScanCameraService {
7
+ static async attach(video, session) {
8
+ if (video.srcObject !== session.stream)
9
+ video.srcObject = session.stream;
10
+ await video.play().catch(() => undefined);
11
+ }
12
+ static async check() {
13
+ if (globalThis.isSecureContext === false)
14
+ return ps_scan_camera_status_1.PsScanCameraStatus.Insecure;
15
+ if (!PsScanCameraService.isSupported())
16
+ return ps_scan_camera_status_1.PsScanCameraStatus.Unavailable;
17
+ if (!(await PsScanCameraService.hasCamera()))
18
+ return ps_scan_camera_status_1.PsScanCameraStatus.Unavailable;
19
+ const permission = await PsScanCameraService.permission();
20
+ if (permission === null)
21
+ return ps_scan_camera_status_1.PsScanCameraStatus.Prompt;
22
+ if (permission.state === 'granted')
23
+ return ps_scan_camera_status_1.PsScanCameraStatus.Starting;
24
+ return permission.state === 'denied' ? ps_scan_camera_status_1.PsScanCameraStatus.Denied : ps_scan_camera_status_1.PsScanCameraStatus.Prompt;
25
+ }
7
26
  static classify(error) {
8
27
  const name = typeof error === 'object' && error !== null && 'name' in error ? String(error.name) : '';
9
- return name === 'NotAllowedError' ||
10
- name === 'SecurityError' ||
11
- name === 'PermissionDeniedError'
12
- ? ps_scan_camera_status_1.PsScanCameraStatus.Denied
13
- : ps_scan_camera_status_1.PsScanCameraStatus.Unavailable;
28
+ if (name === 'NotAllowedError' || name === 'PermissionDeniedError')
29
+ return ps_scan_camera_status_1.PsScanCameraStatus.Denied;
30
+ if (name === 'SecurityError')
31
+ return ps_scan_camera_status_1.PsScanCameraStatus.Insecure;
32
+ if (name === 'NotReadableError' || name === 'TrackStartError' || name === 'AbortError')
33
+ return ps_scan_camera_status_1.PsScanCameraStatus.Busy;
34
+ return ps_scan_camera_status_1.PsScanCameraStatus.Unavailable;
35
+ }
36
+ static async hasCamera() {
37
+ if (typeof navigator.mediaDevices.enumerateDevices !== 'function')
38
+ return true;
39
+ try {
40
+ const devices = await navigator.mediaDevices.enumerateDevices();
41
+ return devices.length === 0 || devices.some((device) => device.kind === 'videoinput');
42
+ }
43
+ catch {
44
+ return true;
45
+ }
14
46
  }
15
47
  static isSupported() {
16
48
  return (typeof navigator !== 'undefined' &&
@@ -22,13 +54,26 @@ class PsScanCameraService {
22
54
  audio: false,
23
55
  video: {
24
56
  facingMode: { ideal: 'environment' },
25
- height: { ideal: 1440 },
26
- width: { ideal: 1920 },
57
+ height: { ideal: 2160 },
58
+ width: { ideal: 3840 },
27
59
  },
28
60
  });
29
61
  const track = stream.getVideoTracks()[0];
30
62
  return { canTorch: PsScanCameraService.supportsTorch(track), stream, track };
31
63
  }
64
+ static async permission() {
65
+ if (typeof navigator.permissions === 'undefined' ||
66
+ typeof navigator.permissions.query !== 'function')
67
+ return null;
68
+ try {
69
+ return await navigator.permissions.query({
70
+ name: ps_scan_config_1.PsScanConfig.cameraPermission,
71
+ });
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
32
77
  static async setTorch(track, isOn) {
33
78
  try {
34
79
  await track.applyConstraints({
@@ -0,0 +1,7 @@
1
+ export declare class PsScanCannyService {
2
+ static edges(gray: Uint8ClampedArray, width: number, height: number): Uint8Array;
3
+ private static gradient;
4
+ private static highThreshold;
5
+ private static hysteresis;
6
+ private static suppress;
7
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsScanCannyService = void 0;
4
+ const ps_scan_config_1 = require("../constants/ps-scan-config");
5
+ class PsScanCannyService {
6
+ static edges(gray, width, height) {
7
+ const gradient = PsScanCannyService.gradient(gray, width, height);
8
+ const ridges = PsScanCannyService.suppress(gradient, width, height);
9
+ const high = PsScanCannyService.highThreshold(ridges);
10
+ return PsScanCannyService.hysteresis(ridges, width, height, high * ps_scan_config_1.PsScanConfig.cannyLowRatio, high);
11
+ }
12
+ static gradient(gray, width, height) {
13
+ const magnitude = new Float32Array(width * height);
14
+ const direction = new Uint8Array(width * height);
15
+ for (let y = 1; y < height - 1; y += 1)
16
+ for (let x = 1; x < width - 1; x += 1) {
17
+ const i = y * width + x;
18
+ const gx = -gray[i - width - 1] +
19
+ gray[i - width + 1] -
20
+ 2 * gray[i - 1] +
21
+ 2 * gray[i + 1] -
22
+ gray[i + width - 1] +
23
+ gray[i + width + 1];
24
+ const gy = -gray[i - width - 1] -
25
+ 2 * gray[i - width] -
26
+ gray[i - width + 1] +
27
+ gray[i + width - 1] +
28
+ 2 * gray[i + width] +
29
+ gray[i + width + 1];
30
+ const ax = Math.abs(gx);
31
+ const ay = Math.abs(gy);
32
+ magnitude[i] = Math.hypot(gx, gy) / 4;
33
+ if (ay <= ax * 0.41421356)
34
+ direction[i] = 0;
35
+ else if (ay >= ax * 2.41421356)
36
+ direction[i] = 2;
37
+ else
38
+ direction[i] = gx * gy > 0 ? 1 : 3;
39
+ }
40
+ return { direction, magnitude };
41
+ }
42
+ static highThreshold(ridges) {
43
+ const values = [];
44
+ for (let index = 0; index < ridges.length; index += 1)
45
+ if (ridges[index] > 0)
46
+ values.push(ridges[index]);
47
+ if (values.length === 0)
48
+ return ps_scan_config_1.PsScanConfig.cannyMinHigh;
49
+ values.sort((a, b) => a - b);
50
+ const at = Math.min(values.length - 1, Math.floor(values.length * ps_scan_config_1.PsScanConfig.cannyHighPercentile));
51
+ return Math.max(ps_scan_config_1.PsScanConfig.cannyMinHigh, values[at]);
52
+ }
53
+ static hysteresis(ridges, width, height, low, high) {
54
+ const out = new Uint8Array(width * height);
55
+ const stack = [];
56
+ for (let index = 0; index < ridges.length; index += 1)
57
+ if (ridges[index] >= high) {
58
+ out[index] = 1;
59
+ stack.push(index);
60
+ }
61
+ while (stack.length > 0) {
62
+ const index = stack.pop();
63
+ if (index === undefined)
64
+ break;
65
+ const x = index % width;
66
+ const y = (index - x) / width;
67
+ for (let dy = -1; dy <= 1; dy += 1)
68
+ for (let dx = -1; dx <= 1; dx += 1) {
69
+ const nx = x + dx;
70
+ const ny = y + dy;
71
+ if (nx < 0 || ny < 0 || nx >= width || ny >= height)
72
+ continue;
73
+ const next = ny * width + nx;
74
+ if (out[next] === 0 && ridges[next] >= low) {
75
+ out[next] = 1;
76
+ stack.push(next);
77
+ }
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+ static suppress(gradient, width, height) {
83
+ const { direction, magnitude } = gradient;
84
+ const out = new Float32Array(width * height);
85
+ const offsets = [1, width + 1, width, width - 1];
86
+ for (let y = 1; y < height - 1; y += 1)
87
+ for (let x = 1; x < width - 1; x += 1) {
88
+ const i = y * width + x;
89
+ const offset = offsets[direction[i]];
90
+ const value = magnitude[i];
91
+ if (value >= magnitude[i - offset] && value > magnitude[i + offset])
92
+ out[i] = value;
93
+ }
94
+ return out;
95
+ }
96
+ }
97
+ exports.PsScanCannyService = PsScanCannyService;
@@ -4,5 +4,6 @@ export declare class PsScanCanvasService {
4
4
  static decode(blob: Blob, maxDimension: number): Promise<PsScanDecodedImage>;
5
5
  static encode(raster: PsScanRaster, quality: number): Promise<Blob>;
6
6
  static fromVideo(video: HTMLVideoElement, quality: number): Promise<Blob>;
7
+ static sample(video: HTMLVideoElement, maxDimension: number): PsScanRaster;
7
8
  private static context;
8
9
  }
@@ -1,11 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsScanCanvasService = void 0;
4
+ const ps_scan_config_1 = require("../constants/ps-scan-config");
4
5
  const ps_scan_geometry_service_1 = require("./ps-scan-geometry-service");
5
6
  class PsScanCanvasService {
6
7
  static async decode(blob, maxDimension) {
7
8
  const bitmap = await createImageBitmap(blob, { imageOrientation: 'from-image' });
8
- const size = ps_scan_geometry_service_1.PsScanGeometryService.fitWithin({ height: bitmap.height, width: bitmap.width }, maxDimension);
9
+ const capped = ps_scan_geometry_service_1.PsScanGeometryService.fitWithin({ height: bitmap.height, width: bitmap.width }, maxDimension);
10
+ const shrink = Math.min(1, Math.sqrt(ps_scan_config_1.PsScanConfig.decodeMaxPixels / (capped.width * capped.height)));
11
+ const size = {
12
+ height: Math.max(1, Math.round(capped.height * shrink)),
13
+ width: Math.max(1, Math.round(capped.width * shrink)),
14
+ };
9
15
  const context = PsScanCanvasService.context(size.width, size.height);
10
16
  context.drawImage(bitmap, 0, 0, size.width, size.height);
11
17
  const naturalHeight = bitmap.height;
@@ -30,6 +36,12 @@ class PsScanCanvasService {
30
36
  context.drawImage(video, 0, 0);
31
37
  return await PsScanCanvasService.encode(context.getImageData(0, 0, video.videoWidth, video.videoHeight), quality);
32
38
  }
39
+ static sample(video, maxDimension) {
40
+ const size = ps_scan_geometry_service_1.PsScanGeometryService.fitWithin({ height: video.videoHeight, width: video.videoWidth }, maxDimension);
41
+ const context = PsScanCanvasService.context(size.width, size.height);
42
+ context.drawImage(video, 0, 0, size.width, size.height);
43
+ return context.getImageData(0, 0, size.width, size.height);
44
+ }
33
45
  static context(width, height) {
34
46
  if (typeof OffscreenCanvas !== 'undefined') {
35
47
  const context = new OffscreenCanvas(width, height).getContext('2d', {
@@ -1,9 +1,9 @@
1
+ import type { PsScanLocated } from '../models/ps-scan-located';
1
2
  import type { PsScanQuad } from '../models/ps-scan-quad';
2
3
  import type { PsScanRaster } from '../models/ps-scan-raster';
3
4
  export declare class PsScanEdgeService {
4
- static detect(source: PsScanRaster): PsScanQuad | null;
5
- private static dilate;
6
- private static foreground;
7
- private static largestComponent;
8
- private static threshold;
5
+ static detect(source: PsScanRaster, maxDimension: number): Promise<PsScanQuad | null>;
6
+ static locate(source: PsScanRaster, maxDimension: number): Promise<PsScanLocated>;
7
+ private static candidate;
8
+ private static normalise;
9
9
  }
@@ -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 quad.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,12 +2,16 @@ 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;
8
+ static cover(container: PsPageSize, content: PsPageSize): PsPageSize;
7
9
  static defaultQuad(): PsScanQuad;
8
10
  static distance(from: PsPoint, to: PsPoint): number;
11
+ static expand(quad: PsScanQuad, distance: number): PsScanQuad;
9
12
  static fit(container: PsPageSize, content: PsPageSize): PsPageSize;
10
13
  static fitWithin(size: PsPageSize, maxDimension: number): PsPageSize;
14
+ static inset(quad: PsScanQuad, distance: number): PsScanQuad;
11
15
  static isConvex(points: PsPoint[]): boolean;
12
16
  static isUsable(quad: PsScanQuad): boolean;
13
17
  static nudge(quad: PsScanQuad, index: number, dx: number, dy: number): PsScanQuad;