@sanctum-key/react-native-sdk 1.0.14 → 1.0.16

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.
@@ -1,7 +1,5 @@
1
- import { Image as RNImage, Platform } from 'react-native';
1
+ import { Platform } from 'react-native';
2
2
  import * as ImageManipulator from "expo-image-manipulator";
3
- import { truncateFields } from '../modules/api/KYCService';
4
- import { logger } from './logger';
5
3
  /** OBB confidence below this = card not fully in frame; don't crop, give user feedback. */
6
4
  export const OBB_CONFIDENCE_THRESHOLD = 0.85;
7
5
  /**
@@ -15,7 +13,7 @@ export function getObbConfidence(cardObb) {
15
13
  return null;
16
14
  const first = cardObb[0];
17
15
  // New format: first is an object with a confidence field
18
- if (first && typeof first === 'object' && 'confidence' in first) {
16
+ if (first && typeof first === 'object' && !Array.isArray(first) && 'confidence' in first) {
19
17
  const score = first.confidence;
20
18
  return typeof score === 'number' ? score : null;
21
19
  }
@@ -25,46 +23,47 @@ export function getObbConfidence(cardObb) {
25
23
  const score = first[1];
26
24
  return typeof score === 'number' ? score : null;
27
25
  }
28
- /**
29
- * Extracts the card_in_frame flag from card_obb when present.
30
- * Returns:
31
- * - true => card fully in frame
32
- * - false => card not fully in frame
33
- * - null => field not present (backward compatibility)
34
- */
35
26
  /**
36
27
  * Extracts the framing data from card_obb.
37
28
  * STRICT CHECK: Returns true ONLY if card_in_frame is true AND cropped_sides is empty.
38
- * Returns:
39
- * - true => card fully in frame (no sides cropped)
40
- * - false => card not fully in frame OR edges are cropped
41
- * - null => field not present (backward compatibility)
42
29
  */
43
30
  export function getCardInFrame(cardObb) {
44
31
  if (!cardObb)
45
32
  return null;
46
- // card_obb can be an array of objects or a single object
47
33
  const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;
48
34
  if (first && typeof first === 'object' && 'card_in_frame' in first) {
49
35
  const isCardInFrame = first.card_in_frame === true;
50
36
  const hasCroppedSides = Array.isArray(first.cropped_sides) && first.cropped_sides.length > 0;
51
- // If the API explicitly says it's not in frame, OR if it lists cropped edges, reject it.
52
37
  if (!isCardInFrame || hasCroppedSides) {
53
38
  if (hasCroppedSides) {
54
39
  console.warn(`Card is cropped on sides: ${first.cropped_sides.join(', ')}`);
55
40
  }
56
41
  return false;
57
42
  }
58
- // If we made it here, card_in_frame is true AND cropped_sides is empty.
59
43
  return true;
60
44
  }
61
- // Backward compatibility check
62
45
  if (cardObb && typeof cardObb === 'object' && 'card_in_frame' in cardObb) {
63
46
  const value = cardObb.card_in_frame;
64
47
  return typeof value === 'boolean' ? value : null;
65
48
  }
66
49
  return null;
67
50
  }
51
+ function extractObbPoints(cardObb) {
52
+ if (!cardObb)
53
+ return null;
54
+ const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;
55
+ // New format: { obb: [...], confidence, ... }
56
+ if (first && typeof first === 'object' && !Array.isArray(first) && 'obb' in first) {
57
+ const pts = first.obb;
58
+ if (Array.isArray(pts) && pts.length === 4)
59
+ return pts;
60
+ }
61
+ // Legacy format: [ [p1,p2,p3,p4], confidence ]
62
+ if (Array.isArray(first) && Array.isArray(first[0]) && first[0].length === 4) {
63
+ return first[0];
64
+ }
65
+ return null;
66
+ }
68
67
  // Compute axis-aligned bounding box from oriented quadrilateral
69
68
  function computeAabb(points) {
70
69
  const xs = points.map(p => p[0]);
@@ -88,27 +87,25 @@ async function cropWeb(uri, points) {
88
87
  const ctx = canvas.getContext('2d');
89
88
  if (!ctx)
90
89
  return reject(new Error('Canvas context not available'));
91
- ctx.drawImage(img, minX, // sx
92
- minY, // sy
93
- width, // sw
94
- height, // sh
95
- 0, 0, canvas.width, canvas.height);
90
+ ctx.drawImage(img, minX, minY, width, height, // Source
91
+ 0, 0, canvas.width, canvas.height // Destination
92
+ );
96
93
  resolve(canvas.toDataURL('image/jpeg', 0.92));
97
94
  };
98
95
  img.onerror = (e) => reject(e);
99
96
  img.src = uri;
100
97
  });
