/** * react-native-pixel-data * * High-performance React Native SDK for extracting raw pixel data * from images and preparing it for machine learning inference. * * @remarks * This library provides native implementations in Swift (iOS) and Kotlin (Android) * for optimal performance when preprocessing images for ML models. * * Key features: * - Multiple image sources (URL, file, base64, asset, photo library) * - Flexible color formats (RGB, RGBA, BGR, BGRA, grayscale) * - ML-ready normalization presets (ImageNet, TensorFlow, custom) * - Smart resize strategies (cover, contain, stretch) * - Data layouts for all major ML frameworks (HWC, CHW, NHWC, NCHW) * - Region of Interest (ROI) extraction * - Efficient batch processing * * @example * ```typescript * import { getPixelData } from 'react-native-pixel-data'; * * // Basic usage * const result = await getPixelData({ * source: { type: 'url', value: 'https://example.com/image.jpg' }, * resize: { width: 224, height: 224 }, * }); * * // Use with PyTorch model * const pytorchResult = await getPixelData({ * source: { type: 'url', value: 'https://example.com/image.jpg' }, * resize: { width: 224, height: 224, strategy: 'cover' }, * normalization: { preset: 'imagenet' }, * dataLayout: 'CHW', * }); * ``` * * @packageDocumentation * @module react-native-pixel-data */ import { NativeModules, Platform } from 'react-native'; import type { GetPixelDataOptions, PixelDataResult, BatchOptions, BatchResult, } from './types'; import { PixelDataException } from './types'; // ============================================================================ // Re-export all types // ============================================================================ export { // Image sources type ImageSource, type UrlImageSource, type FileImageSource, type Base64ImageSource, type AssetImageSource, type PhotoLibraryImageSource, // Color formats type ColorFormat, getChannelCount, // Normalization type Normalization, type NormalizationPreset, // Resize type ResizeOptions, type ResizeStrategy, // Data layout type DataLayout, // ROI type Roi, // Output type OutputFormat, // Options & Results type GetPixelDataOptions, type PixelDataResult, type BatchOptions, type BatchResult, type PixelDataError, // Utilities isPixelDataError, PixelDataException, } from './types'; // ============================================================================ // Native Module Interface // ============================================================================ interface PixelDataNativeModule { getPixelData(options: Record): Promise; batchGetPixelData( optionsArray: Record[], batchOptions: Record ): Promise; } // ============================================================================ // Module Loading // ============================================================================ const LINKING_ERROR = `The package 'react-native-pixel-data' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n'; const NativePixelData: PixelDataNativeModule = NativeModules.PixelData ? NativeModules.PixelData : new Proxy( {}, { get() { throw new Error(LINKING_ERROR); }, } ); // ============================================================================ // Helper Functions // ============================================================================ /** * Apply default options and prepare for native module. * * @internal * @param options - User-provided options * @returns Prepared options with defaults applied */ function prepareOptions(options: GetPixelDataOptions): Record { const prepared: Record = { source: options.source, colorFormat: options.colorFormat ?? 'RGB', normalization: options.normalization ?? { preset: 'scale' }, dataLayout: options.dataLayout ?? 'HWC', outputFormat: options.outputFormat ?? 'array', }; if (options.resize) { prepared.resize = { width: options.resize.width, height: options.resize.height, strategy: options.resize.strategy ?? 'cover', }; } if (options.roi) { prepared.roi = options.roi; } return prepared; } /** * Validate options before sending to native module. * * @internal * @param options - Options to validate * @throws {PixelDataException} If options are invalid */ function validateOptions(options: GetPixelDataOptions): void { if (!options.source) { throw new PixelDataException('INVALID_SOURCE', 'Image source is required'); } if (!options.source.type) { throw new PixelDataException('INVALID_SOURCE', 'Image source type is required'); } if (!options.source.value) { throw new PixelDataException('INVALID_SOURCE', 'Image source value is required'); } // Validate resize dimensions if (options.resize) { if (options.resize.width <= 0 || options.resize.height <= 0) { throw new PixelDataException( 'INVALID_RESIZE', 'Resize dimensions must be positive' ); } } // Validate ROI if (options.roi) { if ( options.roi.width <= 0 || options.roi.height <= 0 || options.roi.x < 0 || options.roi.y < 0 ) { throw new PixelDataException( 'INVALID_ROI', 'ROI dimensions must be positive and coordinates non-negative' ); } } // Validate custom normalization if (options.normalization?.preset === 'custom') { if (!options.normalization.mean || !options.normalization.std) { throw new PixelDataException( 'INVALID_NORMALIZATION', 'Custom normalization requires mean and std arrays' ); } } } /** * Convert native result data to the requested output format. * * @internal * @param result - Native result with number[] data * @param outputFormat - Requested output format * @returns Result with data converted to requested format */ function convertOutputFormat( result: PixelDataResult, outputFormat: 'array' | 'float32Array' | 'uint8Array' ): PixelDataResult { if (outputFormat === 'array') { // Native module returns number[], no conversion needed return result; } const data = result.data as number[]; if (outputFormat === 'float32Array') { return { ...result, data: new Float32Array(data), }; } if (outputFormat === 'uint8Array') { // For uint8Array, clamp values to 0-255 range // (useful when using 'raw' normalization preset) return { ...result, data: new Uint8Array(data.map(v => Math.max(0, Math.min(255, Math.round(v))))), }; } return result; } // ============================================================================ // Public API // ============================================================================ /** * Extract pixel data from an image source. * * This is the main function for extracting normalized pixel data from images. * It supports various image sources, color formats, normalization presets, * and data layouts to match the requirements of different ML frameworks. * * @param options - Configuration for pixel data extraction * @returns Promise resolving to the extracted pixel data and metadata * @throws {PixelDataException} If image loading or processing fails * * @remarks * Processing pipeline: * 1. Load image from source * 2. Apply ROI cropping (if specified) * 3. Resize to target dimensions (if specified) * 4. Convert to target color format * 5. Apply normalization * 6. Rearrange to target data layout * * @example * Basic usage - get RGB pixel data from a URL: * ```typescript * import { getPixelData } from 'react-native-pixel-data'; * * const result = await getPixelData({ * source: { type: 'url', value: 'https://example.com/image.jpg' }, * resize: { width: 224, height: 224 }, * }); * * console.log(`Processed ${result.width}x${result.height} image`); * console.log(`Data length: ${result.data.length}`); * ``` * * @example * PyTorch model preprocessing: * ```typescript * const result = await getPixelData({ * source: { type: 'url', value: 'https://example.com/image.jpg' }, * colorFormat: 'RGB', * resize: { width: 224, height: 224, strategy: 'cover' }, * normalization: { preset: 'imagenet' }, * dataLayout: 'CHW', // PyTorch expects CHW format * }); * * // result.data is now ready for PyTorch inference * // Shape: [3, 224, 224] (channels, height, width) * ``` * * @example * TensorFlow model preprocessing: * ```typescript * const result = await getPixelData({ * source: { type: 'url', value: 'https://example.com/image.jpg' }, * colorFormat: 'RGB', * resize: { width: 224, height: 224, strategy: 'cover' }, * normalization: { preset: 'tensorflow' }, * dataLayout: 'NHWC', // TensorFlow expects NHWC format * }); * * // result.data is now ready for TensorFlow inference * // Shape: [1, 224, 224, 3] (batch, height, width, channels) * ``` * * @example * Extract region of interest: * ```typescript * const result = await getPixelData({ * source: { type: 'file', value: '/path/to/large-image.jpg' }, * roi: { x: 100, y: 100, width: 200, height: 200 }, * resize: { width: 224, height: 224 }, * }); * ``` * * @example * Error handling: * ```typescript * import { getPixelData, PixelDataException } from 'react-native-pixel-data'; * * try { * const result = await getPixelData(options); * } catch (error) { * if (error instanceof PixelDataException) { * console.error(`[${error.code}] ${error.message}`); * } * } * ``` */ export async function getPixelData( options: GetPixelDataOptions ): Promise { validateOptions(options); const prepared = prepareOptions(options); const outputFormat = options.outputFormat ?? 'array'; try { const result = await NativePixelData.getPixelData(prepared); // Convert data to requested output format return convertOutputFormat(result, outputFormat); } catch (error: unknown) { if (error instanceof Error) { throw new PixelDataException( (error as { code?: string }).code ?? 'UNKNOWN', error.message ); } throw error; } } /** * Process multiple images in batch with concurrency control. * * This function efficiently processes multiple images in parallel, * with configurable concurrency to balance speed and memory usage. * * @param optionsArray - Array of options for each image to process * @param batchOptions - Batch processing configuration (optional) * @returns Promise resolving to batch results * @throws {PixelDataException} If validation fails for any image * * @remarks * - Results maintain the same order as the input array * - Failed images return PixelDataError instead of throwing * - Use `isPixelDataError()` to check for individual failures * - Higher concurrency uses more memory but processes faster * * @example * Basic batch processing: * ```typescript * import { batchGetPixelData, isPixelDataError } from 'react-native-pixel-data'; * * const images = [ * 'https://example.com/image1.jpg', * 'https://example.com/image2.jpg', * 'https://example.com/image3.jpg', * ]; * * const result = await batchGetPixelData( * images.map(url => ({ * source: { type: 'url', value: url }, * resize: { width: 224, height: 224 }, * normalization: { preset: 'imagenet' }, * })), * { concurrency: 4 } * ); * * console.log(`Processed ${result.results.length} images in ${result.totalTimeMs}ms`); * ``` * * @example * Handle individual failures: * ```typescript * const result = await batchGetPixelData(optionsArray); * * result.results.forEach((item, index) => { * if (isPixelDataError(item)) { * console.error(`Image ${index} failed: [${item.code}] ${item.message}`); * } else { * console.log(`Image ${index}: ${item.width}x${item.height}`); * // Use item.data for ML inference * } * }); * * // Count successes and failures * const successes = result.results.filter(r => !isPixelDataError(r)); * const failures = result.results.filter(isPixelDataError); * console.log(`Success: ${successes.length}, Failed: ${failures.length}`); * ``` * * @example * Prepare batch for ML model: * ```typescript * const result = await batchGetPixelData( * imageUrls.map(url => ({ * source: { type: 'url', value: url }, * resize: { width: 224, height: 224 }, * normalization: { preset: 'imagenet' }, * dataLayout: 'CHW', * })), * { concurrency: 2 } // Lower concurrency for memory-constrained devices * ); * * // Extract successful results only * const pixelDataBatch = result.results * .filter((r): r is PixelDataResult => !isPixelDataError(r)) * .map(r => r.data); * * // pixelDataBatch is now ready for batch ML inference * ``` */ export async function batchGetPixelData( optionsArray: GetPixelDataOptions[], batchOptions: BatchOptions = {} ): Promise { // Validate all options optionsArray.forEach((options, index) => { try { validateOptions(options); } catch (error: unknown) { if (error instanceof PixelDataException) { throw new PixelDataException( error.code, `Image ${index}: ${error.message}` ); } throw error; } }); const preparedArray = optionsArray.map(prepareOptions); const preparedBatchOptions: Record = { concurrency: batchOptions.concurrency ?? 4, }; try { const result = await NativePixelData.batchGetPixelData(preparedArray, preparedBatchOptions); // Convert each successful result to the requested output format return { ...result, results: result.results.map((item, index) => { if ('error' in item) { // This is a PixelDataError, return as-is return item; } // Convert successful result to requested output format const outputFormat = optionsArray[index]?.outputFormat ?? 'array'; return convertOutputFormat(item, outputFormat); }), }; } catch (error: unknown) { if (error instanceof Error) { throw new PixelDataException( (error as { code?: string }).code ?? 'UNKNOWN', error.message ); } throw error; } }