@jaak.ai/stamps 2.0.0-dev.21 → 2.0.0-dev.23

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.
package/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # Jaak Stamps - Document Detector Web Component
2
2
 
3
- [![Built With Stencil](https://img.shields.io/badge/-Built%20With%20Stencil-16161d.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BCgkuc3Qwe2ZpbGw6I0ZGRkZGRjt9Cjwvc3R5bGU%2BCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00MjQuNywzNzMuOWMwLDM3LjYtNTUuMSw2OC42LTkyLjcsNjguNkgxODAuNGMtMzcuOSwwLTkyLjctMzAuNy05Mi43LTY4LjZ2LTMuNmgzMzYuOVYzNzMuOXoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQyNC43LDI5Mi4xSDE4MC40Yy0zNy42LDAtOTIuNy0zMS05Mi43LTY4LjZ2LTMuNkgzMzJjMzcuNiwwLDkyLjcsMzEsOTIuNyw2OC42VjI5Mi4xeiIvPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNDI0LjcsMTQxLjdIODcuN3YtMy42YzAtMzcuNiw1NC44LTY4LjYsOTIuNy02OC42SDMzMmMzNy45LDAsOTIuNywzMC43LDkyLjcsNjguNlYxNDEuN3oiLz4KPC9zdmc%2BCg%3D%3D&colorA=16161d&style=flat-square)](https://stenciljs.com)
4
-
5
3
  Un componente web para la detección y captura automática de documentos de identificación usando visión artificial y redes neuronales.
6
4
 
7
5
  ## Características
@@ -10,6 +8,7 @@ Un componente web para la detección y captura automática de documentos de iden
10
8
  - 🎯 **Guía visual** para posicionamiento óptimo del documento
11
9
  - 📋 **Captura doble cara** (frente y reverso) con opción de omitir reverso
12
10
  - 🖼 **Doble salida** para cada lado: imagen completa y recorte del documento
11
+ - ✂️ **Recorte configurable** con margen personalizable alrededor del documento
13
12
  - 🔧 **API programática** para control total del proceso
14
13
  - 📱 **Responsive** y compatible con dispositivos móviles
15
14
  - ⚡ **Optimización automática** usando ONNX Runtime Web
@@ -43,7 +42,7 @@ yarn add @jaak.ai/stamps
43
42
  <script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
44
43
  </head>
45
44
  <body>
46
- <jaak-stamps id="detector" debug="false"></jaak-stamps>
45
+ <jaak-stamps id="detector" debug="false" mask-size="90" crop-margin="10"></jaak-stamps>
47
46
 
48
47
  <button onclick="startCapture()">Iniciar Captura</button>
49
48
  <button onclick="stopCapture()">Detener</button>
@@ -140,6 +139,8 @@ import { Component, ElementRef, ViewChild, OnInit } from '@angular/core';
140
139
  <jaak-stamps
141
140
  #detector
142
141
  [debug]="false"
142
+ [maskSize]="90"
143
+ [cropMargin]="10"
143
144
  (captureCompleted)="onCaptureCompleted($event)"
144
145
  (isReady)="onComponentReady($event)">
145
146
  </jaak-stamps>
@@ -241,7 +242,7 @@ import React, { useRef, useEffect, useImperativeHandle, forwardRef } from 'react
241
242
  // Importar el web component
242
243
  import '@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js';
243
244
 
244
- const JaakStampsWrapper = forwardRef(({ debug = false, onCaptureCompleted, onIsReady }, ref) => {
245
+ const JaakStampsWrapper = forwardRef(({ debug = false, maskSize = 90, cropMargin = 0, onCaptureCompleted, onIsReady }, ref) => {
245
246
  const elementRef = useRef(null);
246
247
 
247
248
  useImperativeHandle(ref, () => ({
@@ -285,6 +286,8 @@ const JaakStampsWrapper = forwardRef(({ debug = false, onCaptureCompleted, onIsR
285
286
  <jaak-stamps
286
287
  ref={elementRef}
287
288
  debug={debug}
289
+ mask-size={maskSize}
290
+ crop-margin={cropMargin}
288
291
  />
289
292
  );
290
293
  });
@@ -364,6 +367,8 @@ function App() {
364
367
  <JaakStampsWrapper
365
368
  ref={detectorRef}
366
369
  debug={false}
370
+ maskSize={90}
371
+ cropMargin={10}
367
372
  onCaptureCompleted={handleCaptureCompleted}
368
373
  onIsReady={handleIsReady}
369
374
  />
@@ -435,6 +440,8 @@ export default App;
435
440
  |-----------|------|---------------|-------------|
436
441
  | `debug` | `boolean` | `false` | Activa el modo debug para mostrar información de depuración |
437
442
  | `alignmentTolerance` | `number` | `10` | Tolerancia en píxeles para considerar un lado alineado |
443
+ | `maskSize` | `number` | `90` | Porcentaje del video que ocupará la máscara de detección (50-100) |
444
+ | `cropMargin` | `number` | `0` | Margen en píxeles agregado al recorte del documento (0-100) |
438
445
 
439
446
  ### Métodos Públicos
440
447
 
@@ -18,7 +18,7 @@ var patchBrowser = () => {
18
18
 
19
19
  patchBrowser().then(async (options) => {
20
20
  await index.globalScripts();
21
- return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"isVideoActive":[32],"statusMessage":[32],"statusColor":[32],"bestScore":[32],"capturedFullFrame":[32],"capturedCroppedId":[32],"capturedBackFullFrame":[32],"capturedBackCroppedId":[32],"captureStep":[32],"isCapturing":[32],"showFlipAnimation":[32],"showSuccessAnimation":[32],"shouldMirrorVideo":[32],"sideAlignment":[32],"isDetectionPaused":[32],"isLoading":[32],"isModelPreloaded":[32],"isMaskReady":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"getStatus":[64],"preloadModel":[64],"skipBackCapture":[64]}]]]], options);
21
+ return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"isVideoActive":[32],"statusMessage":[32],"statusColor":[32],"bestScore":[32],"capturedFullFrame":[32],"capturedCroppedId":[32],"capturedBackFullFrame":[32],"capturedBackCroppedId":[32],"captureStep":[32],"isCapturing":[32],"showFlipAnimation":[32],"showSuccessAnimation":[32],"shouldMirrorVideo":[32],"sideAlignment":[32],"isDetectionPaused":[32],"isLoading":[32],"isModelPreloaded":[32],"isMaskReady":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"getStatus":[64],"preloadModel":[64],"skipBackCapture":[64]}]]]], options);
22
22
  });