101
98
  }
102
- // Fallback: return original for native (no dependency added); caller can still use bbox to draw overlay
103
99
  export async function cropByObb(uri, cardObb) {
104
100
  try {
105
- // card_obb format: [ [ [p1,p2,p3,p4], score ] ] — if confidence too low, don't crop (send full image)
106
101
  const confidence = getObbConfidence(cardObb);
107
102
  if (confidence !== null && confidence < OBB_CONFIDENCE_THRESHOLD) {
108
103
  return {};
109
104
  }
110
- const first = Array.isArray(cardObb) ? cardObb[0] : null;
111
- const points = Array.isArray(first?.[0]) ? first[0] : null;
105
+ // FIX: Use extractObbPoints() which handles both legacy and new API formats.
106
+ // The old code used `Array.isArray(first?.[0]) ? first[0] : null`, which
107
+ // returned null for new-format responses like [{obb:[...], confidence}].
108
+ const points = extractObbPoints(cardObb);
112
109
  if (!points || points.length !== 4)
113
110
  return {};
114
111
  const { minX, minY, width, height } = computeAabb(points);
@@ -116,100 +113,150 @@ export async function cropByObb(uri, cardObb) {
116
113
  const base64 = await cropWeb(uri, points);
117
114
  return { base64, bbox: { minX, minY, width, height } };
118
115
  }
119
- // Native: return bbox only; keep base64 undefined
120
116
  return { bbox: { minX, minY, width, height } };
121
117
  }
122
118
  catch (e) {
123
119
  return {};
124
120
  }
125
121
  }
