@ayasofyazilim/ui 0.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 (236) hide show
  1. package/__mocks__/canvas.ts +8 -0
  2. package/components.json +21 -0
  3. package/eslint.config.js +4 -0
  4. package/jest-environment.js +37 -0
  5. package/jest.config.ts +47 -0
  6. package/jest.setup.ts +69 -0
  7. package/package.json +124 -0
  8. package/postcss.config.mjs +6 -0
  9. package/src/aria/index.tsx +1 -0
  10. package/src/aria/number-field.tsx +41 -0
  11. package/src/components/.gitkeep +0 -0
  12. package/src/components/accordion.tsx +66 -0
  13. package/src/components/alert-dialog.tsx +157 -0
  14. package/src/components/alert.tsx +70 -0
  15. package/src/components/aspect-ratio.tsx +11 -0
  16. package/src/components/avatar.tsx +53 -0
  17. package/src/components/badge.tsx +67 -0
  18. package/src/components/breadcrumb.tsx +109 -0
  19. package/src/components/button-group.tsx +83 -0
  20. package/src/components/button.tsx +68 -0
  21. package/src/components/calendar.tsx +219 -0
  22. package/src/components/card.tsx +92 -0
  23. package/src/components/carousel.tsx +241 -0
  24. package/src/components/chart.tsx +363 -0
  25. package/src/components/checkbox.tsx +32 -0
  26. package/src/components/collapsible.tsx +33 -0
  27. package/src/components/command.tsx +184 -0
  28. package/src/components/context-menu.tsx +252 -0
  29. package/src/components/dialog.tsx +144 -0
  30. package/src/components/drawer.tsx +135 -0
  31. package/src/components/dropdown-menu.tsx +258 -0
  32. package/src/components/empty.tsx +100 -0
  33. package/src/components/field.tsx +248 -0
  34. package/src/components/form.tsx +169 -0
  35. package/src/components/hover-card.tsx +44 -0
  36. package/src/components/input-group.tsx +170 -0
  37. package/src/components/input-otp.tsx +77 -0
  38. package/src/components/input.tsx +21 -0
  39. package/src/components/item.tsx +193 -0
  40. package/src/components/kbd.tsx +28 -0
  41. package/src/components/label.tsx +24 -0
  42. package/src/components/menubar.tsx +276 -0
  43. package/src/components/navigation-menu.tsx +168 -0
  44. package/src/components/pagination.tsx +130 -0
  45. package/src/components/popover.tsx +88 -0
  46. package/src/components/progress.tsx +31 -0
  47. package/src/components/radio-group.tsx +45 -0
  48. package/src/components/resizable.tsx +56 -0
  49. package/src/components/scroll-area.tsx +58 -0
  50. package/src/components/select.tsx +189 -0
  51. package/src/components/separator.tsx +28 -0
  52. package/src/components/sheet.tsx +140 -0
  53. package/src/components/sidebar.tsx +862 -0
  54. package/src/components/skeleton.tsx +13 -0
  55. package/src/components/slider.tsx +63 -0
  56. package/src/components/sonner.tsx +40 -0
  57. package/src/components/spinner.tsx +16 -0
  58. package/src/components/stepper.tsx +291 -0
  59. package/src/components/switch.tsx +31 -0
  60. package/src/components/table.tsx +133 -0
  61. package/src/components/tabs.tsx +66 -0
  62. package/src/components/textarea.tsx +18 -0
  63. package/src/components/toggle-group.tsx +83 -0
  64. package/src/components/toggle.tsx +47 -0
  65. package/src/components/tooltip.tsx +66 -0
  66. package/src/custom/action-button.tsx +48 -0
  67. package/src/custom/async-select.tsx +287 -0
  68. package/src/custom/awesome-not-found.tsx +116 -0
  69. package/src/custom/charts/area-chart.tsx +147 -0
  70. package/src/custom/charts/bar-chart.tsx +233 -0
  71. package/src/custom/charts/chart-card.tsx +103 -0
  72. package/src/custom/charts/index.tsx +16 -0
  73. package/src/custom/charts/pie-chart.tsx +168 -0
  74. package/src/custom/charts/radar-chart.tsx +126 -0
  75. package/src/custom/checkbox-tree.tsx +100 -0
  76. package/src/custom/combobox.tsx +296 -0
  77. package/src/custom/confirm-dialog.tsx +102 -0
  78. package/src/custom/country-selector.tsx +204 -0
  79. package/src/custom/date-picker/calendar-rac.tsx +109 -0
  80. package/src/custom/date-picker/datefield-rac.tsx +84 -0
  81. package/src/custom/date-picker/index.tsx +273 -0
  82. package/src/custom/date-picker/types/index.ts +4 -0
  83. package/src/custom/date-picker/utils/index.ts +42 -0
  84. package/src/custom/date-picker-old.tsx +50 -0
  85. package/src/custom/date-tooltip.tsx +98 -0
  86. package/src/custom/document-scanner/consts.ts +5 -0
  87. package/src/custom/document-scanner/corner-adjustment/action-buttons.tsx +33 -0
  88. package/src/custom/document-scanner/corner-adjustment/corner-handle.tsx +43 -0
  89. package/src/custom/document-scanner/corner-adjustment/hooks/use-corner-drag.ts +85 -0
  90. package/src/custom/document-scanner/corner-adjustment/index.tsx +125 -0
  91. package/src/custom/document-scanner/corner-adjustment/types.ts +53 -0
  92. package/src/custom/document-scanner/corner-adjustment/utils/clip-path.ts +22 -0
  93. package/src/custom/document-scanner/corner-adjustment/zoom-magnifier.tsx +115 -0
  94. package/src/custom/document-scanner/hooks/use-document-capture.ts +81 -0
  95. package/src/custom/document-scanner/hooks/use-document-scanner.ts +80 -0
  96. package/src/custom/document-scanner/hooks/use-perspective-crop.ts +38 -0
  97. package/src/custom/document-scanner/index.tsx +255 -0
  98. package/src/custom/document-scanner/lib.ts +407 -0
  99. package/src/custom/document-scanner/types.ts +205 -0
  100. package/src/custom/document-scanner/utils/perspective-correction.ts +139 -0
  101. package/src/custom/document-viewer/controllers.tsx +98 -0
  102. package/src/custom/document-viewer/index.tsx +43 -0
  103. package/src/custom/document-viewer/renderers/image.tsx +37 -0
  104. package/src/custom/document-viewer/renderers/index.tsx +2 -0
  105. package/src/custom/document-viewer/renderers/pdf.tsx +105 -0
  106. package/src/custom/email-input/domains.json +159 -0
  107. package/src/custom/email-input/email.tsx +229 -0
  108. package/src/custom/email-input/index.tsx +4 -0
  109. package/src/custom/email-input/types.ts +104 -0
  110. package/src/custom/file-uploader.tsx +541 -0
  111. package/src/custom/filter-component/fields/async-select.tsx +33 -0
  112. package/src/custom/filter-component/fields/date.tsx +60 -0
  113. package/src/custom/filter-component/fields/multi-select.tsx +30 -0
  114. package/src/custom/filter-component/index.tsx +217 -0
  115. package/src/custom/image-canvas.tsx +260 -0
  116. package/src/custom/json-editor.tsx +22 -0
  117. package/src/custom/master-data-grid/components/dialogs/column-settings-dialog.tsx +100 -0
  118. package/src/custom/master-data-grid/components/dialogs/index.ts +1 -0
  119. package/src/custom/master-data-grid/components/filters/client-filter.tsx +368 -0
  120. package/src/custom/master-data-grid/components/filters/filter-input.tsx +256 -0
  121. package/src/custom/master-data-grid/components/filters/index.ts +3 -0
  122. package/src/custom/master-data-grid/components/filters/inline-column-filter.tsx +233 -0
  123. package/src/custom/master-data-grid/components/filters/multi-filter-dialog.tsx +90 -0
  124. package/src/custom/master-data-grid/components/filters/server-filter.tsx +255 -0
  125. package/src/custom/master-data-grid/components/master-data-grid.tsx +472 -0
  126. package/src/custom/master-data-grid/components/pagination/index.ts +1 -0
  127. package/src/custom/master-data-grid/components/pagination/pagination.tsx +178 -0
  128. package/src/custom/master-data-grid/components/table/cell-renderer.tsx +634 -0
  129. package/src/custom/master-data-grid/components/table/header-cell.tsx +162 -0
  130. package/src/custom/master-data-grid/components/table/index.ts +4 -0
  131. package/src/custom/master-data-grid/components/table/table-body-renderer.tsx +113 -0
  132. package/src/custom/master-data-grid/components/table/virtual-body.tsx +138 -0
  133. package/src/custom/master-data-grid/components/toolbar/index.ts +1 -0
  134. package/src/custom/master-data-grid/components/toolbar/toolbar.tsx +314 -0
  135. package/src/custom/master-data-grid/hooks/index.ts +3 -0
  136. package/src/custom/master-data-grid/hooks/use-columns.tsx +332 -0
  137. package/src/custom/master-data-grid/hooks/use-editing.ts +106 -0
  138. package/src/custom/master-data-grid/hooks/use-table-state-reducer.ts +157 -0
  139. package/src/custom/master-data-grid/hooks/use-table-state.ts +31 -0
  140. package/src/custom/master-data-grid/index.ts +16 -0
  141. package/src/custom/master-data-grid/types.ts +466 -0
  142. package/src/custom/master-data-grid/utils/column-generator.tsx +306 -0
  143. package/src/custom/master-data-grid/utils/export-utils.ts +67 -0
  144. package/src/custom/master-data-grid/utils/filter-fns.ts +290 -0
  145. package/src/custom/master-data-grid/utils/index.ts +8 -0
  146. package/src/custom/master-data-grid/utils/pinning-utils.ts +88 -0
  147. package/src/custom/master-data-grid/utils/translation-utils.ts +42 -0
  148. package/src/custom/multi-select.tsx +432 -0
  149. package/src/custom/password-input.tsx +194 -0
  150. package/src/custom/phone-input.tsx +172 -0
  151. package/src/custom/schema-form/custom/index.tsx +1 -0
  152. package/src/custom/schema-form/custom/label.tsx +53 -0
  153. package/src/custom/schema-form/fields/base-input-field.tsx +82 -0
  154. package/src/custom/schema-form/fields/field.tsx +67 -0
  155. package/src/custom/schema-form/fields/index.tsx +5 -0
  156. package/src/custom/schema-form/fields/object.tsx +12 -0
  157. package/src/custom/schema-form/fields/table-array/array-field-item.tsx +90 -0
  158. package/src/custom/schema-form/fields/table-array/array-field-template.tsx +115 -0
  159. package/src/custom/schema-form/index.tsx +259 -0
  160. package/src/custom/schema-form/templates/description.tsx +20 -0
  161. package/src/custom/schema-form/templates/index.tsx +2 -0
  162. package/src/custom/schema-form/templates/submit.tsx +32 -0
  163. package/src/custom/schema-form/types.ts +64 -0
  164. package/src/custom/schema-form/utils/index.ts +4 -0
  165. package/src/custom/schema-form/utils/schema-dependency.ts +655 -0
  166. package/src/custom/schema-form/utils/schemas.ts +289 -0
  167. package/src/custom/schema-form/utils/validation.ts +23 -0
  168. package/src/custom/schema-form/widgets/boolean.tsx +77 -0
  169. package/src/custom/schema-form/widgets/combobox.tsx +274 -0
  170. package/src/custom/schema-form/widgets/date.tsx +59 -0
  171. package/src/custom/schema-form/widgets/email.tsx +34 -0
  172. package/src/custom/schema-form/widgets/index.tsx +10 -0
  173. package/src/custom/schema-form/widgets/password.tsx +40 -0
  174. package/src/custom/schema-form/widgets/phone.tsx +40 -0
  175. package/src/custom/schema-form/widgets/select.tsx +105 -0
  176. package/src/custom/schema-form/widgets/selectable.tsx +25 -0
  177. package/src/custom/schema-form/widgets/string-array.tsx +296 -0
  178. package/src/custom/schema-form/widgets/url.tsx +56 -0
  179. package/src/custom/section-layout-v2.tsx +212 -0
  180. package/src/custom/select-tabs.tsx +109 -0
  181. package/src/custom/selectable.tsx +316 -0
  182. package/src/custom/stepper.tsx +236 -0
  183. package/src/custom/tab-layout.tsx +213 -0
  184. package/src/custom/tanstack-table/fields/index.tsx +12 -0
  185. package/src/custom/tanstack-table/fields/tanstack-table-action-dialogs.tsx +89 -0
  186. package/src/custom/tanstack-table/fields/tanstack-table-column-header.tsx +66 -0
  187. package/src/custom/tanstack-table/fields/tanstack-table-filter-date.tsx +180 -0
  188. package/src/custom/tanstack-table/fields/tanstack-table-filter-faceted.tsx +158 -0
  189. package/src/custom/tanstack-table/fields/tanstack-table-filter-text.tsx +76 -0
  190. package/src/custom/tanstack-table/fields/tanstack-table-pagination.tsx +136 -0
  191. package/src/custom/tanstack-table/fields/tanstack-table-plain-table.tsx +142 -0
  192. package/src/custom/tanstack-table/fields/tanstack-table-row-actions-confirmation.tsx +77 -0
  193. package/src/custom/tanstack-table/fields/tanstack-table-row-actions-custom-dialog.tsx +87 -0
  194. package/src/custom/tanstack-table/fields/tanstack-table-row-actions.tsx +151 -0
  195. package/src/custom/tanstack-table/fields/tanstack-table-table-actions-custom-dialog.tsx +88 -0
  196. package/src/custom/tanstack-table/fields/tanstack-table-table-actions-schemaform-dialog.tsx +47 -0
  197. package/src/custom/tanstack-table/fields/tanstack-table-toolbar.tsx +143 -0
  198. package/src/custom/tanstack-table/fields/tanstack-table-view-options.tsx +171 -0
  199. package/src/custom/tanstack-table/index.tsx +244 -0
  200. package/src/custom/tanstack-table/types/index.ts +328 -0
  201. package/src/custom/tanstack-table/utils/cell-with-actions.tsx +21 -0
  202. package/src/custom/tanstack-table/utils/column-names.ts +26 -0
  203. package/src/custom/tanstack-table/utils/columns-by-row-data.tsx +312 -0
  204. package/src/custom/tanstack-table/utils/editable-columns-by-row-data.tsx +219 -0
  205. package/src/custom/tanstack-table/utils/faceted-boolean-options.tsx +22 -0
  206. package/src/custom/tanstack-table/utils/index.tsx +10 -0
  207. package/src/custom/tanstack-table/utils/pinning-styles.ts +57 -0
  208. package/src/custom/tanstack-table/utils/table.tsx +83 -0
  209. package/src/custom/tanstack-table/utils/test-conditions.ts +17 -0
  210. package/src/custom/timeline.tsx +208 -0
  211. package/src/custom/tree.tsx +200 -0
  212. package/src/custom/tscanify/browser.ts +66 -0
  213. package/src/custom/tscanify/index.ts +51 -0
  214. package/src/custom/tscanify/tscanify-browser.ts +522 -0
  215. package/src/custom/tscanify/tscanify.ts +262 -0
  216. package/src/custom/tscanify/types.ts +22 -0
  217. package/src/custom/webcam.tsx +737 -0
  218. package/src/hooks/.gitkeep +0 -0
  219. package/src/hooks/use-callback-ref.ts +27 -0
  220. package/src/hooks/use-controllable-state.ts +67 -0
  221. package/src/hooks/use-debounce.ts +19 -0
  222. package/src/hooks/use-is-visible.ts +23 -0
  223. package/src/hooks/use-media-query.ts +21 -0
  224. package/src/hooks/use-mobile.ts +21 -0
  225. package/src/hooks/use-on-window-resize.ts +15 -0
  226. package/src/hooks/use-scroll.tsx +22 -0
  227. package/src/lib/utils.ts +61 -0
  228. package/src/lib/zod.ts +2 -0
  229. package/src/styles/core.css +57 -0
  230. package/src/styles/globals.css +130 -0
  231. package/src/test/email-input.test.tsx +217 -0
  232. package/src/test/password-input.test.tsx +92 -0
  233. package/src/test/select-tabs.test.tsx +302 -0
  234. package/src/test/selectable.test.tsx +1093 -0
  235. package/tsconfig.json +13 -0
  236. package/tsconfig.lint.json +8 -0
