# imgfprint Usage Guide

> Complete API reference and examples for high-performance image fingerprinting

---

## Table of Contents

- [Quick Start](#quick-start)
- [Core API](#core-api)
  - [ImageFingerprinter](#imagefingerprinter)
    - [Multi-Algorithm Mode (Default)](#multi-algorithm-mode-default)
    - [Single Algorithm Mode](#single-algorithm-mode)
  - [FingerprinterContext](#fingerprintercontext)
  - [ImageFingerprint](#imagefingerprint)
  - [MultiHashFingerprint](#multihashfingerprint)
  - [Similarity](#similarity)
- [Advanced Usage](#advanced-usage)
  - [Batch Processing](#batch-processing)
  - [Semantic Embeddings](#semantic-embeddings)
- [Error Handling](#error-handling)
- [Performance Tips](#performance-tips)
- [Feature Flags](#feature-flags)

---

## Quick Start

Add the dependency to your `Cargo.toml`:

```toml
[dependencies]
imgfprint = "0.4.3"
```

### Basic Example (Multi-Algorithm)

Compare three images using AHash, PHash, and DHash for best accuracy:

```rust
use imgfprint::ImageFingerprinter;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load images
    let img1 = std::fs::read("photo1.jpg")?;
    let img2 = std::fs::read("photo2.jpg")?;
    
    // Generate fingerprints (computes AHash, PHash, and DHash in parallel)
    let fp1 = ImageFingerprinter::fingerprint(&img1)?;
    let fp2 = ImageFingerprinter::fingerprint(&img2)?;
    
    // Compare using default weighted combination (10% AHash, 60% PHash, 30% DHash).
    // For per-deployment tuning use `fp1.compare_with_config(&fp2, &cfg)` —
    // see the Configurability section below.
    let sim = fp1.compare(&fp2);
    
    println!("Similarity: {:.2}", sim.score);
    println!("Exact match: {}", sim.exact_match);
    
    if sim.score > 0.8 {
        println!("Images are perceptually similar");
    }
    
    Ok(())
}
```

### Single Algorithm Example

For better performance when you only need one algorithm:

```rust
use imgfprint::{ImageFingerprinter, HashAlgorithm};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let img1 = std::fs::read("photo1.jpg")?;
    let img2 = std::fs::read("photo2.jpg")?;
    
    // Use specific algorithm for better speed
    let fp1 = ImageFingerprinter::fingerprint_with(&img1, HashAlgorithm::AHash)?;
    let fp2 = ImageFingerprinter::fingerprint_with(&img2, HashAlgorithm::AHash)?;
    
    let sim = ImageFingerprinter::compare(&fp1, &fp2);
    
    println!("Similarity: {:.2}", sim.score);
    
    Ok(())
}
```

---

## Core API

### ImageFingerprinter

Static methods for computing and comparing fingerprints. The primary entry point for most use cases.

#### Multi-Algorithm Mode (Default)

##### `fingerprint()`

```rust
pub fn fingerprint(
    image_bytes: &[u8]
) -> Result<MultiHashFingerprint, ImgFprintError>
```

Computes **AHash, PHash, and DHash** in parallel for maximum accuracy.

**Returns:**
- `MultiHashFingerprint` containing:
  - BLAKE3 hash of original bytes (exact matching)
  - AHash results: global + 16 block hashes
  - PHash results: global + 16 block hashes
  - DHash results: global + 16 block hashes

**Example:**
```rust
use imgfprint::ImageFingerprinter;

let image_bytes = std::fs::read("image.png")?;
let fingerprint = ImageFingerprinter::fingerprint(&image_bytes)?;

// Access individual algorithms
let ahash = fingerprint.ahash();
let phash = fingerprint.phash();
let dhash = fingerprint.dhash();
```

#### Single Algorithm Mode

##### `fingerprint_with()`

```rust
pub fn fingerprint_with(
    image_bytes: &[u8],
    algorithm: HashAlgorithm
) -> Result<ImageFingerprint, ImgFprintError>
```

Computes a single perceptual hash using the specified algorithm.

**Algorithms:**
- `HashAlgorithm::AHash` - Average Hash (fastest, simplest)
- `HashAlgorithm::PHash` - DCT-based (robust to compression)
- `HashAlgorithm::DHash` - Gradient-based (good for structural changes)

**Example:**
```rust
use imgfprint::{ImageFingerprinter, HashAlgorithm};

// Use only AHash (fastest)
let fp = ImageFingerprinter::fingerprint_with(
    &image_bytes,
    HashAlgorithm::AHash
)?;
```

#### `compare()`

```rust
pub fn compare(
    a: &ImageFingerprint,
    b: &ImageFingerprint
) -> Similarity
```

Compares two fingerprints and returns a similarity score.

**Note:** For `MultiHashFingerprint`, use the `compare()` method directly on the struct for weighted multi-algorithm comparison.

**Returns:**
- `Similarity` struct with:
  - `score`: 0.0 (different) to 1.0 (identical)
  - `exact_match`: true if BLAKE3 hashes match
  - `perceptual_distance`: Hamming distance 0-64

**Example:**
```rust
let sim = ImageFingerprinter::compare(&fp1, &fp2);

println!("Score: {:.2}", sim.score);              // 0.0 - 1.0
println!("Exact match: {}", sim.exact_match);      // true/false
println!("Distance: {}", sim.perceptual_distance); // 0-64
```

**Similarity Thresholds:**

| Score Range | Interpretation |
|-------------|----------------|
| 1.0 | Identical images |
| 0.8 - 1.0 | Same image, minor edits |
| 0.5 - 0.8 | Visually similar |
| < 0.5 | Different images |

#### `fingerprint_batch()`

```rust
pub fn fingerprint_batch<S>(
    images: &[(S, Vec<u8>)]
) -> Vec<(S, Result<MultiHashFingerprint, ImgFprintError>)>
where
    S: Send + Sync + Clone + 'static
```

Processes multiple images in parallel (requires `parallel` feature). Uses per-thread context caching to minimize allocations across parallel workers.

**Example:**
```rust
let images = vec![
    ("img1.jpg".to_string(), std::fs::read("img1.jpg")?),
    ("img2.jpg".to_string(), std::fs::read("img2.jpg")?),
    ("img3.jpg".to_string(), std::fs::read("img3.jpg")?),
];

let results = ImageFingerprinter::fingerprint_batch(&images);

for (id, result) in results {
    match result {
        Ok(fp) => println!("{}: fingerprinted successfully", id),
        Err(e) => println!("{}: error - {}", id, e),
    }
}
```

**Performance Note:** With the `parallel` feature enabled (default), batch processing uses all available CPU cores for 2-3x speedup.

#### `fingerprint_batch_chunked()`

```rust
pub fn fingerprint_batch_chunked<S, F>(
    images: &[(S, Vec<u8>)],
    chunk_size: usize,
    callback: F
)
where
    S: Send + Sync + Clone + 'static,
    F: FnMut(S, Result<MultiHashFingerprint, ImgFprintError>)
```

Processes multiple images in chunks to limit memory usage. Invokes the callback for each result as it completes.

**Arguments:**
- `images` - Slice of (id, image_bytes) pairs
- `chunk_size` - Number of images to process per chunk
- `callback` - Function called with each result

**Example:**
```rust
let images = vec![
    ("img1.jpg".to_string(), std::fs::read("img1.jpg")?),
    ("img2.jpg".to_string(), std::fs::read("img2.jpg")?),
    // ... thousands of images
];

let mut processed = 0;
ImageFingerprinter::fingerprint_batch_chunked(&images, 100, |id, result| {
    match result {
        Ok(fp) => println!("{}: fingerprinted", id),
        Err(e) => println!("{}: error - {}", id, e),
    }
    processed += 1;
});

println!("Processed {} images", processed);
```

**Use case:** Prevents unbounded memory consumption when processing large batches (e.g., 10,000+ images).

---

### FingerprinterContext

High-performance context for repeated fingerprinting with buffer reuse. Recommended for processing thousands of images.

#### `new()`

```rust
pub fn new() -> Self
```

Creates a new context with cached resources.

**Example:**
```rust
use imgfprint::FingerprinterContext;

let mut ctx = FingerprinterContext::new();
```

#### `fingerprint()`

```rust
pub fn fingerprint(
    &mut self,
    image_bytes: &[u8]
) -> Result<MultiHashFingerprint, ImgFprintError>
```

Computes **all hashes** (AHash + PHash + DHash) in parallel using internal buffer reuse.

**Example:**
```rust
use imgfprint::FingerprinterContext;

let mut ctx = FingerprinterContext::new();

// Process thousands of images efficiently
for path in image_paths {
    let bytes = std::fs::read(path)?;
    let fp = ctx.fingerprint(&bytes)?;
    // fp is MultiHashFingerprint
}
```

**Performance:** Context API saves approximately 260KB of allocations per image compared to the static API. Essential for high-throughput scenarios (10M+ images/day).

#### `fingerprint_with()`

```rust
pub fn fingerprint_with(
    &mut self,
    image_bytes: &[u8],
    algorithm: HashAlgorithm
) -> Result<ImageFingerprint, ImgFprintError>
```

Computes a **single algorithm** fingerprint using internal buffer reuse.

**Example:**
```rust
use imgfprint::{FingerprinterContext, HashAlgorithm};

let mut ctx = FingerprinterContext::new();

// Process only DHash for speed
for path in image_paths {
    let bytes = std::fs::read(path)?;
    let fp = ctx.fingerprint_with(&bytes, HashAlgorithm::DHash)?;
    // Process fingerprint...
}
```

#### `fingerprint_batch_chunked()`

```rust
pub fn fingerprint_batch_chunked<S, F>(
    &mut self,
    images: &[(S, Vec<u8>)],
    chunk_size: usize,
    callback: F
)
where
    S: Send + Sync + Clone + 'static,
    F: FnMut(S, Result<MultiHashFingerprint, ImgFprintError>)
```

Processes images in chunks with buffer reuse. Combines memory efficiency of streaming with better cache utilization.

**Example:**
```rust
use imgfprint::FingerprinterContext;

let mut ctx = FingerprinterContext::new();

let images = vec![
    ("img1.jpg".to_string(), std::fs::read("img1.jpg")?),
    ("img2.jpg".to_string(), std::fs::read("img2.jpg")?),
    // ... thousands of images
];

ctx.fingerprint_batch_chunked(&images, 50, |id, result| {
    if let Ok(fp) = result {
        // Store or process fingerprint
        db.store(&id, fp)?;
    }
});
```

---

### ImageFingerprint

A perceptual fingerprint containing multiple hash layers for robust comparison.

#### Accessors

##### `exact_hash()`

```rust
pub fn exact_hash(&self) -> &[u8; 32]
```

Returns the BLAKE3 hash of original image bytes.

**Use case:** Exact deduplication before perceptual comparison (much faster).

**Example:**
```rust
let exact: &[u8; 32] = fp.exact_hash();

// Convert to hex string
let hex = hex::encode(exact);
println!("Exact hash: {}", hex);
```

##### `global_hash()`

```rust
pub fn global_hash(&self) -> u64
```

Returns the global perceptual hash from the center 32x32 region. The algorithm used (PHash or DHash) depends on which was specified when creating the fingerprint.

**Example:**
```rust
let hash: u64 = fp.global_hash();
println!("Perceptual hash: {:016x}", hash);
```

#### Display Formatting

Both `ImageFingerprint` and `MultiHashFingerprint` implement `Display` for easy logging and serialization to strings:

```rust
let fp = ImageFingerprinter::fingerprint_with(&img, HashAlgorithm::PHash)?;
// Format: exact_hex:global_hex:block0,block1,...,block15
println!("{}", fp);

let multi = ImageFingerprinter::fingerprint(&img)?;
// Format: exact_hex|ahash_global|phash_global|dhash_global
println!("{}", multi);
```

##### `block_hashes()`

```rust
pub fn block_hashes(&self) -> &[u64; 16]
```

Returns 16 block-level hashes from a 4x4 grid.

**Use case:** Crop-resistant comparison by matching partial regions.

**Example:**
```rust
let blocks: &[u64; 16] = fp.block_hashes();

// Access individual blocks
for (i, hash) in blocks.iter().enumerate() {
    println!("Block {}: {:016x}", i, hash);
}
```

#### Comparison Methods

##### `distance()`

```rust
pub fn distance(&self, other: &ImageFingerprint) -> u32
```

Computes Hamming distance between global hashes.

**Returns:** 0 (identical) to 64 (completely different)

**Example:**
```rust
let dist = fp1.distance(&fp2);
println!("Hamming distance: {} (lower is more similar)", dist);

// Convert to similarity percentage
let similarity = 1.0 - (dist as f32 / 64.0);
```

##### `is_similar()`

```rust
pub fn is_similar(&self, other: &ImageFingerprint, threshold: f32) -> bool
```

Checks if this fingerprint is similar to another within a threshold.

**Parameters:**
- `other`: Fingerprint to compare against
- `threshold`: Similarity threshold 0.0 to 1.0

**Note:** Uses the full weighted similarity (40% global + 60% block hashes) for consistency with `MultiHashFingerprint`. Out-of-range or NaN thresholds are clamped to [0.0, 1.0] (returns `false` if clamped to 0.0). Panics in debug mode to help catch bugs during development.

**Example:**
```rust
// Check if images are 80% similar
if fp1.is_similar(&fp2, 0.8) {
    println!("Images are similar!");
}

// Stricter matching (90%)
if fp1.is_similar(&fp2, 0.9) {
    println!("Images are nearly identical!");
}

// Loose matching (50%)
if fp1.is_similar(&fp2, 0.5) {
    println!("Images share some visual elements");
}
```

**Note:** A threshold of 1.0 requires exact match (both BLAKE3 and perceptual hash identical).

---

### MultiHashFingerprint

A multi-algorithm fingerprint containing AHash, PHash, and DHash results for enhanced accuracy.

#### Accessors

##### `exact_hash()`

```rust
pub fn exact_hash(&self) -> &[u8; 32]
```

Returns the BLAKE3 hash of original image bytes.

##### `ahash()`

```rust
pub fn ahash(&self) -> &ImageFingerprint
```

Returns the AHash-based fingerprint (global + block hashes).

##### `phash()`

```rust
pub fn phash(&self) -> &ImageFingerprint
```

Returns the PHash-based fingerprint (global + block hashes).

##### `dhash()`

```rust
pub fn dhash(&self) -> &ImageFingerprint
```

Returns the DHash-based fingerprint (global + block hashes).

**Example:**
```rust
let multi = ImageFingerprinter::fingerprint(&image_bytes)?;

// Access individual algorithms
let ahash = multi.ahash();
let phash = multi.phash();
let dhash = multi.dhash();

println!("AHash: {:016x}", ahash.global_hash());
println!("PHash: {:016x}", phash.global_hash());
println!("DHash: {:016x}", dhash.global_hash());
```

##### `get()`

```rust
pub fn get(&self, algorithm: HashAlgorithm) -> &ImageFingerprint
```

Returns the fingerprint for a specific algorithm.

**Example:**
```rust
use imgfprint::HashAlgorithm;

let fp = multi.get(HashAlgorithm::PHash);
```

#### Comparison Methods

##### `compare()`

```rust
pub fn compare(&self, other: &MultiHashFingerprint) -> Similarity
```

Compares two multi-hash fingerprints using weighted combination with block-level similarity for crop resistance.

**Default Algorithm Weights** (override with `MultiHashConfig`):
- **10%** AHash similarity (average hash, fastest)
- **60%** PHash similarity (DCT-based, robust to compression)
- **30%** DHash similarity (gradient-based, good for structural changes)

**Default Per-Algorithm Composition** (override with `global_weight` / `block_weight`):
- **40%** global hash similarity (overall structure)
- **60%** block-level similarity (4x4 grid, crop resistance)

This provides superior crop resistance compared to global-only comparison. See `compare_with_config()` below to retune any of these weights without forking the crate.

**Example:**
```rust
let multi1 = ImageFingerprinter::fingerprint(&img1)?;
let multi2 = ImageFingerprinter::fingerprint(&img2)?;

// Weighted comparison
let sim = multi1.compare(&multi2);
println!("Combined similarity: {:.2}", sim.score);
```

##### `compare_with_config()`

```rust
pub fn compare_with_config(
    &self,
    other: &MultiHashFingerprint,
    config: &MultiHashConfig,
) -> Similarity
```

Tunable variant of `compare()`. All six weights and the block distance threshold are integrator-controlled — defaults reproduce `compare()` exactly. Setting any algorithm weight to `0.0` removes it from the score (no need to skip the hash at compute time).

```rust
use imgfprint::MultiHashConfig;

// PHash-only scoring
let cfg = MultiHashConfig {
    ahash_weight: 0.0,
    phash_weight: 1.0,
    dhash_weight: 0.0,
    ..MultiHashConfig::default()
};
let sim = multi1.compare_with_config(&multi2, &cfg);
```

##### `compare_with_threshold()`

```rust
pub fn compare_with_threshold(
    &self,
    other: &MultiHashFingerprint,
    block_threshold: u32,
) -> Similarity
```

Convenience wrapper for the common case of overriding only `block_distance_threshold`. Equivalent to `compare_with_config` with that one field changed.

##### `is_similar()`

```rust
pub fn is_similar(&self, other: &MultiHashFingerprint, threshold: f32) -> bool
```

Checks if this fingerprint is similar to another within a threshold using the weighted comparison score.

**Note:** Out-of-range or NaN thresholds are clamped to [0.0, 1.0] (returns `false` if clamped to 0.0). Panics in debug mode to help catch bugs during development.

**Example:**
```rust
if multi1.is_similar(&multi2, 0.8) {
    println!("Images are similar!");
}
```

---

### Similarity

Similarity score between two image fingerprints.

#### Fields

| Field | Type | Description |
|-------|------|-------------|
| `score` | `f32` | Similarity 0.0 (different) to 1.0 (identical) |
| `exact_match` | `bool` | True if BLAKE3 hashes match |
| `perceptual_distance` | `u32` | Hamming distance 0-64 |

#### Methods

##### `perfect()`

```rust
pub fn perfect() -> Self
```

Returns a perfect similarity score (1.0, exact match).

**Example:**
```rust
let perfect = imgfprint::Similarity::perfect();
assert_eq!(perfect.score, 1.0);
assert!(perfect.exact_match);
```

---

## Advanced Usage

### Batch Processing

Process thousands of images efficiently using two strategies:

#### Strategy 1: Parallel Batch (Best for Many Images)

Uses all CPU cores for maximum throughput:

```rust
use imgfprint::ImageFingerprinter;
use std::path::Path;

fn process_parallel(image_paths: &[&Path]) -> Result<(), Box<dyn std::error::Error>> {
    let images: Vec<_> = image_paths
        .iter()
        .map(|p| (p.to_string_lossy().to_string(), std::fs::read(p).unwrap()))
        .collect();
    
    let results = ImageFingerprinter::fingerprint_batch(&images);
    
    for (path, result) in results {
        match result {
            Ok(fp) => {
                // Store in database, index, etc.
                db.store(&path, fp)?;
            }
            Err(e) => eprintln!("Failed to process {}: {}", path, e),
        }
    }
    
    Ok(())
}
```

**Speedup:** 2-3x on multi-core systems.

#### Strategy 2: Streaming with Context (Best for Memory-Constrained)

Lower memory usage, ideal for processing large datasets:

```rust
use imgfprint::FingerprinterContext;
use std::path::Path;

fn process_streaming(image_paths: &[&Path]) -> Result<(), Box<dyn std::error::Error>> {
    let mut ctx = FingerprinterContext::new();
    
    for path in image_paths {
        let bytes = std::fs::read(path)?;
        let fp = ctx.fingerprint(&bytes)?;
        db.store(&path.to_string_lossy(), fp)?;
    }
    
    Ok(())
}
```

**Benefit:** Minimal memory footprint, processes images one at a time.

#### Strategy 3: Chunked Batch (Best for Large Batches)

Combines memory efficiency with callback-based processing for very large datasets:

```rust
use imgfprint::ImageFingerprinter;
use std::path::Path;

fn process_chunked(image_paths: &[&Path]) -> Result<usize, Box<dyn std::error::Error>> {
    let images: Vec<_> = image_paths
        .iter()
        .map(|p| (p.to_string_lossy().to_string(), std::fs::read(p).unwrap()))
        .collect();
    
    let mut count = 0;
    
    // Process in chunks of 100 to limit memory
    ImageFingerprinter::fingerprint_batch_chunked(&images, 100, |id, result| {
        match result {
            Ok(fp) => {
                db.store(&id, fp).unwrap();
                count += 1;
            }
            Err(e) => eprintln!("Failed to process {}: {}", id, e),
        }
    });
    
    Ok(count)
}
```

**Benefit:** Bounded memory usage (100 images max), callback enables immediate processing/streaming results.

#### Strategy 4: Streaming Iterator (Best for Disk-Based Pipelines)

Lazily reads and fingerprints images one at a time from an iterator of paths. No need to load all images into memory:

```rust
use imgfprint::ImageFingerprinter;
use std::path::PathBuf;

fn process_streaming(paths: Vec<PathBuf>) -> Result<(), Box<dyn std::error::Error>> {
    for (path, result) in ImageFingerprinter::fingerprint_stream(paths.into_iter()) {
        match result {
            Ok(fp) => println!("{}: {}", path.display(), fp),
            Err(e) => eprintln!("{}: {}", path.display(), e),
        }
    }
    Ok(())
}
```

**Benefit:** Zero upfront memory — each image is read, fingerprinted, and dropped before the next. Uses a single `FingerprinterContext` internally for buffer reuse.

### Semantic Embeddings

Work with CLIP-style semantic embeddings (requires custom provider implementation).

#### Custom Provider Example

```rust
use imgfprint::{Embedding, EmbeddingProvider, ImgFprintError};

struct MyClipProvider {
    client: reqwest::Client,
    api_key: String,
}

impl EmbeddingProvider for MyClipProvider {
    fn embed(&self, image: &[u8]) -> Result<Embedding, ImgFprintError> {
        let response = self.client
            .post("https://api.example.com/embed")
            .header("Authorization", &self.api_key)
            .body(image.to_vec())
            .send()
            .map_err(|e| ImgFprintError::ProviderError(e.to_string()))?;
        
        let vector: Vec<f32> = response.json()
            .map_err(|e| ImgFprintError::ProviderError(e.to_string()))?;
        
        Embedding::new(vector)
    }
}

// Usage
let provider = MyClipProvider { 
    client: reqwest::Client::new(),
    api_key: "your-api-key".to_string(),
};

let img1 = std::fs::read("cat.jpg")?;
let img2 = std::fs::read("dog.jpg")?;

let emb1 = provider.embed(&img1)?;
let emb2 = provider.embed(&img2)?;

let similarity = imgfprint::semantic_similarity(&emb1, &emb2)?;
println!("Semantic similarity: {:.4}", similarity);
```

**Note:** The library does not include built-in embedding providers. You must implement `EmbeddingProvider` for your use case (OpenAI CLIP, HuggingFace, local models, etc.).

#### Model ID Support

To prevent accidental comparison of embeddings from different models (e.g., comparing 512-dim CLIP with 768-dim CLIP), you can tag embeddings with a model identifier:

```rust
use imgfprint::Embedding;

// Create embedding with model ID
let embedding = Embedding::new_with_model(
    vec![0.1, 0.2, 0.3, /* ... 512 dimensions */],
    Some("clip-vit-base-patch32".to_string())
)?;

// The model ID is validated during similarity comparison
let sim = imgfprint::semantic_similarity(&emb1, &emb2)?;
// Will error if model IDs differ (prevents meaningless comparisons)
```

**Benefits:**
- Prevents comparing incompatible embeddings
- Explicit model tracking for audit trails
- Backward compatible (model_id is optional)

#### Local ONNX Models

With the `local-embedding` feature:

```toml
[dependencies]
imgfprint = { version = "0.4.3", features = ["local-embedding"] }
```

```rust
use imgfprint::{LocalProvider, LocalProviderConfig};

// Load CLIP model
let provider = LocalProvider::from_file("clip-vit-base-patch32.onnx")?;

// Or with custom configuration
let config = LocalProviderConfig::clip_vit_large_patch14();
let provider = LocalProvider::from_file_with_config("clip.onnx", config)?;

// Generate embedding
let image = std::fs::read("photo.jpg")?;
let embedding = provider.embed(&image)?;

println!("Generated {}-dimensional embedding", embedding.len());
```

---

## Error Handling

All operations return `Result<T, ImgFprintError>`. The library never panics on malformed input.

### Creating Errors (For SDK Users)

When implementing custom providers or extending the library, use the optimized error constructors:

```rust
use imgfprint::ImgFprintError;

let err = ImgFprintError::decode_error("invalid format");
let err = ImgFprintError::invalid_image("dimensions too large");
let err = ImgFprintError::processing_error("resize failed");

let size = 1024;
let err = ImgFprintError::invalid_image(format!("image too large: {} bytes", size));
```

### Complete Error Handling Example

```rust
use imgfprint::{ImageFingerprinter, ImgFprintError};

match ImageFingerprinter::fingerprint(&bytes) {
    Ok(fp) => {
        // Use fingerprint
    }
    
    // Image-related errors
    Err(ImgFprintError::InvalidImage(msg)) => {
        eprintln!("Invalid image: {}", msg);
    }
    Err(ImgFprintError::UnsupportedFormat(msg)) => {
        eprintln!("Unsupported format: {}", msg);
    }
    Err(ImgFprintError::DecodeError(msg)) => {
        eprintln!("Decode failed: {}", msg);
    }
    Err(ImgFprintError::ImageTooSmall(msg)) => {
        eprintln!("Image too small: {} (minimum 32x32)", msg);
    }
    
    // Processing errors
    Err(ImgFprintError::ProcessingError(msg)) => {
        eprintln!("Processing error: {}", msg);
    }
    
    // Embedding errors
    Err(ImgFprintError::EmbeddingDimensionMismatch { expected, actual }) => {
        eprintln!("Dimension mismatch: expected {}, got {}", expected, actual);
    }
    Err(ImgFprintError::ProviderError(msg)) => {
        eprintln!("Provider error: {}", msg);
    }
    Err(ImgFprintError::InvalidEmbedding(msg)) => {
        eprintln!("Invalid embedding: {}", msg);
    }
}
```

---

## Performance Tips

### 1. Use Context API for High Throughput

**Avoid:** Creating new allocations for each image
```rust
// Slow: Allocates buffers for each image
for path in paths {
    let fp = ImageFingerprinter::fingerprint(&std::fs::read(path)?)?;
}
```

**Prefer:** Buffer reuse with context
```rust
// Fast: Reuses buffers
let mut ctx = FingerprinterContext::new();
for path in paths {
    let fp = ctx.fingerprint(&std::fs::read(path)?)?;
}
```

**Savings:** ~260KB allocations per image eliminated.

### 2. Use Batch Processing for Parallel Workloads

```rust
// Process 100 images in parallel using all CPU cores
let results = ImageFingerprinter::fingerprint_batch(&images);
```

**Speedup:** 2-3x on multi-core systems.

### 3. Check Exact Hash First

```rust
// Fast path: exact byte match (BLAKE3 comparison)
if fp1.exact_hash() == fp2.exact_hash() {
    return Similarity::perfect();
}

// Slow path: perceptual comparison (DCT-based)
let sim = ImageFingerprinter::compare(&fp1, &fp2);
```

### 4. Handle Common Formats Efficiently

Supported formats: PNG, JPEG, GIF, WebP, BMP

| Format | Decode Speed | Best For |
|--------|--------------|----------|
| PNG | Fast | Synthetic images, screenshots |
| JPEG | Medium | Photos (use `zune-jpeg` for 2-3x speedup) |
| WebP | Slow | Web-optimized images |
| GIF | Fast | Simple graphics |

### 5. Image Size Guidelines

| Metric | Value | Notes |
|--------|-------|-------|
| Minimum | 32x32 pixels | Enforced |
| Maximum | 8192x8192 pixels | OOM protection |
| Optimal | 256x512 to 1024x1024 | Best performance |

**Note:** Larger images are downscaled to 256x256 internally, so feeding very large images wastes decode time.

### 6. `no_std` Comparison Layer

The similarity computation module (`Similarity`, `compute_similarity`, `hamming_distance`, `hash_similarity`) uses only `core` primitives and the `subtle` crate — both `no_std`-compatible. If you only need to *compare* pre-computed fingerprints (e.g., on an embedded target or in a WASM module), the comparison types work without `std`.

---

## Feature Flags

Configure the library for your needs:

```toml
[dependencies]
# Minimal build (no parallel processing)
imgfprint = { version = "0.4.3", default-features = false }

# Default (serialization + parallel processing)
imgfprint = "0.4.3"

# With local ONNX inference
imgfprint = { version = "0.4.3", features = ["local-embedding"] }
```

### Available Features

| Feature | Default | Description |
|---------|---------|-------------|
| `serde` | Yes | Serialization support (JSON, binary) via serde |
| `parallel` | Yes | Parallel batch processing using rayon |
| `local-embedding` | No | Local ONNX model inference for embeddings |
| `tracing` | No | Observability hooks for production debugging |

### Tracing Feature

Enable the `tracing` feature to add performance instrumentation:

```toml
[dependencies]
imgfprint = { version = "0.4.3", features = ["tracing"] }
```

```rust
use imgfprint::ImageFingerprinter;

// Initialize a tracing subscriber with env filter
// RUST_LOG env var controls the log level: info, debug, trace
std::env::set_var("RUST_LOG", "info");
tracing_subscriber::fmt::init();

// Now all fingerprint operations emit trace events
let fp = ImageFingerprinter::fingerprint(&data).unwrap();
// Outputs: TRACE fingerprinter: fingerprint completed duration_ms=1.23
```

**Environment Variables:**
- `RUST_LOG=info` - Show info level and above
- `RUST_LOG=debug` - Show debug level and above  
- `RUST_LOG=trace` - Show all trace events (most verbose)
- `RUST_LOG=imgfprint=debug` - Show only imgfprint debug logs

**Traced Operations:**
- `fingerprint()` - tracks image size, duration
- `fingerprint_with()` - tracks image size, algorithm, duration
- `fingerprint_batch()` - tracks image count, parallel/sequential, duration
- `fingerprint_batch_chunked()` - tracks chunk_size, processed count, failed count, duration

**Note:** If you don't see output, ensure you've set `RUST_LOG` before calling `tracing_subscriber::fmt::init()`. The subscriber uses an env filter by default which requires this environment variable.

---

## License

MIT License - See LICENSE file for details.

## Links

- [Crates.io](https://crates.io/crates/imgfprint)
- [Documentation](https://docs.rs/imgfprint)
- [Repository](https://github.com/themankindproject/imgfprint-rs)