126
- // exemple d'appel :
127
- export async function cropImageWithBBox(uri, bbox) {
128
- // 1️⃣ Récupère la taille originale de l'image
129
- await RNImage.getSize(uri, (width, height) => {
130
- console.log("Image originale:", width, height);
131
- });
132
- // // 2️⃣ Suppose que ton bbox vient d'une image affichée dans `displayedSize`
133
- // const scaleX = originalWidth / displayedSize.width;
134
- // const scaleY = originalHeight / displayedSize.height;
135
- // 3️⃣ Convertir le bbox à la taille réelle scale 0.10 = 10%
136
- const crop = {
137
- originX: bbox.minX,
138
- originY: bbox.minY,
139
- width: bbox.width,
140
- height: bbox.height,
141
- };
142
- // 4️⃣ Appliquer le crop
143
- // @ts-ignore - manipulateAsync is deprecated but still functional, new API (useImageManipulator) is a React hook and not suitable for utility functions
144
- const result = await ImageManipulator.manipulateAsync(uri, [{ crop }], { compress: 1, format: ImageManipulator.SaveFormat.PNG });
145
- console.log("Image recadrée:", result.uri);
146
- return result.uri;
122
+ async function normalizeOrientationAndGetDimensions(uri) {
123
+ // Web: canvas already handles EXIF; skip normalization
124
+ if (Platform.OS === 'web') {
125
+ return new Promise((resolve, reject) => {
126
+ const img = new Image();
127
+ img.onload = () => resolve({ normalizedUri: uri, width: img.naturalWidth, height: img.naturalHeight });
128
+ img.onerror = reject;
129
+ img.src = uri;
130
+ });
131
+ }
132
+ // Native: re-encode to bake EXIF rotation into actual pixels
133
+ const result = await ImageManipulator.manipulateAsync(uri, [], // No transforms — just re-encode to normalize
134
+ { compress: 1, format: ImageManipulator.SaveFormat.JPEG });
135
+ return { normalizedUri: result.uri, width: result.width, height: result.height };
147
136
  }
148
- // Fonction pour rogner avec une tolérance de 10% autour du bbox
149
- export async function cropImageWithBBoxWithTolerance(uri, bbox, tolerance = 0.1) {
150
- console.log("cropImageWithBBoxWithTolerance", JSON.stringify(truncateFields({ uri, bbox, tolerance }), null, 2));
151
- return new Promise((resolve, reject) => {
137
+ export async function cropImageWithBBoxWithTolerance(uri, bbox, tolerance = 0.02) {
138
+ return new Promise((resolve) => {
152
139
  let isResolved = false;
153
- // Timeout de sécurité (15 secondes)
140
+ // Safety Timeout (15 seconds — slightly longer to account for normalization step)
154
141
  const timeout = setTimeout(() => {
155
142
  if (!isResolved) {
156
143
  isResolved = true;
157
- console.warn("Timeout lors du rognage avec tolérance, utilisation de l'image originale");
158
- resolve(uri); // Retourner l'URI original en cas de timeout
144
+ console.warn("Crop timeout, returning original.");
145
+ resolve(uri);
159
146
  }
160
147
  }, 15000);
161
- // 1️⃣ Récupère la taille originale de l'image
162
- RNImage.getSize(uri, (imageWidth, imageHeight) => {
163
- if (isResolved)
164
- return;
165
- // 2️⃣ Calculer la tolérance en pixels (10% de chaque côté)
166
- const toleranceX = bbox.width * tolerance;
167
- const toleranceY = bbox.height * tolerance;
168
- // 3️⃣ Ajuster le bbox avec la tolérance
169
- // Calculer les nouvelles coordonnées avec tolérance
170
- const newMinX = Math.max(0, bbox.minX - toleranceX);
171
- const newMinY = Math.max(0, bbox.minY - toleranceY);
172
- const newMaxX = Math.min(imageWidth, bbox.minX + bbox.width + toleranceX);
173
- const newMaxY = Math.min(imageHeight, bbox.minY + bbox.height + toleranceY);
174
- // Calculer la largeur et hauteur finales
175
- const finalWidth = newMaxX - newMinX;
176
- const finalHeight = newMaxY - newMinY;
177
- const crop = {
178
- originX: newMinX,
179
- originY: newMinY,
180
- width: finalWidth,
181
- height: finalHeight,
182
- };
183
- logger.log("cropImageWithBBoxWithTolerance - crop params", JSON.stringify(truncateFields({ uri, crop, tolerance, imageWidth, imageHeight }), null, 2));
184
- // 4️⃣ Appliquer le crop avec tolérance
185
- // @ts-ignore - manipulateAsync is deprecated but still functional, new API (useImageManipulator) is a React hook and not suitable for utility functions
186
- ImageManipulator.manipulateAsync(uri, [{ crop }], { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG })
187
- .then((result) => {
148
+ (async () => {
149
+ try {
150
+ // STEP 1: Normalize EXIF orientation.
151
+ // This is the core fix — on Android the raw photo pixels may be stored
152
+ // rotated. After this call, normalizedUri has pixels matching the on-screen
153
+ // orientation and actualWidth/actualHeight are the display-space dimensions.
154
+ const { normalizedUri, width: actualWidth, height: actualHeight } = await normalizeOrientationAndGetDimensions(uri);
188
155
  if (isResolved)
189
156
  return;
157
+ // STEP 2: Resolve bbox to pixel coordinates.
158
+ // Detect if bbox is percentage (e.g., 0.90) or absolute pixels (e.g., 800).
159
+ const isPercentageBased = bbox.width <= 1.5 && bbox.height <= 1.5;
160
+ let pixelMinX, pixelMinY, pixelWidth, pixelHeight;
161
+ if (isPercentageBased) {
162
+ pixelMinX = bbox.minX * actualWidth;
163
+ pixelMinY = bbox.minY * actualHeight;
164
+ pixelWidth = bbox.width * actualWidth;
165
+ pixelHeight = bbox.height * actualHeight;
166
+ }
167
+ else {
168
+ pixelMinX = bbox.minX;
169
+ pixelMinY = bbox.minY;
170
+ pixelWidth = bbox.width;
171
+ pixelHeight = bbox.height;
172
+ }
173
+ // STEP 3: Add tolerance and clamp to image bounds.
174
+ const toleranceX = pixelWidth * tolerance;
175
+ const toleranceY = pixelHeight * tolerance;
176
+ const newMinX = Math.max(0, pixelMinX - toleranceX);
177
+ const newMinY = Math.max(0, pixelMinY - toleranceY);
178
+ const newMaxX = Math.min(actualWidth, pixelMinX + pixelWidth + toleranceX);
179
+ const newMaxY = Math.min(actualHeight, pixelMinY + pixelHeight + toleranceY);
180
+ const finalCrop = {
181
+ originX: Math.floor(newMinX),
182
+ originY: Math.floor(newMinY),
183
+ width: Math.max(1, Math.floor(newMaxX - newMinX)),
184
+ height: Math.max(1, Math.floor(newMaxY - newMinY)),
185
+ };
186
+ // STEP 4: Perform final crop on the already-normalized image.
187
+ try {
188
+ const result = await ImageManipulator.manipulateAsync(normalizedUri, [{ crop: finalCrop }], { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG });
189
+ if (!isResolved) {
190
+ isResolved = true;
191
+ clearTimeout(timeout);
192
+ resolve(result.uri);
193
+ }
194
+ }
195
+ catch (err) {
196
+ if (!isResolved) {
197
+ isResolved = true;
198
+ clearTimeout(timeout);
199
+ console.error("ImageManipulator crop failed, returning normalized image", err);
200
+ resolve(normalizedUri);
201
+ }
202
+ }
203
+ }
204
+ catch (e) {
205
+ if (!isResolved) {
206
+ isResolved = true;
207
+ clearTimeout(timeout);
208
+ console.error("normalizeOrientationAndGetDimensions failed, returning original", e);
209
+ resolve(uri);
210
+ }
211
+ }
212
+ })();
213
+ });
214
+ }
215
+ export async function cropToCenterScanArea(uri, scanWidthRatio = 0.91, // Match Kotlin's 0.91f
216
+ scanAreaAspectRatio = 1.59 // Standard ID Card ratio
217
+ ) {
218
+ return new Promise((resolve) => {
219
+ let isResolved = false;
220
+ const timeout = setTimeout(() => {
221
+ if (!isResolved) {
190
222
  isResolved = true;
191
- clearTimeout(timeout);
192
- console.log("Image recadrée avec tolérance:", result.uri);
193
- resolve(result.uri);
194
- })
195
- .catch((error) => {
223
+ resolve(uri);
224
+ }
225
+ }, 10000);
226
+ (async () => {
227
+ try {
228
+ // 1. Use Claude's brilliant EXIF normalization first!
229
+ const { normalizedUri, width: actualWidth, height: actualHeight } = await normalizeOrientationAndGetDimensions(uri);
196
230
  if (isResolved)
197
231
  return;
198
- isResolved = true;
199
- clearTimeout(timeout);
200
- console.error("Erreur lors du rognage avec tolérance:", error);
201
- // En cas d'erreur, retourner l'URI original au lieu de rejeter
202
- resolve(uri);
203
- });
204
- }, (error) => {
205
- if (isResolved)
206
- return;
207
- isResolved = true;
208
- clearTimeout(timeout);
209
- console.error("Erreur lors de la récupération de la taille de l'image:", error);
210
- // En cas d'erreur, retourner l'URI original au lieu de rejeter
211
- resolve(uri);
212
- });
232
+ // 2. Apply the Kotlin mathematical centering
233
+ const scanWidth = actualWidth * scanWidthRatio;
234
+ const scanHeight = scanWidth / scanAreaAspectRatio;
235
+ const left = (actualWidth - scanWidth) / 2;
236
+ const top = (actualHeight - scanHeight) / 2;
237
+ const finalCrop = {
238
+ originX: Math.max(0, Math.floor(left)),
239
+ originY: Math.max(0, Math.floor(top)),
240
+ width: Math.max(1, Math.floor(scanWidth)),
241
+ height: Math.max(1, Math.floor(scanHeight)),
242
+ };
243
+ // 3. Perform the final centered crop
244
+ const result = await ImageManipulator.manipulateAsync(normalizedUri, [{ crop: finalCrop }], { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG });
245
+ if (!isResolved) {
246
+ isResolved = true;
247
+ clearTimeout(timeout);
248
+ resolve(result.uri);
249
+ }
250
+ }
251
+ catch (err) {
252
+ if (!isResolved) {
253
+ isResolved = true;
254
+ clearTimeout(timeout);
255
+ console.error("Center crop failed, returning original", err);
256
+ resolve(uri); // Fallback to raw image
257
+ }
258
+ }
259
+ })();
213
260
  });
214
261
  }
215
262
  //# sourceMappingURL=cropByObb.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cropByObb.js","sourceRoot":"","sources":["../../../src/utils/cropByObb.ts"],"names":[],"mappings":"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","sourcesContent":["import { Image as RNImage, Platform } from 'react-native';\nimport * as ImageManipulator from \"expo-image-manipulator\";\nimport { truncateFields } from '../modules/api/KYCService';\nimport { logger } from './logger';\n\ntype Point = [number, number];\n\n/** OBB confidence below this = card not fully in frame; don't crop, give user feedback. */\nexport const OBB_CONFIDENCE_THRESHOLD = 0.85;\n\n/**\n * card_obb format from API:\n * - Legacy: [ [ [p1,p2,p3,p4], confidence ] ]\n * - New: [ { obb: [p1,p2,p3,p4], confidence, card_in_frame?, cropped_sides? } ]\n * Returns confidence in [0,1] or null if not present.\n */\nexport function getObbConfidence(cardObb: any): number | null {\n if (!cardObb || !Array.isArray(cardObb) || cardObb.length === 0) return null;\n const first = cardObb[0];\n\n // New format: first is an object with a confidence field\n if (first && typeof first === 'object' && 'confidence' in first) {\n const score = (first as any).confidence;\n return typeof score === 'number' ? score : null;\n }\n\n // Legacy format: [ [points], score ]\n if (!Array.isArray(first) || first.length < 2) return null;\n const score = first[1];\n return typeof score === 'number' ? score : null;\n}\n\n/**\n * Extracts the card_in_frame flag from card_obb when present.\n * Returns:\n * - true => card fully in frame\n * - false => card not fully in frame\n * - null => field not present (backward compatibility)\n */\n/**\n * Extracts the framing data from card_obb.\n * STRICT CHECK: Returns true ONLY if card_in_frame is true AND cropped_sides is empty.\n * Returns:\n * - true => card fully in frame (no sides cropped)\n * - false => card not fully in frame OR edges are cropped\n * - null => field not present (backward compatibility)\n */\nexport function getCardInFrame(cardObb: any): boolean | null {\n if (!cardObb) return null;\n\n // card_obb can be an array of objects or a single object\n const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;\n\n if (first && typeof first === 'object' && 'card_in_frame' in first) {\n const isCardInFrame = first.card_in_frame === true;\n const hasCroppedSides = Array.isArray(first.cropped_sides) && first.cropped_sides.length > 0;\n\n // If the API explicitly says it's not in frame, OR if it lists cropped edges, reject it.\n if (!isCardInFrame || hasCroppedSides) {\n if (hasCroppedSides) {\n console.warn(`Card is cropped on sides: ${first.cropped_sides.join(', ')}`);\n }\n return false; \n }\n\n // If we made it here, card_in_frame is true AND cropped_sides is empty.\n return true; \n }\n\n // Backward compatibility check\n if (cardObb && typeof cardObb === 'object' && 'card_in_frame' in cardObb) {\n const value = (cardObb as any).card_in_frame;\n return typeof value === 'boolean' ? value : null;\n }\n\n return null;\n}\n\n// Compute axis-aligned bounding box from oriented quadrilateral\nfunction computeAabb(points: Point[]) {\n const xs = points.map(p => p[0]);\n const ys = points.map(p => p[1]);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n const maxX = Math.max(...xs);\n const maxY = Math.max(...ys);\n return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };\n}\n\n// Web-only crop using Canvas; returns dataURL (base64)\nasync function cropWeb(uri: string, points: Point[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n const { minX, minY, width, height } = computeAabb(points);\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n const ctx = canvas.getContext('2d');\n if (!ctx) return reject(new Error('Canvas context not available'));\n ctx.drawImage(\n img,\n minX, // sx\n minY, // sy\n width, // sw\n height, // sh\n 0,\n 0,\n canvas.width,\n canvas.height\n );\n resolve(canvas.toDataURL('image/jpeg', 0.92));\n };\n img.onerror = (e) => reject(e);\n img.src = uri;\n });\n}\n\n// Fallback: return original for native (no dependency added); caller can still use bbox to draw overlay\nexport async function cropByObb(uri: string, cardObb: any): Promise<{ base64?: string; bbox?: { minX: number; minY: number; width: number; height: number } }> {\n try {\n // card_obb format: [ [ [p1,p2,p3,p4], score ] ] — if confidence too low, don't crop (send full image)\n const confidence = getObbConfidence(cardObb);\n if (confidence !== null && confidence < OBB_CONFIDENCE_THRESHOLD) {\n return {};\n }\n const first = Array.isArray(cardObb) ? cardObb[0] : null;\n const points = Array.isArray(first?.[0]) ? (first[0] as Point[]) : null;\n if (!points || points.length !== 4) return {};\n\n const { minX, minY, width, height } = computeAabb(points);\n\n if (Platform.OS === 'web') {\n const base64 = await cropWeb(uri, points);\n return { base64, bbox: { minX, minY, width, height } };\n }\n // Native: return bbox only; keep base64 undefined\n return { bbox: { minX, minY, width, height } };\n } catch (e) {\n return {};\n }\n}\n\n\n\n\n// exemple d'appel :\nexport async function cropImageWithBBox(uri: string, bbox: any) {\n // 1️⃣ Récupère la taille originale de l'image\n await RNImage.getSize(uri, (width, height) => {\n console.log(\"Image originale:\", width, height);\n });\n\n // // 2️⃣ Suppose que ton bbox vient d'une image affichée dans `displayedSize`\n // const scaleX = originalWidth / displayedSize.width;\n // const scaleY = originalHeight / displayedSize.height;\n\n // 3️⃣ Convertir le bbox à la taille réelle scale 0.10 = 10%\n const crop = {\n originX: bbox.minX,\n originY: bbox.minY,\n width: bbox.width,\n height: bbox.height,\n };\n\n // 4️⃣ Appliquer le crop\n // @ts-ignore - manipulateAsync is deprecated but still functional, new API (useImageManipulator) is a React hook and not suitable for utility functions\n const result = await ImageManipulator.manipulateAsync(\n uri,\n [{ crop }],\n { compress: 1, format: ImageManipulator.SaveFormat.PNG }\n );\n\n console.log(\"Image recadrée:\", result.uri);\n return result.uri;\n}\n\n// Fonction pour rogner avec une tolérance de 10% autour du bbox\nexport async function cropImageWithBBoxWithTolerance(uri: string, bbox: any, tolerance: number = 0.1): Promise<string> {\n console.log(\"cropImageWithBBoxWithTolerance\", JSON.stringify(truncateFields({ uri, bbox, tolerance }), null, 2));\n \n return new Promise<string>((resolve, reject) => {\n let isResolved = false;\n \n // Timeout de sécurité (15 secondes)\n const timeout = setTimeout(() => {\n if (!isResolved) {\n isResolved = true;\n console.warn(\"Timeout lors du rognage avec tolérance, utilisation de l'image originale\");\n resolve(uri); // Retourner l'URI original en cas de timeout\n }\n }, 15000);\n\n // 1️⃣ Récupère la taille originale de l'image\n RNImage.getSize(uri, (imageWidth, imageHeight) => {\n if (isResolved) return;\n \n // 2️⃣ Calculer la tolérance en pixels (10% de chaque côté)\n const toleranceX = bbox.width * tolerance;\n const toleranceY = bbox.height * tolerance;\n\n // 3️⃣ Ajuster le bbox avec la tolérance\n // Calculer les nouvelles coordonnées avec tolérance\n const newMinX = Math.max(0, bbox.minX - toleranceX);\n const newMinY = Math.max(0, bbox.minY - toleranceY);\n const newMaxX = Math.min(imageWidth, bbox.minX + bbox.width + toleranceX);\n const newMaxY = Math.min(imageHeight, bbox.minY + bbox.height + toleranceY);\n\n // Calculer la largeur et hauteur finales\n const finalWidth = newMaxX - newMinX;\n const finalHeight = newMaxY - newMinY;\n\n const crop = {\n originX: newMinX,\n originY: newMinY,\n width: finalWidth,\n height: finalHeight,\n };\n\n logger.log(\"cropImageWithBBoxWithTolerance - crop params\", JSON.stringify(truncateFields({ uri, crop, tolerance, imageWidth, imageHeight }), null, 2));\n\n // 4️⃣ Appliquer le crop avec tolérance\n // @ts-ignore - manipulateAsync is deprecated but still functional, new API (useImageManipulator) is a React hook and not suitable for utility functions\n ImageManipulator.manipulateAsync(\n uri,\n [{ crop }],\n { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG }\n )\n .then((result) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n console.log(\"Image recadrée avec tolérance:\", result.uri);\n resolve(result.uri);\n })\n .catch((error) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"Erreur lors du rognage avec tolérance:\", error);\n // En cas d'erreur, retourner l'URI original au lieu de rejeter\n resolve(uri);\n });\n }, (error) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"Erreur lors de la récupération de la taille de l'image:\", error);\n // En cas d'erreur, retourner l'URI original au lieu de rejeter\n resolve(uri);\n });\n });\n}\n"]}
1
+ {"version":3,"file":"cropByObb.js","sourceRoot":"","sources":["../../../src/utils/cropByObb.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from 'react-native';\nimport * as ImageManipulator from \"expo-image-manipulator\";\n\n\ntype Point = [number, number];\n\n/** OBB confidence below this = card not fully in frame; don't crop, give user feedback. */\nexport const OBB_CONFIDENCE_THRESHOLD = 0.85;\n\n/**\n * card_obb format from API:\n * - Legacy: [ [ [p1,p2,p3,p4], confidence ] ]\n * - New: [ { obb: [p1,p2,p3,p4], confidence, card_in_frame?, cropped_sides? } ]\n * Returns confidence in [0,1] or null if not present.\n */\nexport function getObbConfidence(cardObb: any): number | null {\n if (!cardObb || !Array.isArray(cardObb) || cardObb.length === 0) return null;\n const first = cardObb[0];\n\n // New format: first is an object with a confidence field\n if (first && typeof first === 'object' && !Array.isArray(first) && 'confidence' in first) {\n const score = (first as any).confidence;\n return typeof score === 'number' ? score : null;\n }\n\n // Legacy format: [ [points], score ]\n if (!Array.isArray(first) || first.length < 2) return null;\n const score = first[1];\n return typeof score === 'number' ? score : null;\n}\n\n/**\n * Extracts the framing data from card_obb.\n * STRICT CHECK: Returns true ONLY if card_in_frame is true AND cropped_sides is empty.\n */\nexport function getCardInFrame(cardObb: any): boolean | null {\n if (!cardObb) return null;\n \n const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;\n\n if (first && typeof first === 'object' && 'card_in_frame' in first) {\n const isCardInFrame = first.card_in_frame === true;\n const hasCroppedSides = Array.isArray(first.cropped_sides) && first.cropped_sides.length > 0;\n\n if (!isCardInFrame || hasCroppedSides) {\n if (hasCroppedSides) {\n console.warn(`Card is cropped on sides: ${first.cropped_sides.join(', ')}`);\n }\n return false;\n }\n\n return true;\n }\n\n if (cardObb && typeof cardObb === 'object' && 'card_in_frame' in cardObb) {\n const value = (cardObb as any).card_in_frame;\n return typeof value === 'boolean' ? value : null;\n }\n\n return null;\n}\n\n\nfunction extractObbPoints(cardObb: any): Point[] | null {\n if (!cardObb) return null;\n const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;\n\n // New format: { obb: [...], confidence, ... }\n if (first && typeof first === 'object' && !Array.isArray(first) && 'obb' in first) {\n const pts = (first as any).obb;\n if (Array.isArray(pts) && pts.length === 4) return pts as Point[];\n }\n\n // Legacy format: [ [p1,p2,p3,p4], confidence ]\n if (Array.isArray(first) && Array.isArray(first[0]) && first[0].length === 4) {\n return first[0] as Point[];\n }\n\n return null;\n}\n\n// Compute axis-aligned bounding box from oriented quadrilateral\nfunction computeAabb(points: Point[]) {\n const xs = points.map(p => p[0]);\n const ys = points.map(p => p[1]);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n const maxX = Math.max(...xs);\n const maxY = Math.max(...ys);\n return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };\n}\n\n// Web-only crop using Canvas; returns dataURL (base64)\nasync function cropWeb(uri: string, points: Point[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n const { minX, minY, width, height } = computeAabb(points);\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n const ctx = canvas.getContext('2d');\n if (!ctx) return reject(new Error('Canvas context not available'));\n ctx.drawImage(\n img,\n minX, minY, width, height, // Source\n 0, 0, canvas.width, canvas.height // Destination\n );\n resolve(canvas.toDataURL('image/jpeg', 0.92));\n };\n img.onerror = (e) => reject(e);\n img.src = uri;\n });\n}\n\nexport async function cropByObb(uri: string, cardObb: any): Promise<{ base64?: string; bbox?: { minX: number; minY: number; width: number; height: number } }> {\n try {\n const confidence = getObbConfidence(cardObb);\n if (confidence !== null && confidence < OBB_CONFIDENCE_THRESHOLD) {\n return {};\n }\n\n // FIX: Use extractObbPoints() which handles both legacy and new API formats.\n // The old code used `Array.isArray(first?.[0]) ? first[0] : null`, which\n // returned null for new-format responses like [{obb:[...], confidence}].\n const points = extractObbPoints(cardObb);\n if (!points || points.length !== 4) return {};\n\n const { minX, minY, width, height } = computeAabb(points);\n\n if (Platform.OS === 'web') {\n const base64 = await cropWeb(uri, points);\n return { base64, bbox: { minX, minY, width, height } };\n }\n \n return { bbox: { minX, minY, width, height } };\n } catch (e) {\n return {};\n }\n}\n\n\nasync function normalizeOrientationAndGetDimensions(\n uri: string\n): Promise<{ normalizedUri: string; width: number; height: number }> {\n // Web: canvas already handles EXIF; skip normalization\n if (Platform.OS === 'web') {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve({ normalizedUri: uri, width: img.naturalWidth, height: img.naturalHeight });\n img.onerror = reject;\n img.src = uri;\n });\n }\n\n // Native: re-encode to bake EXIF rotation into actual pixels\n const result = await ImageManipulator.manipulateAsync(\n uri,\n [], // No transforms — just re-encode to normalize\n { compress: 1, format: ImageManipulator.SaveFormat.JPEG }\n );\n return { normalizedUri: result.uri, width: result.width, height: result.height };\n}\n\n\nexport async function cropImageWithBBoxWithTolerance(uri: string, bbox: any, tolerance: number = 0.02): Promise<string> {\n return new Promise<string>((resolve) => {\n let isResolved = false;\n\n // Safety Timeout (15 seconds — slightly longer to account for normalization step)\n const timeout = setTimeout(() => {\n if (!isResolved) {\n isResolved = true;\n console.warn(\"Crop timeout, returning original.\");\n resolve(uri);\n }\n }, 15000);\n\n (async () => {\n try {\n // STEP 1: Normalize EXIF orientation.\n // This is the core fix — on Android the raw photo pixels may be stored\n // rotated. After this call, normalizedUri has pixels matching the on-screen\n // orientation and actualWidth/actualHeight are the display-space dimensions.\n const { normalizedUri, width: actualWidth, height: actualHeight } =\n await normalizeOrientationAndGetDimensions(uri);\n\n if (isResolved) return;\n\n // STEP 2: Resolve bbox to pixel coordinates.\n // Detect if bbox is percentage (e.g., 0.90) or absolute pixels (e.g., 800).\n const isPercentageBased = bbox.width <= 1.5 && bbox.height <= 1.5;\n\n let pixelMinX: number, pixelMinY: number, pixelWidth: number, pixelHeight: number;\n\n if (isPercentageBased) {\n pixelMinX = bbox.minX * actualWidth;\n pixelMinY = bbox.minY * actualHeight;\n pixelWidth = bbox.width * actualWidth;\n pixelHeight = bbox.height * actualHeight;\n } else {\n pixelMinX = bbox.minX;\n pixelMinY = bbox.minY;\n pixelWidth = bbox.width;\n pixelHeight = bbox.height;\n }\n\n // STEP 3: Add tolerance and clamp to image bounds.\n const toleranceX = pixelWidth * tolerance;\n const toleranceY = pixelHeight * tolerance;\n\n const newMinX = Math.max(0, pixelMinX - toleranceX);\n const newMinY = Math.max(0, pixelMinY - toleranceY);\n const newMaxX = Math.min(actualWidth, pixelMinX + pixelWidth + toleranceX);\n const newMaxY = Math.min(actualHeight, pixelMinY + pixelHeight + toleranceY);\n\n const finalCrop = {\n originX: Math.floor(newMinX),\n originY: Math.floor(newMinY),\n width: Math.max(1, Math.floor(newMaxX - newMinX)),\n height: Math.max(1, Math.floor(newMaxY - newMinY)),\n };\n\n // STEP 4: Perform final crop on the already-normalized image.\n try {\n const result = await ImageManipulator.manipulateAsync(\n normalizedUri,\n [{ crop: finalCrop }],\n { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG }\n );\n\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n resolve(result.uri);\n }\n } catch (err) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"ImageManipulator crop failed, returning normalized image\", err);\n\n resolve(normalizedUri);\n }\n }\n } catch (e) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"normalizeOrientationAndGetDimensions failed, returning original\", e);\n resolve(uri);\n }\n }\n })();\n });\n}\n\nexport async function cropToCenterScanArea(\n uri: string, \n scanWidthRatio: number = 0.91, // Match Kotlin's 0.91f\n scanAreaAspectRatio: number = 1.59 // Standard ID Card ratio\n): Promise<string> {\n return new Promise<string>((resolve) => {\n let isResolved = false;\n\n const timeout = setTimeout(() => {\n if (!isResolved) {\n isResolved = true;\n resolve(uri);\n }\n }, 10000);\n\n (async () => {\n try {\n // 1. Use Claude's brilliant EXIF normalization first!\n const { normalizedUri, width: actualWidth, height: actualHeight } = \n await normalizeOrientationAndGetDimensions(uri);\n\n if (isResolved) return;\n\n // 2. Apply the Kotlin mathematical centering\n const scanWidth = actualWidth * scanWidthRatio;\n const scanHeight = scanWidth / scanAreaAspectRatio;\n const left = (actualWidth - scanWidth) / 2;\n const top = (actualHeight - scanHeight) / 2;\n\n const finalCrop = {\n originX: Math.max(0, Math.floor(left)),\n originY: Math.max(0, Math.floor(top)),\n width: Math.max(1, Math.floor(scanWidth)),\n height: Math.max(1, Math.floor(scanHeight)),\n };\n\n // 3. Perform the final centered crop\n const result = await ImageManipulator.manipulateAsync(\n normalizedUri,\n [{ crop: finalCrop }],\n { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG }\n );\n \n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n resolve(result.uri);\n }\n } catch (err) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"Center crop failed, returning original\", err);\n resolve(uri); // Fallback to raw image\n }\n }\n })();\n });\n}"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanctum-key/react-native-sdk",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Sanctum Key React Native SDK",
5
5
  "main": "build/src/index.js",
6
6
  "types": "build/src/index.d.ts",