photodna 1.5.1

Safe, high-level Rust bindings for the Microsoft PhotoDNA Edge Hash Generator
Documentation

photodna

Safe, high-level Rust bindings for the Microsoft PhotoDNA Edge Hash Generator.

Crates.io Documentation License

Overview

PhotoDNA is a perceptual hashing technology that creates a compact "fingerprint" of an image. This fingerprint can be used to identify visually similar images even after modifications like resizing, cropping, color adjustments, or format conversion.

This crate provides a safe, ergonomic Rust API on top of the low-level photodna-sys bindings.

Features

  • Safe API – All unsafe FFI operations are encapsulated behind a safe interface
  • Zero-Copy Hashes – The Hash type uses a fixed-size stack array (no heap allocation)
  • Typed Errors – Comprehensive error handling via PhotoDnaError
  • Builder Pattern – Ergonomic configuration via GeneratorOptions and HashOptions

Requirements

This crate requires the proprietary Microsoft PhotoDNA SDK.

Before building, you must set the PHOTODNA_SDK_ROOT environment variable to point to the SDK installation directory:

export PHOTODNA_SDK_ROOT=/path/to/PhotoDNA.EdgeHashGeneration-1.05.001

See the photodna-sys crate documentation for detailed setup instructions.

Quick Start

use photodna::{Generator, GeneratorOptions, Hash, Result};

fn main() -> Result<()> {
    // Initialize the generator (loads the PhotoDNA library)
    let generator = Generator::new(GeneratorOptions::default())?;

    // Load your image as raw RGB pixels (e.g., using the `image` crate)
    let image_data: Vec<u8> = load_image_as_rgb("photo.jpg");
    let width = 640;
    let height = 480;

    // Compute the hash
    let hash: Hash = generator.compute_hash_rgb(&image_data, width, height)?;

    // Use the hash (e.g., store in database as hex)
    println!("Hash: {}", hash.to_hex());

    Ok(())
}

API Overview

Generator

The main entry point. Manages the PhotoDNA library lifecycle:

use photodna::{Generator, GeneratorOptions};

let generator = Generator::new(
    GeneratorOptions::new()
        .max_threads(4)
)?;

// Check library version
println!("PhotoDNA v{}", generator.library_version_text().unwrap_or("?"));

Hash

A fixed-size (924 bytes) perceptual hash with zero-copy semantics:

use photodna::Hash;

// Format as hex for storage
let hex: String = hash.to_hex();

// Parse from hex
let hash = Hash::from_hex(&hex).unwrap();

// Access raw bytes
let bytes: &[u8] = hash.as_bytes();

// Use as HashMap key (implements Hash trait)
use std::collections::HashSet;
let mut seen = HashSet::new();
seen.insert(hash);

Pixel Formats

Multiple pixel formats are supported:

use photodna::{HashOptions, PixelFormat};

let options = HashOptions::new()
    .pixel_format(PixelFormat::Bgra)  // Common in Windows
    .remove_border(true);             // Auto-detect and remove borders

Supported formats:

  • Rgb, Bgr (3 bytes/pixel)
  • Rgba, Bgra, Argb, Abgr (4 bytes/pixel)
  • Gray8 (1 byte/pixel), Gray32 (4 bytes/pixel)
  • Cmyk, YCbCr, Yuv420p

Border Detection

Automatically detect and remove borders from images:

let result = generator.compute_hash_with_border_detection(
    &image_data,
    width,
    height,
    HashOptions::default(),
)?;

println!("Primary hash: {}", result.primary.to_hex());

if let Some(borderless) = result.borderless {
    println!("Borderless hash: {}", borderless.to_hex());
    if let Some((x, y, w, h)) = result.content_region {
        println!("Content region: {}x{} at ({}, {})", w, h, x, y);
    }
}

Error Handling

All operations return Result<T, PhotoDnaError>:

use photodna::{PhotoDnaError, Result};

fn process_image(data: &[u8], w: u32, h: u32) -> Result<()> {
    match generator.compute_hash_rgb(data, w, h) {
        Ok(hash) => println!("Success: {}", hash),
        Err(PhotoDnaError::ImageTooSmall) => {
            eprintln!("Image must be at least 50x50 pixels");
        }
        Err(PhotoDnaError::ImageIsFlat) => {
            eprintln!("Image has insufficient detail for hashing");
        }
        Err(e) => return Err(e),
    }
    Ok(())
}

Thread Safety

Generator implements Send but not Sync. Options for concurrent use:

// Option 1: One generator per thread
let generator = Generator::new(GeneratorOptions::new().max_threads(1))?;

// Option 2: Shared with Mutex
use std::sync::{Arc, Mutex};
let generator = Arc::new(Mutex::new(Generator::new(GeneratorOptions::default())?));

// Option 3: Use max_threads for internal parallelism
let generator = Generator::new(GeneratorOptions::new().max_threads(8))?;
// Internal operations can use up to 8 threads

Platform Support

Platform Architecture Support
Windows x86_64, x86, arm64 ✅ Native library
Linux x86_64, x86, arm64 ✅ Native library
macOS x86_64, arm64 ✅ Native library
BSD any 🔧 WebAssembly (via photodna-sys)

License

This crate is licensed under MIT OR Apache-2.0.

Note: The PhotoDNA library itself is proprietary software from Microsoft. Usage of PhotoDNA requires a separate license agreement with Microsoft.