23
23
 
24
24
  exports.setNonce = index.setNonce;
@@ -13,6 +13,8 @@ const JaakStamps = class {
13
13
  get el() { return index.getElement(this); }
14
14
  debug = false;
15
15
  alignmentTolerance = 10; // Tolerancia en píxeles para considerar un lado alineado (escalado para 320x320)
16
+ maskSize = 90; // Porcentaje del video que ocupará la máscara (default 90%)
17
+ cropMargin = 0; // Margen en píxeles para el recorte del documento (default 0)
16
18
  captureCompleted;
17
19
  isReady;
18
20
  isVideoActive = false;
@@ -68,6 +70,18 @@ const JaakStamps = class {
68
70
  console.log(...args);
69
71
  }
70
72
  }
73
+ validateMaskSize() {
74
+ if (this.maskSize < 50 || this.maskSize > 100) {
75
+ console.warn(`maskSize debe estar entre 50 y 100. Valor actual: ${this.maskSize}. Usando valor por defecto: 90`);
76
+ this.maskSize = 90;
77
+ }
78
+ }
79
+ validateCropMargin() {
80
+ if (this.cropMargin < 0 || this.cropMargin > 100) {
81
+ console.warn(`cropMargin debe estar entre 0 y 100. Valor actual: ${this.cropMargin}. Usando valor por defecto: 0`);
82
+ this.cropMargin = 0;
83
+ }
84
+ }
71
85
  emitReadyEvent() {
72
86
  const isDocumentReady = !!window.ort && this.isModelPreloaded;
73
87
  this.isReady.emit(isDocumentReady);
@@ -81,6 +95,8 @@ const JaakStamps = class {
81
95
  return settings.facingMode === 'environment';
82
96
  }
83
97
  async componentDidLoad() {
98
+ this.validateMaskSize();
99
+ this.validateCropMargin();
84
100
  if (!window.ort) {
85
101
  const script = document.createElement('script');
86
102
  script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
@@ -148,14 +164,15 @@ const JaakStamps = class {
148
164
  // Calculate what would be the max width if limited by video height
149
165
  const maxWidthByHeight = displayedVideoHeight * this.ID1_ASPECT_RATIO;
150
166
  let maskWidthInVideo, maskHeightInVideo;
167
+ const sizeRatio = this.maskSize / 100;
151
168
  if (maxWidthByHeight <= displayedVideoWidth) {
152
169
  // Video height is the limiting factor
153
- maskHeightInVideo = displayedVideoHeight * 0.9; // Use 90% of available height
170
+ maskHeightInVideo = displayedVideoHeight * sizeRatio;
154
171
  maskWidthInVideo = maskHeightInVideo * this.ID1_ASPECT_RATIO;
155
172
  }
156
173
  else {
157
174
  // Video width is the limiting factor
158
- maskWidthInVideo = displayedVideoWidth * 0.9; // Use 90% of available width
175
+ maskWidthInVideo = displayedVideoWidth * sizeRatio;
159
176
  maskHeightInVideo = maskWidthInVideo / this.ID1_ASPECT_RATIO;
160
177
  }
161
178
  // Convert to percentages of the container
@@ -593,14 +610,15 @@ const JaakStamps = class {
593
610
  // Calculate mask dimensions using the same logic as updateMaskDimensions
594
611
  const maxWidthByHeight = displayedVideoHeight * this.ID1_ASPECT_RATIO;
595
612
  let visualMaskWidth, visualMaskHeight;
613
+ const sizeRatio = this.maskSize / 100;
596
614
  if (maxWidthByHeight <= displayedVideoWidth) {
597
615
  // Video height is the limiting factor
598
- visualMaskHeight = displayedVideoHeight * 0.9;
616
+ visualMaskHeight = displayedVideoHeight * sizeRatio;
599
617
  visualMaskWidth = visualMaskHeight * this.ID1_ASPECT_RATIO;
600
618
  }
601
619
  else {
602
620
  // Video width is the limiting factor
603
- visualMaskWidth = displayedVideoWidth * 0.9;
621
+ visualMaskWidth = displayedVideoWidth * sizeRatio;
604
622
  visualMaskHeight = visualMaskWidth / this.ID1_ASPECT_RATIO;
605
623
  }
606
624
  // Now map these visual mask dimensions to the distorted model space
@@ -767,10 +785,10 @@ const JaakStamps = class {
767
785
  // Calcular las coordenadas de recorte basadas en la detección
768
786
  const scaleX = this.videoRef.videoWidth / this.INPUT_SIZE;
769
787
  const scaleY = this.videoRef.videoHeight / this.INPUT_SIZE;
770
- const cropX = Math.max(0, this.lastDetectedBox.x * scaleX);
771
- const cropY = Math.max(0, this.lastDetectedBox.y * scaleY);
772
- const cropWidth = Math.min(this.lastDetectedBox.w * scaleX, this.videoRef.videoWidth - cropX);
773
- const cropHeight = Math.min(this.lastDetectedBox.h * scaleY, this.videoRef.videoHeight - cropY);
788
+ const cropX = Math.max(0, (this.lastDetectedBox.x * scaleX) - this.cropMargin);
789
+ const cropY = Math.max(0, (this.lastDetectedBox.y * scaleY) - this.cropMargin);
790
+ const cropWidth = Math.min((this.lastDetectedBox.w * scaleX) + (2 * this.cropMargin), this.videoRef.videoWidth - cropX);
791
+ const cropHeight = Math.min((this.lastDetectedBox.h * scaleY) + (2 * this.cropMargin), this.videoRef.videoHeight - cropY);
774
792
  // OPTIMIZATION: Create temporary canvas only for cropped version
775
793
  // (We reuse main capture canvas for full frame)
776
794
  const croppedCanvas = document.createElement('canvas');
@@ -914,7 +932,7 @@ const JaakStamps = class {
914
932
  this.cleanup();
915
933
  }
916
934
  render() {
917
- return (index.h("div", { key: 'b63a5966ee08d4de7b5d8a10dbf248150e8620ec', class: "detector-container" }, index.h("div", { key: '1c62d540a1802a758b90a543edfdc364123ae950', class: "video-container" }, index.h("video", { key: '18e066e0005c3b287983ebb29c9e4aa147eaaf8d', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: this.isVideoActive ? 'block' : 'none' } }), index.h("canvas", { key: '43cd238e38a5a2ff9c140800ac686580a7ca33b9', ref: el => this.canvasRef = el, class: this.shouldMirrorVideo ? 'mirror' : '' }), this.isMaskReady && (index.h("div", { key: 'f43db67b9d1ef47264092694c3ab8afa9925e258', class: "overlay-mask" }, index.h("div", { key: 'ac60d94edc4cfa6ccf91ba6e04d2be6e4368be97', class: "card-outline" }, index.h("div", { key: 'de6aca9bbfe778890adf4c69e8525a114c37f6d4', class: "side side-top" }), index.h("div", { key: '7ce7b1e2215b01ea5d714b8ecc988361e384b6f7', class: "side side-right" }), index.h("div", { key: 'b79a68f68facb42ca139d6a2bc8e1e6d7133872d', class: "side side-bottom" }), index.h("div", { key: '81d90c4a08cfc2267cec195a3b342c87bb1d07d0', class: "side side-left" }), index.h("div", { key: 'c93c83abb8e17a5cb9c1bd53b2e1239ef50c7bf4', class: "corner corner-tl" }), index.h("div", { key: '81ca7cf2aab7026e851bd226843a6db5807315d5', class: "corner corner-tr" }), index.h("div", { key: 'c451fe226ac15023ee9799443060f1da620cb3b3', class: "corner corner-bl" }), index.h("div", { key: '7c3e2b2a21bd37144fe6b4d5df67df625dc12103', class: "corner corner-br" }), !this.showFlipAnimation && !this.showSuccessAnimation && (index.h("div", { key: '74ab171b64bdf0e1269845b4fcd9bf8e2caf891d', class: "guide-text" }, this.statusMessage))), this.captureStep === 'back' && !this.showFlipAnimation && !this.showSuccessAnimation && (index.h("button", { key: '2b2b9387ce8b023acf0d65c7f7b3480211f7cb2d', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")))), this.isCapturing && (index.h("div", { key: 'eabd88499f52098f79e434afcce73acb2727c411', class: "capture-animation" })), this.showFlipAnimation && (index.h("div", { key: '9affe6040a39260747db98db2b68a103fb719fbb', class: "flip-animation" }, index.h("div", { key: '8b25f3688a16f4b070664237a093122a6720edfb', class: "id-card-icon" }), index.h("div", { key: '805b11cfb4d06a25e02370b06f6cf15f0f0cf6b5', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), this.showSuccessAnimation && (index.h("div", { key: 'cba337b6fef8b919425566c0c9c5de797d42c4c8', class: "success-animation" }, index.h("div", { key: '4a88df3f9285757b489b030ba985d720a8a3badf', class: "check-icon" }), index.h("div", { key: 'a0bdd89c2c6274330ba66a6b6bba86ab6c725979', class: "success-text" }, "\u00A1Proceso completado!"))), this.isLoading && (index.h("div", { key: '564fc1333b0fb69102e26ac1da4e77786a9bc7a3', class: "loading-overlay" }, index.h("div", { key: '283e4a6315a39b26203e19a13f35389bc1870a0f', class: "loading-spinner" }), index.h("div", { key: '7e4f00a1fcee1bbf29d74c8be35a72aba2c1041a', class: "loading-text" }, this.statusMessage))), index.h("div", { key: '6cbe1fea1c4a7d3cb25cd34ee04becb5fa538dfd', class: "watermark" }, index.h("img", { key: '1bd4157b7f74753570ddf48682acf419a6de03ea', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
935
+ return (index.h("div", { key: '7cd80970b12ba60949ff34b5a9405b9782267918', class: "detector-container" }, index.h("div", { key: '0e66e46a524bd37f60f30504a270f72b64a97fc1', class: "video-container" }, index.h("video", { key: '0c1b0719c626659e54bec8ce3383f50d990c32c1', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: this.isVideoActive ? 'block' : 'none' } }), index.h("canvas", { key: '09fc3a2d511e48186099a0ff5d3d6378f15fe1c4', ref: el => this.canvasRef = el, class: this.shouldMirrorVideo ? 'mirror' : '' }), this.isMaskReady && (index.h("div", { key: '70a31b04c9543fc0834a848013daf06c654e4194', class: "overlay-mask" }, index.h("div", { key: '050dcdaa59b028b5c379de310a80ca7191bf8af8', class: "card-outline" }, index.h("div", { key: 'adef93f2e6b16b308dc5d67469f35c2025ca160f', class: "side side-top" }), index.h("div", { key: '4900bed8cc2607f92163a25bbf3c0d3a272b9840', class: "side side-right" }), index.h("div", { key: '11fc9da6cfe04b993bf4a72413da2344122b21e4', class: "side side-bottom" }), index.h("div", { key: 'ed95ac2578ba045e003d61144aecd09ef4f01561', class: "side side-left" }), index.h("div", { key: '37eec8aad0a0d449174d52caab7aec5a25d11731', class: "corner corner-tl" }), index.h("div", { key: 'c7247e71d8936d0a30f415f73377713a8c457420', class: "corner corner-tr" }), index.h("div", { key: 'e131e0c3bd66068ae1d33cd7211edafd92dfbe62', class: "corner corner-bl" }), index.h("div", { key: '89234ddcde735501907276e10287af983033ec85', class: "corner corner-br" }), !this.showFlipAnimation && !this.showSuccessAnimation && (index.h("div", { key: '4b2543dd05f173b3ee4e3053ed7b62d4e7b75e63', class: "guide-text" }, this.statusMessage))), this.captureStep === 'back' && !this.showFlipAnimation && !this.showSuccessAnimation && (index.h("button", { key: 'e7e2b2b1486f7024fc2d42073313833bca88e315', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")))), this.isCapturing && (index.h("div", { key: '6ca8283fe3eb1c5b2700c0d313afb51c2db3ea7f', class: "capture-animation" })), this.showFlipAnimation && (index.h("div", { key: 'eaed1200385a98a8750cdaef2dad9f8ff63914f3', class: "flip-animation" }, index.h("div", { key: 'dbe9bfc929fa8b13cadc60cdece603c48dbcbd4c', class: "id-card-icon" }), index.h("div", { key: '82b9de0910a993b6cf3af7ecdf43de41d495c181', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), this.showSuccessAnimation && (index.h("div", { key: '92ef47686b0fbf91f6a3f8c3c3f23a78a6fc76f8', class: "success-animation" }, index.h("div", { key: '35aeb41dce144fc844d7447a958473834f6b0843', class: "check-icon" }), index.h("div", { key: '2ba06161a8d665394803ce8cc90320b154048c0b', class: "success-text" }, "\u00A1Proceso completado!"))), this.isLoading && (index.h("div", { key: '8abc4f5724ca6543698f7ca9116ccfc9c042edbd', class: "loading-overlay" }, index.h("div", { key: '5c8f73bb7c30c6a4c304e96e4db2bf75dc89b89e', class: "loading-spinner" }), index.h("div", { key: '8f5665f21be98ddccb9d9d2c6412e4edf4571a51', class: "loading-text" }, this.statusMessage))), index.h("div", { key: '00316bee3c56f07b9771c400f5b84b7e14dca4af', class: "watermark" }, index.h("img", { key: 'd7f58ffa993405912f8aa26386f39caa72e7a000', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
918
936
  }
919
937
  };
920
938
  JaakStamps.style = myComponentCss;