@@ -0,0 +1,522 @@
1
+ /*! tscanify-browser v1.0.0 | Based on jscanify v1.4.0 | (c) ColonelParrot and other contributors | MIT License */
2
+
3
+ import cv, { Mat } from "opencv-ts";
4
+ import { CornerPoints, HighlightOptions, Point, WindowWithCV } from "./types";
5
+
6
+ /**
7
+ * Calculates distance between two points.
8
+ * @param p1 point 1
9
+ * @param p2 point 2
10
+ * @returns distance between two points
11
+ */
12
+ function distance(p1: Point, p2: Point): number {
13
+ return Math.hypot(p1.x - p2.x, p1.y - p2.y);
14
+ }
15
+
16
+ export class TScanifyBrowser {
17
+ constructor() {}
18
+
19
+ /**
20
+ * Loads OpenCV module
21
+ * @param callback Function to call when OpenCV is initialized
22
+ */
23
+ loadOpenCV(callback: (opencv: typeof cv) => void): void {
24
+ // First check if global cv is available (browser-loaded version)
25
+ if (
26
+ (window as unknown as WindowWithCV).cv &&
27
+ (window as unknown as WindowWithCV).cv.Mat
28
+ ) {
29
+ callback((window as unknown as WindowWithCV).cv);
30
+ return;
31
+ }
32
+
33
+ // Next, check if the imported cv is available
34
+ if (cv && (cv as typeof cv).Mat) {
35
+ callback(cv);
36
+ return;
37
+ }
38
+
39
+ // Wait for opencv to be ready (check both global and imported)
40
+ const checkInterval = setInterval(() => {
41
+ // First priority: global cv
42
+ if (
43
+ (window as unknown as WindowWithCV).cv &&
44
+ (window as unknown as WindowWithCV).cv.Mat
45
+ ) {
46
+ clearInterval(checkInterval);
47
+ callback((window as unknown as WindowWithCV).cv);
48
+ return;
49
+ }
50
+
51
+ // Second priority: imported cv
52
+ if (cv && (cv as typeof cv).Mat) {
53
+ clearInterval(checkInterval);
54
+ callback(cv);
55
+ return;
56
+ }
57
+ }, 200);
58
+
59
+ // Set timeout to avoid infinite waiting
60
+ setTimeout(() => {
61
+ clearInterval(checkInterval);
62
+ // Try to load it dynamically as a last resort
63
+ this.loadOpenCVDynamically().then((loadedCv) => {
64
+ callback(loadedCv);
65
+ });
66
+ }, 8000); // 8 seconds timeout
67
+ }
68
+
69
+ /**
70
+ * Dynamically loads OpenCV.js as a last resort
71
+ * @returns Promise that resolves with the OpenCV object
72
+ */
73
+ private loadOpenCVDynamically(): Promise<typeof cv> {
74
+ return new Promise((resolve, reject) => {
75
+ if (
76
+ (window as unknown as WindowWithCV).cv &&
77
+ (window as unknown as WindowWithCV).cv.Mat
78
+ ) {
79
+ resolve((window as unknown as WindowWithCV).cv);
80
+ return;
81
+ }
82
+
83
+ // Try to load OpenCV.js dynamically
84
+ const script = document.createElement("script");
85
+ script.setAttribute("async", "true");
86
+ script.setAttribute("type", "text/javascript");
87
+ script.setAttribute("src", "https://docs.opencv.org/4.5.5/opencv.js");
88
+
89
+ script.onload = () => {
90
+ // Check if OpenCV is available
91
+ const checkInterval = setInterval(() => {
92
+ if (
93
+ (window as unknown as WindowWithCV).cv &&
94
+ (window as unknown as WindowWithCV).cv.Mat
95
+ ) {
96
+ clearInterval(checkInterval);
97
+ resolve((window as unknown as WindowWithCV).cv);
98
+ }
99
+ }, 100);
100
+
101
+ // Timeout after 5 seconds
102
+ setTimeout(() => {
103
+ clearInterval(checkInterval);
104
+ reject(new Error("OpenCV dynamic load timed out"));
105
+ }, 5000);
106
+ };
107
+
108
+ script.onerror = () => {
109
+ reject(new Error("Failed to load OpenCV.js dynamically"));
110
+ };
111
+
112
+ document.body.appendChild(script);
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Creates a new canvas of the given width and height
118
+ * @param width Canvas width
119
+ * @param height Canvas height
120
+ * @returns Canvas object
121
+ */
122
+ createCanvas(width: number, height: number): HTMLCanvasElement {
123
+ const canvas = document.createElement("canvas");
124
+ canvas.width = width;
125
+ canvas.height = height;
126
+ return canvas;
127
+ }
128
+
129
+ /**
130
+ * Gets a Mat from an image or canvas
131
+ * @param image Image or Canvas element
132
+ * @returns OpenCV Mat
133
+ */
134
+ imageToMat(image: HTMLImageElement | HTMLCanvasElement): Mat {
135
+ const canvas = document.createElement("canvas");
136
+ const ctx = canvas.getContext("2d")!;
137
+
138
+ // Set canvas dimensions to match input
139
+ canvas.width = image.width as number;
140
+ canvas.height = image.height as number;
141
+
142
+ // Draw the image to the canvas
143
+ ctx.drawImage(image, 0, 0, image.width as number, image.height as number);
144
+
145
+ // Get the image data from the canvas
146
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
147
+
148
+ // Convert to Mat
149
+ return cv.matFromImageData(imageData);
150
+ }
151
+
152
+ /**
153
+ * Resizes an image to fit within the given maximum width and height
154
+ * @param image Image to resize
155
+ * @param maxWidth Maximum width
156
+ * @param maxHeight Maximum height
157
+ * @returns Resized image as Canvas
158
+ */
159
+ resizeImage(
160
+ image: HTMLImageElement | HTMLCanvasElement,
161
+ maxWidth = 500,
162
+ maxHeight = 500
163
+ ): HTMLCanvasElement {
164
+ const canvas = this.createCanvas(1, 1);
165
+ const ctx = canvas.getContext("2d")!;
166
+
167
+ const originalWidth = image.width as number;
168
+ const originalHeight = image.height as number;
169
+
170
+ // Calculate new dimensions
171
+ let newWidth = originalWidth;
172
+ let newHeight = originalHeight;
173
+
174
+ if (originalWidth > maxWidth) {
175
+ newWidth = maxWidth;
176
+ newHeight = (originalHeight * maxWidth) / originalWidth;
177
+ }
178
+
179
+ if (newHeight > maxHeight) {
180
+ newHeight = maxHeight;
181
+ newWidth = (newWidth * maxHeight) / newHeight;
182
+ }
183
+
184
+ // Set canvas to new dimensions
185
+ canvas.width = newWidth;
186
+ canvas.height = newHeight;
187
+
188
+ // Draw the image at the new size
189
+ ctx.drawImage(image, 0, 0, newWidth, newHeight);
190
+ return canvas;
191
+ }
192
+
193
+ /**
194
+ * Finds the corners of a document in an image
195
+ * @param src Input image as Mat
196
+ * @returns Four corner points of the document
197
+ */
198
+ findDocumentCorners(src: Mat): CornerPoints | null {
199
+ try {
200
+ // Convert to grayscale
201
+ const gray = new cv.Mat();
202
+ cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY);
203
+
204
+ // Apply Gaussian blur
205
+ const blur = new cv.Mat();
206
+ const ksize = new cv.Size(5, 5);
207
+ cv.GaussianBlur(gray, blur, ksize, 0, 0, cv.BORDER_DEFAULT);
208
+
209
+ // Apply Canny edge detection
210
+ const edges = new cv.Mat();
211
+ cv.Canny(blur, edges, 75, 200);
212
+
213
+ // Find contours
214
+ const contours = new cv.MatVector();
215
+ const hierarchy = new cv.Mat();
216
+ cv.findContours(
217
+ edges,
218
+ contours,
219
+ hierarchy,
220
+ cv.RETR_EXTERNAL,
221
+ cv.CHAIN_APPROX_SIMPLE
222
+ );
223
+
224
+ // Find largest contour
225
+ let maxArea = 0;
226
+ let largestContourIndex = -1;
227
+
228
+ for (let i = 0; i < contours.size(); i++) {
229
+ const contour = contours.get(i);
230
+ const area = cv.contourArea(contour);
231
+
232
+ if (area > maxArea) {
233
+ maxArea = area;
234
+ largestContourIndex = i;
235
+ }
236
+
237
+ contour.delete();
238
+ }
239
+
240
+ // If no contour found, return null
241
+ if (largestContourIndex === -1) {
242
+ // Clean up
243
+ gray.delete();
244
+ blur.delete();
245
+ edges.delete();
246
+ contours.delete();
247
+ hierarchy.delete();
248
+ return null;
249
+ }
250
+
251
+ // Get largest contour
252
+ const largestContour = contours.get(largestContourIndex);
253
+
254
+ // Approximate the contour with a polygon
255
+ const epsilon = 0.02 * cv.arcLength(largestContour, true);
256
+ const approx = new cv.Mat();
257
+ cv.approxPolyDP(largestContour, approx, epsilon, true);
258
+
259
+ // If the approximation has 4 points, we assume it's a quadrilateral
260
+ let corners: CornerPoints | null = null;
261
+
262
+ if (approx.rows === 4) {
263
+ const points: Point[] = [];
264
+
265
+ for (let i = 0; i < 4; i++) {
266
+ points.push({
267
+ x: approx.data32S[i * 2] as number,
268
+ y: approx.data32S[i * 2 + 1] as number,
269
+ });
270
+ }
271
+
272
+ // Order the points: top-left, top-right, bottom-right, bottom-left
273
+ points.sort((a, b) => a.y - b.y);
274
+
275
+ // Top points (sorted by y)
276
+ const topPoints = points.slice(0, 2).sort((a, b) => a.x - b.x);
277
+ const topLeftCorner = topPoints[0]!;
278
+ const topRightCorner = topPoints[1]!;
279
+
280
+ // Bottom points (sorted by y)
281
+ const bottomPoints = points.slice(2).sort((a, b) => a.x - b.x);
282
+ const bottomLeftCorner = bottomPoints[0]!;
283
+ const bottomRightCorner = bottomPoints[1]!;
284
+
285
+ corners = {
286
+ topLeftCorner,
287
+ topRightCorner,
288
+ bottomRightCorner,
289
+ bottomLeftCorner,
290
+ };
291
+ }
292
+
293
+ // Clean up
294
+ gray.delete();
295
+ blur.delete();
296
+ edges.delete();
297
+ contours.delete();
298
+ hierarchy.delete();
299
+ approx.delete();
300
+ largestContour.delete();
301
+
302
+ return corners;
303
+ } catch {
304
+ return null;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Performs perspective transform on an image based on corner points
310
+ * @param src Source image as Mat
311
+ * @param corners Corner points of document
312
+ * @param width Output width
313
+ * @param height Output height
314
+ * @returns Transformed Mat
315
+ */
316
+ warpPerspective(
317
+ src: Mat,
318
+ corners: CornerPoints,
319
+ width: number,
320
+ height: number
321
+ ): Mat {
322
+ // Create matrices for source and destination points
323
+ const srcPoints = cv.matFromArray(4, 1, cv.CV_32FC2, [
324
+ corners.topLeftCorner.x,
325
+ corners.topLeftCorner.y,
326
+ corners.topRightCorner.x,
327
+ corners.topRightCorner.y,
328
+ corners.bottomRightCorner.x,
329
+ corners.bottomRightCorner.y,
330
+ corners.bottomLeftCorner.x,
331
+ corners.bottomLeftCorner.y,
332
+ ]);
333
+
334
+ const dstPoints = cv.matFromArray(4, 1, cv.CV_32FC2, [
335
+ 0,
336
+ 0,
337
+ width,
338
+ 0,
339
+ width,
340
+ height,
341
+ 0,
342
+ height,
343
+ ]);
344
+
345
+ // Get perspective transform matrix
346
+ const M = cv.getPerspectiveTransform(srcPoints, dstPoints);
347
+
348
+ // Apply perspective transform
349
+ const dst = new cv.Mat();
350
+ cv.warpPerspective(
351
+ src,
352
+ dst,
353
+ M,
354
+ new cv.Size(width, height),
355
+ cv.INTER_LINEAR,
356
+ cv.BORDER_CONSTANT,
357
+ new cv.Scalar()
358
+ );
359
+
360
+ // Clean up
361
+ srcPoints.delete();
362
+ dstPoints.delete();
363
+ M.delete();
364
+
365
+ return dst;
366
+ }
367
+
368
+ /**
369
+ * Highlights corners of a detected document in an image
370
+ * @param image Source image
371
+ * @param corners Corner points to highlight
372
+ * @param options Highlight options
373
+ * @returns Canvas with highlighted corners
374
+ */
375
+ highlightCorners(
376
+ image: HTMLImageElement | HTMLCanvasElement,
377
+ corners: CornerPoints,
378
+ options: HighlightOptions = {}
379
+ ): HTMLCanvasElement {
380
+ // Use our own default options since the HighlightOptions interface only has color and thickness
381
+ const thickness = options.thickness || 2;
382
+ const color = options.color || "#3cba54";
383
+ const pointRadius = 3;
384
+ const pointColor = "#db3236";
385
+
386
+ const canvas = this.createCanvas(
387
+ image.width as number,
388
+ image.height as number
389
+ );
390
+ const ctx = canvas.getContext("2d")!;
391
+
392
+ // Draw the original image
393
+ ctx.drawImage(image, 0, 0);
394
+
395
+ // Draw lines connecting the corners
396
+ ctx.strokeStyle = color;
397
+ ctx.lineWidth = thickness;
398
+ ctx.beginPath();
399
+ ctx.moveTo(corners.topLeftCorner.x, corners.topLeftCorner.y);
400
+ ctx.lineTo(corners.topRightCorner.x, corners.topRightCorner.y);
401
+ ctx.lineTo(corners.bottomRightCorner.x, corners.bottomRightCorner.y);
402
+ ctx.lineTo(corners.bottomLeftCorner.x, corners.bottomLeftCorner.y);
403
+ ctx.lineTo(corners.topLeftCorner.x, corners.topLeftCorner.y);
404
+ ctx.stroke();
405
+
406
+ // Draw dots at the corners
407
+ const cornerPoints = [
408
+ corners.topLeftCorner,
409
+ corners.topRightCorner,
410
+ corners.bottomRightCorner,
411
+ corners.bottomLeftCorner,
412
+ ];
413
+
414
+ ctx.fillStyle = pointColor;
415
+ cornerPoints.forEach((point) => {
416
+ ctx.beginPath();
417
+ ctx.arc(point.x, point.y, pointRadius, 0, Math.PI * 2);
418
+ ctx.fill();
419
+ });
420
+
421
+ return canvas;
422
+ }
423
+
424
+ /**
425
+ * Processes an image to detect and extract a document
426
+ * @param image Input image
427
+ * @returns Object containing original, highlighted and processed images as canvases
428
+ */
429
+ async processImage(image: HTMLImageElement): Promise<{
430
+ original: HTMLCanvasElement;
431
+ highlighted: HTMLCanvasElement | null;
432
+ processed: HTMLCanvasElement | null;
433
+ }> {
434
+ // Resize the image for processing
435
+ const resized = this.resizeImage(image);
436
+
437
+ // Convert to Mat
438
+ const src = this.imageToMat(resized);
439
+
440
+ // Find document corners
441
+ const corners = this.findDocumentCorners(src);
442
+
443
+ if (!corners) {
444
+ // No document found
445
+ src.delete();
446
+ return {
447
+ original: resized,
448
+ highlighted: null,
449
+ processed: null,
450
+ };
451
+ }
452
+
453
+ // Highlight corners
454
+ const highlighted = this.highlightCorners(resized, corners);
455
+
456
+ // Calculate output dimensions
457
+ const width = Math.max(
458
+ distance(corners.topRightCorner, corners.topLeftCorner),
459
+ distance(corners.bottomRightCorner, corners.bottomLeftCorner)
460
+ );
461
+
462
+ const height = Math.max(
463
+ distance(corners.topLeftCorner, corners.bottomLeftCorner),
464
+ distance(corners.topRightCorner, corners.bottomRightCorner)
465
+ );
466
+
467
+ // Apply perspective transform
468
+ const warped = this.warpPerspective(src, corners, width, height);
469
+
470
+ // Convert Mat to Canvas
471
+ const processed = this.createCanvas(width, height);
472
+ const ctx = processed.getContext("2d")!;
473
+
474
+ const imgData = new ImageData(
475
+ new Uint8ClampedArray(warped.data),
476
+ warped.cols,
477
+ warped.rows
478
+ );
479
+
480
+ ctx.putImageData(imgData, 0, 0);
481
+
482
+ // Clean up
483
+ src.delete();
484
+ warped.delete();
485
+
486
+ return {
487
+ original: resized,
488
+ highlighted,
489
+ processed,
490
+ };
491
+ }
492
+
493
+ /**
494
+ * Converts a canvas to a data URL
495
+ * @param canvas Canvas to convert
496
+ * @param type Image type (default: 'image/png')
497
+ * @param quality Image quality for JPEG (0-1)
498
+ * @returns Data URL string
499
+ */
500
+ canvasToDataURL(
501
+ canvas: HTMLCanvasElement,
502
+ type = "image/png",
503
+ quality = 0.9
504
+ ): string {
505
+ return canvas.toDataURL(type, quality);
506
+ }
507
+
508
+ /**
509
+ * Creates an image element from a data URL
510
+ * @param dataURL Data URL string
511
+ * @returns Promise resolving to an Image element
512
+ */
513
+ dataURLToImage(dataURL: string): Promise<HTMLImageElement> {
514
+ return new Promise((resolve, reject) => {
515
+ const img = new Image();
516
+ img.onload = () => resolve(img);
517
+ img.onerror = () =>
518
+ reject(new Error("Failed to load image from data URL"));
519
+ img.src = dataURL;
520
+ });
521
+ }
522
+ }