Complete rewrite of Scry using TypeScript stack:
- Expo/React Native with Expo Router (file-based routing)
- Convex backend (serverless functions + real-time database)
- Adaptive camera system (expo-camera in Expo Go, Vision Camera in production)
- React Native Skia + fast-opencv for image processing
- GDPR-compliant auth setup with Zitadel OIDC (pending configuration)
Key features:
- Card recognition pipeline ported to TypeScript
- Perceptual hashing (192-bit color pHash)
- CLAHE preprocessing for lighting normalization
- Local SQLite cache with Convex sync
- Collection management with offline support
Removes all .NET/MAUI code (src/, test/, tools/).
💘 Generated with Crush
Assisted-by: Claude Opus 4.5 via Crush <crush@charm.land>
95 lines
2.2 KiB
TypeScript
95 lines
2.2 KiB
TypeScript
/**
|
|
* Image loader utilities for React Native.
|
|
* Loads images from file paths and returns pixel data for recognition.
|
|
*/
|
|
|
|
import * as ImageManipulator from "expo-image-manipulator";
|
|
import { Platform } from "react-native";
|
|
|
|
export interface LoadedImage {
|
|
pixels: Uint8Array;
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
/**
|
|
* Load image from a file path and return RGBA pixel data.
|
|
* Uses expo-image-manipulator to resize the image to a manageable size first.
|
|
*
|
|
* Note: expo-image-manipulator doesn't directly provide pixel data.
|
|
* We need to use a canvas (web) or Skia (native) to decode pixels.
|
|
* For now, this provides the resized image URI for the Skia-based decoder.
|
|
*/
|
|
export async function loadImageForRecognition(
|
|
uri: string,
|
|
targetWidth: number = 480, // Reasonable size for processing
|
|
targetHeight: number = 640
|
|
): Promise<{ uri: string; width: number; height: number }> {
|
|
// Normalize the URI for the platform
|
|
const normalizedUri =
|
|
Platform.OS === "android" && !uri.startsWith("file://")
|
|
? `file://${uri}`
|
|
: uri;
|
|
|
|
// Resize the image for faster processing
|
|
const result = await ImageManipulator.manipulateAsync(
|
|
normalizedUri,
|
|
[
|
|
{
|
|
resize: {
|
|
width: targetWidth,
|
|
height: targetHeight,
|
|
},
|
|
},
|
|
],
|
|
{
|
|
compress: 1,
|
|
format: ImageManipulator.SaveFormat.PNG,
|
|
}
|
|
);
|
|
|
|
return {
|
|
uri: result.uri,
|
|
width: result.width,
|
|
height: result.height,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Load image and get base64 data.
|
|
* Useful for passing to native modules or Skia.
|
|
*/
|
|
export async function loadImageAsBase64(
|
|
uri: string,
|
|
targetWidth: number = 480,
|
|
targetHeight: number = 640
|
|
): Promise<{ base64: string; width: number; height: number }> {
|
|
// Normalize the URI
|
|
const normalizedUri =
|
|
Platform.OS === "android" && !uri.startsWith("file://")
|
|
? `file://${uri}`
|
|
: uri;
|
|
|
|
const result = await ImageManipulator.manipulateAsync(
|
|
normalizedUri,
|
|
[
|
|
{
|
|
resize: {
|
|
width: targetWidth,
|
|
height: targetHeight,
|
|
},
|
|
},
|
|
],
|
|
{
|
|
compress: 1,
|
|
format: ImageManipulator.SaveFormat.PNG,
|
|
base64: true,
|
|
}
|
|
);
|
|
|
|
return {
|
|
base64: result.base64 || "",
|
|
width: result.width,
|
|
height: result.height,
|
|
};
|
|
}
|